diff --git a/.github/workflows/security-release-gate.yml b/.github/workflows/security-release-gate.yml index 4c30d634e9..b5852a988c 100644 --- a/.github/workflows/security-release-gate.yml +++ b/.github/workflows/security-release-gate.yml @@ -57,6 +57,31 @@ jobs: import xml.etree.ElementTree as ET required = { + 'io.cattle.platform.core.dao.impl.NativeContainerNameRefreshDaoTest': { + 'exactExistingImportedSnapshotAndNameOnlyCas', + 'compareAndSwapContainsEveryOriginalColumnIncludingBinaryTextAndJson', + 'zeroRowCasAndSameNameDoNotReportSuccess', + 'unknownDuplicateOrWrongExactDockerIdCannotBecomeSnapshot', + 'foreignSourceOrWrongResourceAccountNeverReachesQuery', + 'managedServiceSystemEnvironmentAndLifecycleRowsAreRejected', + 'everyNonremovedMapMustBeUniqueAtSelectionAndAtCas', + }, + 'io.cattle.platform.process.containerevent.NativeContainerNameRefreshTest': { + 'currentInspectNameNotUuidHintIsWrittenOnceAndNotificationIsScoped', + 'runningContainerAndSecondDistinctFullIdUseExactIndependentAuthority', + 'absentCandidateOrForeignHostCannotInspectOrCreate', + 'sourceAccountChangedAfterLockSelectionCannotRefreshForeignInstance', + 'actualAgentInspectRequestUsesOnlyFullDockerIdNeverNameFallback', + 'managedLabelShortIdOrDifferentStateCannotReachInspection', + 'inspectFailureWrongIdOrMalformedNameNeverWritesHint', + 'casMissDoesNotNotifyOrRetryAndDelayedHintCannotRollNameBack', + 'notificationFailurePreservesKnownCommittedUpdateWithoutRetry', + }, + 'io.cattle.platform.ha.monitor.impl.PingInstancesMonitorImplTest': { + 'testDetermineSyncActions', + 'changedUuidHintUsesExactKnownDockerIdWithoutNamePairing', + 'unknownRemovedAndAmbiguousKnownDockerIdsCannotBecomeNameCandidates', + }, 'io.cattle.platform.schema.processor.GenericObjectAuthOverlayTest': { 'lowRolesHideCapabilitiesAtTheSharedResponseBoundary', 'inheritedStorageFieldsCannotReintroduceCapabilities', diff --git a/README.md b/README.md index da628ddaf2..cac2843f67 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,15 @@ preserved upstream boundary. ## Current release +The source tree prepares `v0.183.330`, a narrowly scoped imported-container +name refresh. A ping's UUID/name hint is never written as the authoritative +name: the Engine inspects the exact full Docker ID through its existing Agent +contract, and performs a name-only compare-and-swap on an eligible existing +native container. Managed service/stack names and lifecycle processing are +unchanged. See the [330 release note](docs/releases/orchestration-engine-0.183.330.md). +The focused offline checks have passed; a published WAR, consuming Server +image, and real-host/browser name-refresh acceptance are still required. + The current published release is `v0.183.329`. It closes a low-role GenericObject read bypass for plugin capabilities in both v2-beta and the frozen v1 schemas. Readonly/restricted clients retain resource metadata; plugin configuration is @@ -130,7 +139,7 @@ bash scripts/check-cattle-jdk25-full-package After the gate passes, package and check the release artifact: ```sh -ENGINE_VERSION=0.183.329 bash scripts/build --release +ENGINE_VERSION=0.183.330 bash scripts/build --release bash scripts/check-release-artifact dist/artifacts/cattle.jar ``` diff --git a/code/framework/api-pub-sub-jetty/pom.xml b/code/framework/api-pub-sub-jetty/pom.xml index 6837ac40ed..3631001909 100644 --- a/code/framework/api-pub-sub-jetty/pom.xml +++ b/code/framework/api-pub-sub-jetty/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/api-pub-sub/pom.xml b/code/framework/api-pub-sub/pom.xml index ff78e3e8b9..c827c57cf6 100644 --- a/code/framework/api-pub-sub/pom.xml +++ b/code/framework/api-pub-sub/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/api/pom.xml b/code/framework/api/pom.xml index 14d87e0ba0..999f7c9a84 100644 --- a/code/framework/api/pom.xml +++ b/code/framework/api/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/archaius/pom.xml b/code/framework/archaius/pom.xml index fd11c24d2a..6b3ce1d741 100644 --- a/code/framework/archaius/pom.xml +++ b/code/framework/archaius/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-meta-parent - 0.183.329 + 0.183.330 ../../meta-parent/pom.xml diff --git a/code/framework/async/pom.xml b/code/framework/async/pom.xml index 90f4f1af0f..3cbf6ada13 100644 --- a/code/framework/async/pom.xml +++ b/code/framework/async/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/auditing/pom.xml b/code/framework/auditing/pom.xml index 794c5f0dde..e7a6894dff 100644 --- a/code/framework/auditing/pom.xml +++ b/code/framework/auditing/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/db-loader/pom.xml b/code/framework/db-loader/pom.xml index 4de2f090e9..4bd136a808 100644 --- a/code/framework/db-loader/pom.xml +++ b/code/framework/db-loader/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/deferred/pom.xml b/code/framework/deferred/pom.xml index dd2a0fc3fc..256d1cd722 100644 --- a/code/framework/deferred/pom.xml +++ b/code/framework/deferred/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/encryption/pom.xml b/code/framework/encryption/pom.xml index 68fefe8e68..c0de0ce421 100644 --- a/code/framework/encryption/pom.xml +++ b/code/framework/encryption/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/engine/pom.xml b/code/framework/engine/pom.xml index cd18c3739c..27c4004cdc 100644 --- a/code/framework/engine/pom.xml +++ b/code/framework/engine/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/eventing/pom.xml b/code/framework/eventing/pom.xml index 3fc4bb586a..539c517b5f 100644 --- a/code/framework/eventing/pom.xml +++ b/code/framework/eventing/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/events/pom.xml b/code/framework/events/pom.xml index c57596ebd3..d7153d9173 100644 --- a/code/framework/events/pom.xml +++ b/code/framework/events/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/extension-spring/pom.xml b/code/framework/extension-spring/pom.xml index b968d564c1..4009745597 100644 --- a/code/framework/extension-spring/pom.xml +++ b/code/framework/extension-spring/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/extension/pom.xml b/code/framework/extension/pom.xml index 464eb12f5b..552d06b6e9 100644 --- a/code/framework/extension/pom.xml +++ b/code/framework/extension/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/java-server/pom.xml b/code/framework/java-server/pom.xml index bce836552e..1b313d3251 100644 --- a/code/framework/java-server/pom.xml +++ b/code/framework/java-server/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/jmx/pom.xml b/code/framework/jmx/pom.xml index 7478e65308..0eb54165d2 100644 --- a/code/framework/jmx/pom.xml +++ b/code/framework/jmx/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/jooq/pom.xml b/code/framework/jooq/pom.xml index 81ee08f8ae..a4c4bcad58 100644 --- a/code/framework/jooq/pom.xml +++ b/code/framework/jooq/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/json/pom.xml b/code/framework/json/pom.xml index abbc8b2632..91c7e829fe 100644 --- a/code/framework/json/pom.xml +++ b/code/framework/json/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/launcher/pom.xml b/code/framework/launcher/pom.xml index e283623767..dd85d69296 100644 --- a/code/framework/launcher/pom.xml +++ b/code/framework/launcher/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/lock/pom.xml b/code/framework/lock/pom.xml index a234ba5612..514a32bb76 100644 --- a/code/framework/lock/pom.xml +++ b/code/framework/lock/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/logback/pom.xml b/code/framework/logback/pom.xml index 65c2716acb..e930cfb291 100644 --- a/code/framework/logback/pom.xml +++ b/code/framework/logback/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-meta-parent - 0.183.329 + 0.183.330 ../../meta-parent/pom.xml diff --git a/code/framework/managed-context/pom.xml b/code/framework/managed-context/pom.xml index 195c02bbd5..c0a1f92640 100644 --- a/code/framework/managed-context/pom.xml +++ b/code/framework/managed-context/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/metrics/pom.xml b/code/framework/metrics/pom.xml index d02ec9f844..dc8751bcfd 100644 --- a/code/framework/metrics/pom.xml +++ b/code/framework/metrics/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/module/pom.xml b/code/framework/module/pom.xml index 32423e40b3..c11ab133be 100644 --- a/code/framework/module/pom.xml +++ b/code/framework/module/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/object/pom.xml b/code/framework/object/pom.xml index 363ff179f2..2bfd2de5f4 100644 --- a/code/framework/object/pom.xml +++ b/code/framework/object/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/pool/pom.xml b/code/framework/pool/pom.xml index 6d7fb5d0c2..4882005854 100644 --- a/code/framework/pool/pom.xml +++ b/code/framework/pool/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/resource-monitor/pom.xml b/code/framework/resource-monitor/pom.xml index 3281175d30..c12e58ce1f 100644 --- a/code/framework/resource-monitor/pom.xml +++ b/code/framework/resource-monitor/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/schema/pom.xml b/code/framework/schema/pom.xml index 329af7ff57..da386f1c36 100644 --- a/code/framework/schema/pom.xml +++ b/code/framework/schema/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/server/pom.xml b/code/framework/server/pom.xml index 35d6f444d0..d0d12d4950 100644 --- a/code/framework/server/pom.xml +++ b/code/framework/server/pom.xml @@ -4,7 +4,7 @@ cattle-meta-parent io.cattle - 0.183.329 + 0.183.330 ../../meta-parent/pom.xml diff --git a/code/framework/spring/pom.xml b/code/framework/spring/pom.xml index b89ec08da8..84137d5a90 100644 --- a/code/framework/spring/pom.xml +++ b/code/framework/spring/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/system-task/pom.xml b/code/framework/system-task/pom.xml index 968064bec3..7de497beb4 100644 --- a/code/framework/system-task/pom.xml +++ b/code/framework/system-task/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/token/pom.xml b/code/framework/token/pom.xml index 58b4195033..df44cfdae8 100644 --- a/code/framework/token/pom.xml +++ b/code/framework/token/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/framework/utils/pom.xml b/code/framework/utils/pom.xml index 1310372727..5ed126be07 100644 --- a/code/framework/utils/pom.xml +++ b/code/framework/utils/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/agent-instance/pom.xml b/code/iaas/agent-instance/pom.xml index d11713003b..6279754c85 100644 --- a/code/iaas/agent-instance/pom.xml +++ b/code/iaas/agent-instance/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/agent-server/pom.xml b/code/iaas/agent-server/pom.xml index 89a90ed9c2..df6fec1528 100644 --- a/code/iaas/agent-server/pom.xml +++ b/code/iaas/agent-server/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/agent/pom.xml b/code/iaas/agent/pom.xml index e98234dbd5..b85cff227d 100644 --- a/code/iaas/agent/pom.xml +++ b/code/iaas/agent/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/allocator/pom.xml b/code/iaas/allocator/pom.xml index 9e495069af..c8a87aebcd 100644 --- a/code/iaas/allocator/pom.xml +++ b/code/iaas/allocator/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/api-logic/pom.xml b/code/iaas/api-logic/pom.xml index 0f4bf4634c..dc2cf759b8 100644 --- a/code/iaas/api-logic/pom.xml +++ b/code/iaas/api-logic/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/archaius-management/pom.xml b/code/iaas/archaius-management/pom.xml index bc93c65417..882a1f4181 100644 --- a/code/iaas/archaius-management/pom.xml +++ b/code/iaas/archaius-management/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/auth-logic/pom.xml b/code/iaas/auth-logic/pom.xml index b94cfce7f6..926c53e742 100644 --- a/code/iaas/auth-logic/pom.xml +++ b/code/iaas/auth-logic/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/bootstrap/pom.xml b/code/iaas/bootstrap/pom.xml index fb5671c925..d4e0af5de2 100644 --- a/code/iaas/bootstrap/pom.xml +++ b/code/iaas/bootstrap/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/config-item/api/pom.xml b/code/iaas/config-item/api/pom.xml index f3ad97e8d0..1f6ab2ed49 100644 --- a/code/iaas/config-item/api/pom.xml +++ b/code/iaas/config-item/api/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../../parent/pom.xml diff --git a/code/iaas/config-item/common/pom.xml b/code/iaas/config-item/common/pom.xml index 169fd7d1c2..971e702218 100644 --- a/code/iaas/config-item/common/pom.xml +++ b/code/iaas/config-item/common/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../../parent/pom.xml diff --git a/code/iaas/config-item/server/pom.xml b/code/iaas/config-item/server/pom.xml index 99370dc09a..2effdb06dd 100644 --- a/code/iaas/config-item/server/pom.xml +++ b/code/iaas/config-item/server/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../../parent/pom.xml diff --git a/code/iaas/engine-jooq/pom.xml b/code/iaas/engine-jooq/pom.xml index 69ce2ba412..1a3dabe7ed 100644 --- a/code/iaas/engine-jooq/pom.xml +++ b/code/iaas/engine-jooq/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/events/pom.xml b/code/iaas/events/pom.xml index 1e33aba6f4..c77740ae73 100644 --- a/code/iaas/events/pom.xml +++ b/code/iaas/events/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/external-handler/pom.xml b/code/iaas/external-handler/pom.xml index 98ac47e63a..c652bb536c 100644 --- a/code/iaas/external-handler/pom.xml +++ b/code/iaas/external-handler/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/ha/pom.xml b/code/iaas/ha/pom.xml index cf84982670..ce83cfba88 100644 --- a/code/iaas/ha/pom.xml +++ b/code/iaas/ha/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/ha/src/main/java/io/cattle/platform/ha/monitor/impl/PingInstancesMonitorImpl.java b/code/iaas/ha/src/main/java/io/cattle/platform/ha/monitor/impl/PingInstancesMonitorImpl.java index 6a4a92a05e..2506ad3bfd 100644 --- a/code/iaas/ha/src/main/java/io/cattle/platform/ha/monitor/impl/PingInstancesMonitorImpl.java +++ b/code/iaas/ha/src/main/java/io/cattle/platform/ha/monitor/impl/PingInstancesMonitorImpl.java @@ -35,9 +35,11 @@ import java.time.Duration; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import jakarta.inject.Inject; @@ -122,6 +124,8 @@ public void pingReply(Ping ping) { return; } + refreshNativeNames(knownInstances, reportedInstances, agentId, agentAndHost.hostId); + try { syncContainers(knownInstances, reportedInstances, agentAndHost.agentAccountId, agentId, agentAndHost.hostId, true); } catch (ContainersOutOfSync e) { @@ -131,6 +135,25 @@ public void pingReply(Ping ping) { } } + void refreshNativeNames(Map knownInstances, ReportedInstances reportedInstances, + long agentId, long hostId) { + Set knownIds = new HashSet<>(); + Set ambiguousIds = new HashSet<>(); + for (KnownInstance known : knownInstances.values()) { + if (known.getRemoved() == null && known.getExternalId() != null && !knownIds.add(known.getExternalId())) { + ambiguousIds.add(known.getExternalId()); + } + } + for (ReportedInstance reported : reportedInstances.byExternalId.values()) { + String id = reported.getExternalId(); + if (knownIds.contains(id) && !ambiguousIds.contains(id)) { + ContainerEventCreate.NativeNameRefreshResult result = containerEventCreate.refreshNativeContainerName( + agentId, hostId, reportedInstances.hostUuid, id, reported.getUuid(), reported.getState(), reported.labels); + log.debug("Native container name refresh: agent [{}], host [{}], result [{}]", agentId, hostId, result); + } + } + } + @Override public void computeInstanceActivateReply(Event event) { Long agentId = monitorDao.getAgentIdForInstanceHostMap(event.getResourceId()); diff --git a/code/iaas/ha/src/test/java/io/cattle/platform/ha/monitor/impl/PingInstancesMonitorImplTest.java b/code/iaas/ha/src/test/java/io/cattle/platform/ha/monitor/impl/PingInstancesMonitorImplTest.java index 32dbb37af4..48bf56d6ae 100644 --- a/code/iaas/ha/src/test/java/io/cattle/platform/ha/monitor/impl/PingInstancesMonitorImplTest.java +++ b/code/iaas/ha/src/test/java/io/cattle/platform/ha/monitor/impl/PingInstancesMonitorImplTest.java @@ -7,11 +7,14 @@ import io.cattle.platform.ha.monitor.model.KnownInstance; import io.cattle.platform.object.meta.impl.DefaultObjectMetaDataManager; +import io.cattle.platform.process.containerevent.ContainerEventCreate; +import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.List; import java.util.Set; import org.junit.Before; @@ -161,6 +164,50 @@ public void testDetermineSyncActions() { assertSyncAction(externalIdFF, EVENT_START); } + @Test + public void changedUuidHintUsesExactKnownDockerIdWithoutNamePairing() { + String first = "a".repeat(64), second = "b".repeat(64); + addKnownInstance("first-logical", first, STATE_STOPPED, null); + addKnownInstance("second-logical", second, STATE_RUNNING, null); + addReportedInstance("same-new-name", first, STATE_STOPPED).labels = new HashMap<>(); + addReportedInstance("same-new-name", second, STATE_RUNNING).labels = new HashMap<>(); + List refreshes = new ArrayList<>(); + monitor.containerEventCreate = nameRefreshRecorder(refreshes); + reportedInstances.hostUuid = "host-uuid"; + monitor.refreshNativeNames(knownInstances, reportedInstances, 1L, 2L); + assertEquals(2, refreshes.size()); + assertTrue(refreshes.contains(first)); assertTrue(refreshes.contains(second)); + assertTrue(needsSynced.isEmpty()); assertTrue(syncActions.isEmpty()); + } + + @Test + public void unknownRemovedAndAmbiguousKnownDockerIdsCannotBecomeNameCandidates() { + String removed = "a".repeat(64), duplicate = "b".repeat(64), unknown = "c".repeat(64); + addKnownInstance("removed", removed, REMOVED, new Date()); + addKnownInstance("one", duplicate, STATE_STOPPED, null); + addKnownInstance("two", duplicate, STATE_STOPPED, null); + for (String id : new String[] {removed, duplicate, unknown}) { + addReportedInstance("same-name", id, STATE_STOPPED).labels = new HashMap<>(); + } + List refreshes = new ArrayList<>(); + monitor.containerEventCreate = nameRefreshRecorder(refreshes); + monitor.refreshNativeNames(knownInstances, reportedInstances, 1L, 2L); + assertTrue(refreshes.isEmpty()); + } + + private ContainerEventCreate nameRefreshRecorder(List refreshes) { + return new ContainerEventCreate() { + @Override + public NativeNameRefreshResult refreshNativeContainerName(long agentId, long hostId, String hostUuid, + String externalId, String nameHint, String state, Map labels) { + assertEquals(1L, agentId); assertEquals(2L, hostId); + assertEquals("same-new-name", nameHint); assertEquals("host-uuid", hostUuid); + refreshes.add(externalId); + return NativeNameRefreshResult.UNCHANGED; + } + }; + } + void assertDoNothing(String externalId, String uuid) { assertTrue(!needsSynced.containsKey(externalId)); } diff --git a/code/iaas/healthcheck/pom.xml b/code/iaas/healthcheck/pom.xml index 708dc6fa1c..84fadbda67 100644 --- a/code/iaas/healthcheck/pom.xml +++ b/code/iaas/healthcheck/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/labels/pom.xml b/code/iaas/labels/pom.xml index 7355825de8..da578a745a 100644 --- a/code/iaas/labels/pom.xml +++ b/code/iaas/labels/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/logic-common/pom.xml b/code/iaas/logic-common/pom.xml index 593f912b16..bbe62ce75e 100644 --- a/code/iaas/logic-common/pom.xml +++ b/code/iaas/logic-common/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/logic/pom.xml b/code/iaas/logic/pom.xml index 300646ddef..e03e24d921 100644 --- a/code/iaas/logic/pom.xml +++ b/code/iaas/logic/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/logic/src/main/java/io/cattle/platform/process/containerevent/ContainerEventCreate.java b/code/iaas/logic/src/main/java/io/cattle/platform/process/containerevent/ContainerEventCreate.java index 8bb05db2c7..bddb88f5b6 100644 --- a/code/iaas/logic/src/main/java/io/cattle/platform/process/containerevent/ContainerEventCreate.java +++ b/code/iaas/logic/src/main/java/io/cattle/platform/process/containerevent/ContainerEventCreate.java @@ -20,6 +20,7 @@ import io.cattle.platform.core.dao.InstanceDao; import io.cattle.platform.core.dao.NetworkDao; import io.cattle.platform.core.model.ContainerEvent; +import io.cattle.platform.core.model.Agent; import io.cattle.platform.core.model.Host; import io.cattle.platform.core.model.Instance; import io.cattle.platform.engine.handler.HandlerResult; @@ -27,11 +28,14 @@ import io.cattle.platform.engine.process.ProcessState; import io.cattle.platform.engine.process.impl.ProcessCancelException; import io.cattle.platform.eventing.exception.EventExecutionException; +import io.cattle.platform.eventing.EventService; import io.cattle.platform.eventing.model.Event; import io.cattle.platform.eventing.model.EventVO; import io.cattle.platform.lock.LockCallback; import io.cattle.platform.lock.LockManager; import io.cattle.platform.object.process.StandardProcess; +import io.cattle.platform.framework.event.FrameworkEvents; +import io.cattle.platform.object.meta.ObjectMetaDataManager; import io.cattle.platform.object.resource.ResourceMonitor; import io.cattle.platform.object.util.DataAccessor; import io.cattle.platform.object.util.DataUtils; @@ -93,6 +97,95 @@ public class ContainerEventCreate extends AbstractDefaultProcessHandler { @Inject GenericResourceDao resourceDao; + @Inject + EventService eventService; + + public enum NativeNameRefreshResult { + DISABLED, SOURCE_UNVERIFIED, CANDIDATE_UNVERIFIED, UNCHANGED, + INSPECT_UNAVAILABLE, INSPECT_ID_MISMATCH, INSPECT_NAME_INVALID, CAS_MISS, + UPDATED, UPDATED_NOTIFICATION_FAILED, FAILED + } + + /** The legacy ping UUID is only a hint; inspection by full Docker ID is the name authority. */ + public NativeNameRefreshResult refreshNativeContainerName(long agentId, long hostId, String hostUuid, + String externalId, String nameHint, String reportedState, Map reportedLabels) { + if (!MANAGE_NONRANCHER_CONTAINERS.get()) { + return NativeNameRefreshResult.DISABLED; + } + if (externalId == null || !externalId.matches("[0-9a-f]{64}") || StringUtils.isBlank(nameHint) + || reportedLabels == null || reportedLabels.containsKey(LABEL_RANCHER_UUID) + || !java.util.Arrays.asList(STATE_RUNNING, STATE_STOPPED).contains(reportedState)) { + return NativeNameRefreshResult.CANDIDATE_UNVERIFIED; + } + try { + Host source = objectManager.loadResource(Host.class, hostId); + if (source == null || source.getAccountId() == null || !Long.valueOf(agentId).equals(source.getAgentId())) { + return NativeNameRefreshResult.SOURCE_UNVERIFIED; + } + return lockManager.lock(new ContainerEventInstanceLock(source.getAccountId(), externalId), + new LockCallback() { + @Override + public NativeNameRefreshResult doWithLock() { + Agent agent = objectManager.loadResource(Agent.class, agentId); + Host host = objectManager.loadResource(Host.class, hostId); + if (host == null || !source.getAccountId().equals(host.getAccountId())) { + return NativeNameRefreshResult.SOURCE_UNVERIFIED; + } + Instance original = instanceDao.getNativeContainerForNameRefresh(agent, host, hostUuid, externalId); + if (original == null || !externalId.equals(original.getExternalId()) + || !reportedState.equals(original.getState())) { + return NativeNameRefreshResult.CANDIDATE_UNVERIFIED; + } + if (nameHint.equals(original.getName())) { + return NativeNameRefreshResult.UNCHANGED; + } + Map inspect = inspectNativeNameById(agentId, externalId); + if (inspect == null || inspect.isEmpty()) { + return NativeNameRefreshResult.INSPECT_UNAVAILABLE; + } + if (!externalId.equals(inspect.get("Id"))) { + return NativeNameRefreshResult.INSPECT_ID_MISMATCH; + } + Object rawName = inspect.get(INSPECT_NAME); + if (!(rawName instanceof String)) { + return NativeNameRefreshResult.INSPECT_NAME_INVALID; + } + String name = ((String) rawName).replaceFirst("^/", ""); + if (!name.matches("[A-Za-z0-9][A-Za-z0-9_.-]{0,254}")) { + return NativeNameRefreshResult.INSPECT_NAME_INVALID; + } + if (name.equals(original.getName())) { + return NativeNameRefreshResult.UNCHANGED; + } + if (!instanceDao.updateNativeContainerName(original, agent, host, hostUuid, name)) { + return NativeNameRefreshResult.CAS_MISS; + } + Event changed = EventVO.newEvent(FrameworkEvents.STATE_CHANGE) + .withData(CollectionUtils.asMap(ObjectMetaDataManager.ACCOUNT_FIELD, original.getAccountId())) + .withResourceType(TYPE).withResourceId(original.getId().toString()); + try { + if (!eventService.publish(changed)) { + return NativeNameRefreshResult.UPDATED_NOTIFICATION_FAILED; + } + } catch (RuntimeException e) { + return NativeNameRefreshResult.UPDATED_NOTIFICATION_FAILED; + } + return NativeNameRefreshResult.UPDATED; + } + }); + } catch (EventExecutionException | TimeoutException e) { + return NativeNameRefreshResult.INSPECT_UNAVAILABLE; + } catch (RuntimeException e) { + // Never log SQL bind values, names or inspect payloads; the caller records only this code and numeric IDs. + return NativeNameRefreshResult.FAILED; + } + } + + protected Map inspectNativeNameById(long agentId, String externalId) { + Event result = agentLocator.lookupAgent(agentId).callSync(newInspectEvent(null, externalId)); + return CollectionUtils.toMap(CollectionUtils.getNestedValue(result.getData(), INSTANCE_INSPECT_DATA_NAME)); + } + Cache scheduled = CacheBuilder.newBuilder() .expireAfterWrite(java.time.Duration.ofMinutes(15)) .build(); diff --git a/code/iaas/logic/src/test/java/io/cattle/platform/process/containerevent/NativeContainerNameRefreshTest.java b/code/iaas/logic/src/test/java/io/cattle/platform/process/containerevent/NativeContainerNameRefreshTest.java new file mode 100644 index 0000000000..66f275f64c --- /dev/null +++ b/code/iaas/logic/src/test/java/io/cattle/platform/process/containerevent/NativeContainerNameRefreshTest.java @@ -0,0 +1,235 @@ +package io.cattle.platform.process.containerevent; + +import static io.cattle.platform.process.containerevent.ContainerEventCreate.NativeNameRefreshResult.*; +import static org.junit.Assert.*; + +import io.cattle.platform.agent.AgentLocator; +import io.cattle.platform.agent.RemoteAgent; +import io.cattle.platform.core.dao.InstanceDao; +import io.cattle.platform.core.model.Agent; +import io.cattle.platform.core.model.Host; +import io.cattle.platform.core.model.tables.records.AgentRecord; +import io.cattle.platform.core.model.tables.records.HostRecord; +import io.cattle.platform.core.model.tables.records.InstanceRecord; +import io.cattle.platform.eventing.EventService; +import io.cattle.platform.eventing.model.Event; +import io.cattle.platform.eventing.model.EventVO; +import io.cattle.platform.lock.LockCallback; +import io.cattle.platform.lock.LockManager; +import io.cattle.platform.object.ObjectManager; +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.Map; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import com.netflix.config.ConfigurationManager; + +public class NativeContainerNameRefreshTest { + static final String DOCKER_ID = "a".repeat(64); + TestHandler handler; + AgentRecord agent; + HostRecord host; + InstanceRecord row; + boolean candidate; + boolean cas; + int updates; + int notifications; + String writtenName; + Event notification; + Object previousManageContainers; + + @Before + public void setup() { + previousManageContainers = ConfigurationManager.getConfigInstance().getProperty("manage.nonrancher.containers"); + ConfigurationManager.getConfigInstance().setProperty("manage.nonrancher.containers", true); + handler = new TestHandler(); + agent = new AgentRecord(); agent.setId(1L); + host = new HostRecord(); host.setId(1L); host.setAgentId(1L); host.setAccountId(5L); + row = new InstanceRecord(); row.setId(40L); row.setAccountId(5L); row.setExternalId(DOCKER_ID); + row.setName("original"); row.setState("stopped"); + candidate = true; cas = true; + handler.setObjects(proxy(ObjectManager.class, (method, args) -> { + if (method.equals("loadResource")) return args[0] == Agent.class ? agent : host; + throw new AssertionError("Unexpected object mutation: " + method); + })); + handler.lockManager = proxy(LockManager.class, (method, args) -> ((LockCallback) args[1]).doWithLock()); + handler.instanceDao = proxy(InstanceDao.class, (method, args) -> { + if (method.equals("getNativeContainerForNameRefresh")) return candidate ? row : null; + if (method.equals("updateNativeContainerName")) { + updates++; writtenName = (String) args[4]; + assertSame(row, args[0]); assertSame(agent, args[1]); assertSame(host, args[2]); + assertEquals("host-uuid", args[3]); + return cas; + } + throw new AssertionError("Unexpected lifecycle call: " + method); + }); + handler.eventService = proxy(EventService.class, (method, args) -> { + assertEquals("publish", method); notifications++; notification = (Event) args[0]; return true; + }); + handler.inspect = map("Id", DOCKER_ID, "Name", "/rollback-current"); + } + + @After + public void restoreConfiguration() { + if (previousManageContainers == null) { + ConfigurationManager.getConfigInstance().clearProperty("manage.nonrancher.containers"); + } else { + ConfigurationManager.getConfigInstance().setProperty("manage.nonrancher.containers", previousManageContainers); + } + } + + @Test + public void currentInspectNameNotUuidHintIsWrittenOnceAndNotificationIsScoped() { + assertEquals(UPDATED, refresh("untrusted-old-hint")); + assertEquals("rollback-current", writtenName); + assertEquals(1, updates); assertEquals(1, notifications); + assertEquals("40", notification.getResourceId()); + assertEquals(5L, ((Map) notification.getData()).get("accountId")); + assertEquals("original", row.getName()); + row.setName("rollback-current"); + assertEquals(UNCHANGED, refresh("rollback-current")); + assertEquals(1, updates); assertEquals(1, notifications); assertEquals(1, handler.inspections); + } + + @Test + public void runningContainerAndSecondDistinctFullIdUseExactIndependentAuthority() { + row.setState("running"); + assertEquals(UPDATED, handler.refreshNativeContainerName(1, 1, "host-uuid", DOCKER_ID, + "hint", "running", new HashMap<>())); + String second = "b".repeat(64); + row = new InstanceRecord(); row.setId(41L); row.setAccountId(5L); row.setExternalId(second); + row.setName("original"); row.setState("running"); + handler.inspect = map("Id", second, "Name", "/rollback-second"); + assertEquals(UPDATED, handler.refreshNativeContainerName(1, 1, "host-uuid", second, + "hint", "running", new HashMap<>())); + assertEquals(second, handler.inspectedId); + assertEquals("41", notification.getResourceId()); + assertEquals(2, updates); + } + + @Test + public void absentCandidateOrForeignHostCannotInspectOrCreate() { + candidate = false; + assertEquals(CANDIDATE_UNVERIFIED, refresh("hint")); + candidate = true; host.setAgentId(2L); + assertEquals(SOURCE_UNVERIFIED, refresh("hint")); + assertEquals(0, handler.inspections); assertEquals(0, updates); assertEquals(0, notifications); + } + + @Test + public void sourceAccountChangedAfterLockSelectionCannotRefreshForeignInstance() { + HostRecord moved = new HostRecord(); + moved.setId(1L); moved.setAgentId(1L); moved.setAccountId(6L); + int[] hostLoads = {0}; + handler.setObjects(proxy(ObjectManager.class, (method, args) -> { + assertEquals("loadResource", method); + return args[0] == Agent.class ? agent : (++hostLoads[0] == 1 ? host : moved); + })); + handler.instanceDao = proxy(InstanceDao.class, (method, args) -> { + throw new AssertionError("Reparented host must not reach candidate lookup"); + }); + assertEquals(SOURCE_UNVERIFIED, refresh("hint")); + assertEquals(0, handler.inspections); assertEquals(0, updates); assertEquals(0, notifications); + } + + @Test + public void actualAgentInspectRequestUsesOnlyFullDockerIdNeverNameFallback() { + ContainerEventCreate actual = new ContainerEventCreate(); + RemoteAgent remote = proxy(RemoteAgent.class, (method, args) -> { + assertEquals("callSync", method); + Event request = (Event) args[0]; + assertEquals("compute.instance.inspect", request.getName()); + assertEquals("instanceInspect", request.getResourceType()); + Map data = (Map) ((Map) request.getData()).get("instanceInspect"); + assertEquals("docker", data.get("kind")); + assertEquals(DOCKER_ID, data.get("id")); + assertFalse(data.containsKey("name")); + return EventVO.newEvent(request.getName()).withData(map("instanceInspect", handler.inspect)); + }); + actual.agentLocator = proxy(AgentLocator.class, (method, args) -> { + assertEquals("lookupAgent", method); assertEquals(1L, args[0]); return remote; + }); + assertEquals(handler.inspect, actual.inspectNativeNameById(1L, DOCKER_ID)); + } + + @Test + public void managedLabelShortIdOrDifferentStateCannotReachInspection() { + Map labels = new HashMap<>(); labels.put("io.rancher.container.uuid", "logical-uuid"); + assertEquals(CANDIDATE_UNVERIFIED, handler.refreshNativeContainerName(1, 1, "host-uuid", DOCKER_ID, + "hint", "stopped", labels)); + assertEquals(CANDIDATE_UNVERIFIED, handler.refreshNativeContainerName(1, 1, "host-uuid", "a".repeat(12), + "hint", "stopped", new HashMap<>())); + row.setState("running"); + assertEquals(CANDIDATE_UNVERIFIED, refresh("hint")); + assertEquals(0, handler.inspections); assertEquals(0, updates); + } + + @Test + public void inspectFailureWrongIdOrMalformedNameNeverWritesHint() { + handler.inspect = null; + assertEquals(INSPECT_UNAVAILABLE, refresh("hint")); + handler.inspect = map("Id", "b".repeat(64), "Name", "/wrong"); + assertEquals(INSPECT_ID_MISMATCH, refresh("hint")); + for (Object name : new Object[] {null, false, "", "/", "//invalid", "line\nbreak", "a".repeat(256)}) { + handler.inspect = map("Id", DOCKER_ID, "Name", name); + assertEquals(INSPECT_NAME_INVALID, refresh("hint")); + } + assertEquals(0, updates); assertEquals(0, notifications); + } + + @Test + public void casMissDoesNotNotifyOrRetryAndDelayedHintCannotRollNameBack() { + cas = false; + assertEquals(CAS_MISS, refresh("hint")); + assertEquals(1, updates); assertEquals(0, notifications); + row.setName("rollback-current"); + assertEquals(UNCHANGED, refresh("stale-ping-hint")); + assertEquals(1, updates); assertEquals(0, notifications); + } + + @Test + public void notificationFailurePreservesKnownCommittedUpdateWithoutRetry() { + handler.eventService = proxy(EventService.class, (method, args) -> { + assertEquals("publish", method); notifications++; throw new IllegalStateException("synthetic"); + }); + assertEquals(UPDATED_NOTIFICATION_FAILED, refresh("hint")); + assertEquals(1, updates); assertEquals(1, notifications); + row.setName("rollback-current"); + assertEquals(UNCHANGED, refresh("rollback-current")); + assertEquals(1, updates); assertEquals(1, notifications); + row.setName("original"); + handler.eventService = proxy(EventService.class, (method, args) -> { + assertEquals("publish", method); notifications++; return false; + }); + assertEquals(UPDATED_NOTIFICATION_FAILED, refresh("hint")); + assertEquals(2, updates); assertEquals(2, notifications); + } + + private ContainerEventCreate.NativeNameRefreshResult refresh(String hint) { + return handler.refreshNativeContainerName(1, 1, "host-uuid", DOCKER_ID, hint, "stopped", new HashMap<>()); + } + + interface Call { Object invoke(String method, Object[] args); } + static T proxy(Class type, Call call) { + return type.cast(Proxy.newProxyInstance(type.getClassLoader(), new Class[] {type}, + (object, method, args) -> call.invoke(method.getName(), args))); + } + + static Map map(Object... values) { + Map result = new HashMap<>(); + for (int i = 0; i < values.length; i += 2) result.put((String) values[i], values[i + 1]); + return result; + } + + private static class TestHandler extends ContainerEventCreate { + Map inspect; + int inspections; + String inspectedId; + void setObjects(ObjectManager objects) { this.objectManager = objects; } + @Override + protected Map inspectNativeNameById(long agentId, String externalId) { + assertEquals(1L, agentId); inspections++; inspectedId = externalId; return inspect; + } + } +} diff --git a/code/iaas/metadata/pom.xml b/code/iaas/metadata/pom.xml index 040876d02a..f96d127b88 100644 --- a/code/iaas/metadata/pom.xml +++ b/code/iaas/metadata/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/model/pom.xml b/code/iaas/model/pom.xml index 3bd25d0168..e714a78f97 100644 --- a/code/iaas/model/pom.xml +++ b/code/iaas/model/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/model/src/main/java/io/cattle/platform/core/dao/InstanceDao.java b/code/iaas/model/src/main/java/io/cattle/platform/core/dao/InstanceDao.java index 7a8fef1ea1..b8c6a0d685 100644 --- a/code/iaas/model/src/main/java/io/cattle/platform/core/dao/InstanceDao.java +++ b/code/iaas/model/src/main/java/io/cattle/platform/core/dao/InstanceDao.java @@ -3,6 +3,7 @@ import io.cattle.platform.core.addon.PublicEndpoint; import io.cattle.platform.core.dao.impl.InstanceDaoImpl.IpAddressToServiceIndex; import io.cattle.platform.core.model.Account; +import io.cattle.platform.core.model.Agent; import io.cattle.platform.core.model.Host; import io.cattle.platform.core.model.Instance; import io.cattle.platform.core.model.InstanceHostMap; @@ -28,6 +29,12 @@ public interface InstanceDao { Instance getInstanceByUuidOrExternalId(Long accountId, String uuid, String externalId); + /** Exact existing imported container only; never resolves by name or UUID. */ + Instance getNativeContainerForNameRefresh(Agent agent, Host host, String hostUuid, String externalId); + + /** Name-only update with the original full instance row and source bindings as a CAS. */ + boolean updateNativeContainerName(Instance original, Agent agent, Host host, String hostUuid, String name); + /** * @param instance * @return Services related to this instance diff --git a/code/iaas/model/src/main/java/io/cattle/platform/core/dao/impl/InstanceDaoImpl.java b/code/iaas/model/src/main/java/io/cattle/platform/core/dao/impl/InstanceDaoImpl.java index fdc076b80e..11c9badfa8 100644 --- a/code/iaas/model/src/main/java/io/cattle/platform/core/dao/impl/InstanceDaoImpl.java +++ b/code/iaas/model/src/main/java/io/cattle/platform/core/dao/impl/InstanceDaoImpl.java @@ -1,6 +1,7 @@ package io.cattle.platform.core.dao.impl; import static io.cattle.platform.core.model.tables.HostIpAddressMapTable.*; +import static io.cattle.platform.core.model.tables.AgentTable.*; import static io.cattle.platform.core.model.tables.HostTable.*; import static io.cattle.platform.core.model.tables.InstanceHostMapTable.*; import static io.cattle.platform.core.model.tables.InstanceLinkTable.*; @@ -22,6 +23,7 @@ import io.cattle.platform.core.constants.PortConstants; import io.cattle.platform.core.dao.InstanceDao; import io.cattle.platform.core.model.Account; +import io.cattle.platform.core.model.Agent; import io.cattle.platform.core.model.Host; import io.cattle.platform.core.model.Instance; import io.cattle.platform.core.model.InstanceHostMap; @@ -68,6 +70,9 @@ import org.apache.commons.lang3.StringUtils; import org.jooq.Condition; +import org.jooq.Field; +import org.jooq.UpdateConditionStep; +import org.jooq.impl.DSL; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -167,6 +172,135 @@ public Instance getInstanceByUuidOrExternalId(Long accountId, String uuid, Strin return instance; } + @Override + public Instance getNativeContainerForNameRefresh(Agent agent, Host host, String hostUuid, String externalId) { + if (!validNameRefreshSource(agent, host, hostUuid) || !fullDockerId(externalId)) { + return null; + } + List matches = create().selectFrom(INSTANCE) + .where(nativeNameRefreshCondition(agent, host, hostUuid, externalId)) + .limit(2).fetchInto(InstanceRecord.class); + return matches.size() == 1 && unmanagedImportedContainer(matches.get(0)) + && externalId.equals(matches.get(0).getExternalId()) ? matches.get(0) : null; + } + + @Override + public boolean updateNativeContainerName(Instance original, Agent agent, Host host, String hostUuid, String name) { + if (!(original instanceof InstanceRecord) || !unmanagedImportedContainer(original) + || !validNameRefreshSource(agent, host, hostUuid) || !fullDockerId(original.getExternalId()) + || name == null || !name.matches("[A-Za-z0-9][A-Za-z0-9_.-]{0,254}") + || name.equals(original.getName())) { + return false; + } + return nativeNameRefreshUpdate((InstanceRecord) original, agent, host, hostUuid, name).execute() == 1; + } + + protected UpdateConditionStep nativeNameRefreshUpdate(InstanceRecord original, + Agent agent, Host host, String hostUuid, String name) { + Condition originalRow = DSL.trueCondition(); + for (Field field : original.fields()) { + originalRow = originalRow.and(originalValue(field, original)); + } + return create().update(INSTANCE).set(INSTANCE.NAME, name) + .where(nativeNameRefreshCondition(agent, host, hostUuid, original.getExternalId()).and(originalRow)); + } + + protected Condition nativeNameRefreshCondition(Agent agent, Host host, String hostUuid, String externalId) { + return INSTANCE.ACCOUNT_ID.eq(host.getAccountId()) + .and(binaryEqual(INSTANCE.EXTERNAL_ID, externalId)) + .and(INSTANCE.KIND.eq(InstanceConstants.KIND_CONTAINER)) + .and(INSTANCE.NATIVE_CONTAINER.eq(true)).and(INSTANCE.SYSTEM.eq(false)) + .and(INSTANCE.SERVICE_ID.isNull()).and(INSTANCE.SERVICE_INDEX_ID.isNull()).and(INSTANCE.STACK_ID.isNull()) + .and(INSTANCE.REMOVED.isNull()).and(INSTANCE.REMOVE_TIME.isNull()) + .and(INSTANCE.STATE.in(InstanceConstants.STATE_RUNNING, InstanceConstants.STATE_STOPPED)) + .and(DSL.exists(create().selectOne().from(INSTANCE_HOST_MAP).join(HOST) + .on(HOST.ID.eq(INSTANCE_HOST_MAP.HOST_ID)).join(AGENT).on(AGENT.ID.eq(HOST.AGENT_ID)) + .where(INSTANCE_HOST_MAP.INSTANCE_ID.eq(INSTANCE.ID)) + .and(INSTANCE_HOST_MAP.HOST_ID.eq(host.getId())).and(INSTANCE_HOST_MAP.REMOVED.isNull()) + .and(binaryEqual(INSTANCE_HOST_MAP.STATE, CommonStatesConstants.ACTIVE)) + .and(HOST.ACCOUNT_ID.eq(host.getAccountId())).and(HOST.AGENT_ID.eq(agent.getId())) + .and(HOST.REMOVED.isNull()).and(binaryEqual(HOST.STATE, CommonStatesConstants.ACTIVE)) + .and(jsonType(HOST.DATA, "$.fields.reportedUuid").eq("STRING")) + .and(binaryEqual(jsonText(HOST.DATA, "$.fields.reportedUuid"), hostUuid)) + .and(AGENT.REMOVED.isNull()).and(binaryEqual(AGENT.STATE, CommonStatesConstants.ACTIVE)) + .and(jsonType(AGENT.DATA, "$.agentResourcesAccountId").eq("INTEGER")) + .and(jsonText(AGENT.DATA, "$.agentResourcesAccountId").eq(host.getAccountId().toString())))) + .and(create().selectCount().from(INSTANCE_HOST_MAP) + .where(INSTANCE_HOST_MAP.INSTANCE_ID.eq(INSTANCE.ID)).and(INSTANCE_HOST_MAP.REMOVED.isNull()) + .asField().eq(1)) + .and(DSL.notExists(create().selectOne().from(SERVICE_EXPOSE_MAP) + .where(SERVICE_EXPOSE_MAP.INSTANCE_ID.eq(INSTANCE.ID)).and(SERVICE_EXPOSE_MAP.REMOVED.isNull()))); + } + + private static Field jsonText(Field field, String path) { + return DSL.field("json_unquote(json_extract({0}, {1}))", String.class, field, DSL.val(path)); + } + + private static Field jsonType(Field field, String path) { + return DSL.field("json_type(json_extract({0}, {1}))", String.class, field, DSL.val(path)); + } + + private static Condition binaryEqual(Field field, String value) { + return DSL.condition("binary {0} = binary {1}", field, DSL.val(value)); + } + + private static Condition originalValue(Field field, InstanceRecord original) { + T value = original.get(field); + if (value == null) { + return field.isNull(); + } + if (value instanceof String || value instanceof Map) { + return DSL.condition("binary {0} = binary {1}", field, DSL.val(value, field.getDataType())); + } + return field.eq(value); + } + + private static boolean fullDockerId(String id) { + return id != null && id.matches("[0-9a-f]{64}"); + } + + private static boolean validNameRefreshSource(Agent agent, Host host, String hostUuid) { + if (agent == null || host == null || agent.getId() == null || host.getId() == null + || host.getAccountId() == null || host.getAccountId() <= 0 || !agent.getId().equals(host.getAgentId()) + || agent.getRemoved() != null || host.getRemoved() != null + || !CommonStatesConstants.ACTIVE.equals(agent.getState()) || !CommonStatesConstants.ACTIVE.equals(host.getState()) + || hostUuid == null || !hostUuid.equals(DataAccessor.fields(host).withKey("reportedUuid").get())) { + return false; + } + Object resourceAccount = DataAccessor.fromDataFieldOf(agent).withKey("agentResourcesAccountId").get(); + return (resourceAccount instanceof Long || resourceAccount instanceof Integer) + && ((Number) resourceAccount).longValue() == host.getAccountId(); + } + + private static boolean unmanagedImportedContainer(Instance instance) { + if (!Boolean.TRUE.equals(instance.getNativeContainer()) || !Boolean.FALSE.equals(instance.getSystem()) + || !InstanceConstants.KIND_CONTAINER.equals(instance.getKind()) || instance.getServiceId() != null + || instance.getServiceIndexId() != null || instance.getStackId() != null + || instance.getRemoved() != null || instance.getRemoveTime() != null + || !java.util.Arrays.asList(InstanceConstants.STATE_RUNNING, InstanceConstants.STATE_STOPPED).contains(instance.getState()) + || instance.getData() == null || !(instance.getData().get(DataUtils.FIELDS) instanceof Map)) { + return false; + } + Map fields = DataUtils.getFields(instance); + Object systemContainer = fields.get(InstanceConstants.FIELD_SYSTEM_CONTAINER); + if (systemContainer != null && !"".equals(systemContainer)) { + return false; + } + if (fields.get("serviceIndexId") != null || fields.get("serviceIndex") != null + || !(fields.get(InstanceConstants.FIELD_LABELS) instanceof Map)) { + return false; + } + Map labels = (Map) fields.get(InstanceConstants.FIELD_LABELS); + for (String marker : new String[] {"io.rancher.container.uuid", "io.rancher.container.display_name", + "io.rancher.container.name", "io.rancher.container.system", "io.rancher.stack_service.name", + "io.rancher.service.deployment.unit", "io.rancher.service.launch.config"}) { + if (labels.containsKey(marker)) { + return false; + } + } + return labels.entrySet().stream().allMatch(e -> e.getKey() instanceof String && e.getValue() instanceof String); + } + @Override public List findServicesFor(Instance instance) { return create().select(SERVICE.fields()) diff --git a/code/iaas/model/src/test/java/io/cattle/platform/core/dao/impl/NativeContainerNameRefreshDaoTest.java b/code/iaas/model/src/test/java/io/cattle/platform/core/dao/impl/NativeContainerNameRefreshDaoTest.java new file mode 100644 index 0000000000..3888122a50 --- /dev/null +++ b/code/iaas/model/src/test/java/io/cattle/platform/core/dao/impl/NativeContainerNameRefreshDaoTest.java @@ -0,0 +1,199 @@ +package io.cattle.platform.core.dao.impl; + +import static io.cattle.platform.core.model.tables.InstanceTable.INSTANCE; +import static org.junit.Assert.*; + +import io.cattle.platform.core.model.Instance; +import io.cattle.platform.core.model.tables.records.AgentRecord; +import io.cattle.platform.core.model.tables.records.HostRecord; +import io.cattle.platform.core.model.tables.records.InstanceRecord; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import org.jooq.DSLContext; +import org.jooq.Field; +import org.jooq.Result; +import org.jooq.SQLDialect; +import org.jooq.impl.DSL; +import org.jooq.tools.jdbc.MockConnection; +import org.jooq.tools.jdbc.MockResult; +import org.junit.Before; +import org.junit.Test; + +public class NativeContainerNameRefreshDaoTest { + static final String DOCKER_ID = "a".repeat(64); + TestDao dao; + AgentRecord agent; + HostRecord host; + InstanceRecord row; + List rows; + int calls; + int updateCount; + String lastSql; + + @Before + public void setup() { + agent = new AgentRecord(); + agent.setId(1L); agent.setState("active"); agent.setData(map("agentResourcesAccountId", 5L)); + host = new HostRecord(); + host.setId(1L); host.setAgentId(1L); host.setAccountId(5L); host.setState("active"); + host.setData(map("fields", map("reportedUuid", "host-uuid"))); + row = new InstanceRecord(); + row.setId(40L); row.setAccountId(5L); row.setKind("container"); row.setUuid("logical-uuid"); + row.setName("original"); row.setState("stopped"); row.setExternalId(DOCKER_ID); + row.setNativeContainer(true); row.setSystem(false); + row.setData(map("fields", map("labels", new HashMap()))); + rows = Arrays.asList(row); + updateCount = 1; + dao = new TestDao(); + dao.setConfiguration(DSL.using(new MockConnection(ctx -> { + calls++; + lastSql = ctx.sql(); + if (lastSql.toLowerCase(Locale.ROOT).startsWith("select")) { + DSLContext fixture = DSL.using(SQLDialect.MARIADB); + Result result = fixture.newResult(INSTANCE); + result.addAll(rows); + return new MockResult[] {new MockResult(result.size(), result)}; + } + return new MockResult[] {new MockResult(updateCount, null)}; + }), SQLDialect.MARIADB).configuration()); + } + + @Test + public void exactExistingImportedSnapshotAndNameOnlyCas() { + Instance snapshot = dao.getNativeContainerForNameRefresh(agent, host, "host-uuid", DOCKER_ID); + assertNotNull(snapshot); + assertTrue(dao.updateNativeContainerName(snapshot, agent, host, "host-uuid", "rollback-496")); + String sql = lastSql.toLowerCase(Locale.ROOT); + String set = sql.substring(sql.indexOf(" set "), sql.indexOf(" where ")); + assertTrue(set.contains("`name` = ?")); + assertFalse(set.contains("`state`")); + assertFalse(set.contains("`data`")); + assertEquals("original", row.getName()); + } + + @Test + public void compareAndSwapContainsEveryOriginalColumnIncludingBinaryTextAndJson() { + String sql = dao.sql(row, agent, host, "host-uuid", "new-name").toLowerCase(Locale.ROOT); + String where = sql.substring(sql.indexOf(" where ")); + for (Field field : row.fields()) { + assertTrue(field.getName(), where.contains("`instance`.`" + field.getName() + "`")); + } + assertTrue(where.contains("binary `cattle`.`instance`.`name`")); + assertTrue(where.contains("binary `cattle`.`instance`.`data`")); + assertTrue(where.contains("`instance`.`id` = ?")); + assertTrue(where.contains("not exists")); + assertTrue(where.contains("`service_expose_map`")); + assertTrue(where.contains("`instance_host_map`.`state`")); + assertTrue(where.contains("count(*)")); + assertTrue(where.contains("json_type")); + assertTrue(where.contains("`host`.`agent_id`")); + assertTrue(where.contains("`agent`.`data`")); + } + + @Test + public void zeroRowCasAndSameNameDoNotReportSuccess() { + updateCount = 0; + assertFalse(dao.updateNativeContainerName(row, agent, host, "host-uuid", "new-name")); + int previous = calls; + assertFalse(dao.updateNativeContainerName(row, agent, host, "host-uuid", "original")); + assertEquals(previous, calls); + } + + @Test + public void unknownDuplicateOrWrongExactDockerIdCannotBecomeSnapshot() { + rows = java.util.Collections.emptyList(); + assertNull(dao.getNativeContainerForNameRefresh(agent, host, "host-uuid", DOCKER_ID)); + rows = Arrays.asList(row, row); + assertNull(dao.getNativeContainerForNameRefresh(agent, host, "host-uuid", DOCKER_ID)); + rows = Arrays.asList(row); + row.setExternalId("b".repeat(64)); + assertNull(dao.getNativeContainerForNameRefresh(agent, host, "host-uuid", DOCKER_ID)); + int previous = calls; + assertNull(dao.getNativeContainerForNameRefresh(agent, host, "host-uuid", "a".repeat(12))); + assertEquals(previous, calls); + } + + @Test + public void foreignSourceOrWrongResourceAccountNeverReachesQuery() { + host.setAgentId(2L); + assertNull(dao.getNativeContainerForNameRefresh(agent, host, "host-uuid", DOCKER_ID)); + host.setAgentId(1L); + host.setAccountId(6L); + assertNull(dao.getNativeContainerForNameRefresh(agent, host, "host-uuid", DOCKER_ID)); + host.setAccountId(5L); + assertNull(dao.getNativeContainerForNameRefresh(agent, host, "foreign-host", DOCKER_ID)); + agent.setData(map("agentResourcesAccountId", "5")); + assertNull(dao.getNativeContainerForNameRefresh(agent, host, "host-uuid", DOCKER_ID)); + assertEquals(0, calls); + } + + @Test + public void managedServiceSystemEnvironmentAndLifecycleRowsAreRejected() { + row.setNativeContainer(false); rejectRow(); row.setNativeContainer(true); + row.setSystem(true); rejectRow(); row.setSystem(false); + row.setServiceId(9L); rejectRow(); row.setServiceId(null); + row.setServiceIndexId(7L); rejectRow(); row.setServiceIndexId(null); + row.setStackId(8L); rejectRow(); row.setStackId(null); + row.setRemoved(new Date()); rejectRow(); row.setRemoved(null); + row.setRemoveTime(new Date()); rejectRow(); row.setRemoveTime(null); + for (String state : Arrays.asList("removed", "purged", "purging", "starting", "updating-running")) { + row.setState(state); rejectRow(); + } + row.setState("stopped"); + for (String marker : Arrays.asList("io.rancher.container.uuid", "io.rancher.container.display_name", + "io.rancher.container.name", "io.rancher.container.system", "io.rancher.stack_service.name", + "io.rancher.service.deployment.unit", "io.rancher.service.launch.config")) { + row.setData(map("fields", map("labels", map(marker, "logical")))); + rejectRow(); + } + row.setData(map("fields", map("labels", new HashMap<>(), "systemContainer", "managed"))); rejectRow(); + row.setData(map("fields", map("labels", new HashMap<>(), "serviceIndexId", 7L))); rejectRow(); + row.setData(map("fields", map("labels", map("custom", false)))); rejectRow(); + } + + @Test + public void everyNonremovedMapMustBeUniqueAtSelectionAndAtCas() { + assertNotNull(dao.getNativeContainerForNameRefresh(agent, host, "host-uuid", DOCKER_ID)); + assertUniqueMappingPredicate(lastSql); + updateCount = 0; // Another host map appeared after the selected snapshot: CAS must fail closed. + assertFalse(dao.updateNativeContainerName(row, agent, host, "host-uuid", "new-name")); + assertUniqueMappingPredicate(lastSql); + assertEquals(2, calls); + } + + private static void assertUniqueMappingPredicate(String sql) { + String normalized = sql.toLowerCase(Locale.ROOT); + int begin = normalized.indexOf("select count(*)"); + assertTrue(begin >= 0); + int end = normalized.indexOf(") = ?", begin); + assertTrue(end > begin); + String count = normalized.substring(begin, end); + assertTrue(count.contains("`instance_host_map`.`instance_id` = `cattle`.`instance`.`id`")); + assertTrue(count.contains("`instance_host_map`.`removed` is null")); + assertFalse(count.contains("`instance_host_map`.`host_id`")); + assertFalse(count.contains("`instance_host_map`.`state`")); + } + + private void rejectRow() { + assertNull(dao.getNativeContainerForNameRefresh(agent, host, "host-uuid", DOCKER_ID)); + int previous = calls; + assertFalse(dao.updateNativeContainerName(row, agent, host, "host-uuid", "new-name")); + assertEquals(previous, calls); + } + + static Map map(Object... values) { + Map result = new HashMap<>(); + for (int i = 0; i < values.length; i += 2) result.put((String) values[i], values[i + 1]); + return result; + } + + private static class TestDao extends InstanceDaoImpl { + String sql(InstanceRecord row, AgentRecord agent, HostRecord host, String uuid, String name) { + return nativeNameRefreshUpdate(row, agent, host, uuid, name).getSQL(); + } + } +} diff --git a/code/iaas/resource-pool/pom.xml b/code/iaas/resource-pool/pom.xml index f0629558b3..26c2185761 100644 --- a/code/iaas/resource-pool/pom.xml +++ b/code/iaas/resource-pool/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/service-discovery/api/pom.xml b/code/iaas/service-discovery/api/pom.xml index b47dba16ee..1de905f1cf 100644 --- a/code/iaas/service-discovery/api/pom.xml +++ b/code/iaas/service-discovery/api/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../../parent/pom.xml diff --git a/code/iaas/service-discovery/server/pom.xml b/code/iaas/service-discovery/server/pom.xml index 104fe4d760..4184a3d303 100644 --- a/code/iaas/service-discovery/server/pom.xml +++ b/code/iaas/service-discovery/server/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../../parent/pom.xml diff --git a/code/iaas/ssh-common/pom.xml b/code/iaas/ssh-common/pom.xml index b3522603fe..db7d5d7509 100644 --- a/code/iaas/ssh-common/pom.xml +++ b/code/iaas/ssh-common/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/storage-service/pom.xml b/code/iaas/storage-service/pom.xml index dbd8d5f162..134945f3c1 100644 --- a/code/iaas/storage-service/pom.xml +++ b/code/iaas/storage-service/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/iaas/task-jooq/pom.xml b/code/iaas/task-jooq/pom.xml index 29328a3bf7..697a6dbd96 100644 --- a/code/iaas/task-jooq/pom.xml +++ b/code/iaas/task-jooq/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/implementation/activity-log/pom.xml b/code/implementation/activity-log/pom.xml index dac415e424..64edd8077f 100644 --- a/code/implementation/activity-log/pom.xml +++ b/code/implementation/activity-log/pom.xml @@ -5,7 +5,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/implementation/agent-instance-impl/pom.xml b/code/implementation/agent-instance-impl/pom.xml index 1a1b15a282..5d40d56991 100644 --- a/code/implementation/agent-instance-impl/pom.xml +++ b/code/implementation/agent-instance-impl/pom.xml @@ -3,7 +3,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../parent/pom.xml cattle-agent-instance-impl diff --git a/code/implementation/docker/api/pom.xml b/code/implementation/docker/api/pom.xml index 36ed44941b..5777135211 100644 --- a/code/implementation/docker/api/pom.xml +++ b/code/implementation/docker/api/pom.xml @@ -5,7 +5,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../../parent/pom.xml diff --git a/code/implementation/docker/common/pom.xml b/code/implementation/docker/common/pom.xml index 0c763f3b6e..b866a52293 100644 --- a/code/implementation/docker/common/pom.xml +++ b/code/implementation/docker/common/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../../parent/pom.xml diff --git a/code/implementation/docker/compute/pom.xml b/code/implementation/docker/compute/pom.xml index c166000654..7ceaaede87 100644 --- a/code/implementation/docker/compute/pom.xml +++ b/code/implementation/docker/compute/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../../parent/pom.xml diff --git a/code/implementation/docker/machine/pom.xml b/code/implementation/docker/machine/pom.xml index 26eb011662..635cc3ee58 100644 --- a/code/implementation/docker/machine/pom.xml +++ b/code/implementation/docker/machine/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../../parent/pom.xml diff --git a/code/implementation/docker/storage/pom.xml b/code/implementation/docker/storage/pom.xml index a0f4805b8d..f61bb5ebb7 100644 --- a/code/implementation/docker/storage/pom.xml +++ b/code/implementation/docker/storage/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../../parent/pom.xml @@ -21,12 +21,12 @@ io.cattle cattle-docker-common - 0.183.329 + 0.183.330 io.cattle cattle-iaas-allocator - 0.183.329 + 0.183.330 diff --git a/code/implementation/extension-api/pom.xml b/code/implementation/extension-api/pom.xml index 7126313e64..df477de310 100644 --- a/code/implementation/extension-api/pom.xml +++ b/code/implementation/extension-api/pom.xml @@ -3,7 +3,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../parent/pom.xml cattle-extension-api diff --git a/code/implementation/hazelcast/common/pom.xml b/code/implementation/hazelcast/common/pom.xml index 0558dd5945..0247b65815 100644 --- a/code/implementation/hazelcast/common/pom.xml +++ b/code/implementation/hazelcast/common/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../../parent/pom.xml diff --git a/code/implementation/hazelcast/eventing/pom.xml b/code/implementation/hazelcast/eventing/pom.xml index 6ac3b82944..f55e0e7542 100644 --- a/code/implementation/hazelcast/eventing/pom.xml +++ b/code/implementation/hazelcast/eventing/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../../parent/pom.xml diff --git a/code/implementation/hazelcast/lock/pom.xml b/code/implementation/hazelcast/lock/pom.xml index 1cedc4e20a..51b1a5cc5a 100644 --- a/code/implementation/hazelcast/lock/pom.xml +++ b/code/implementation/hazelcast/lock/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../../parent/pom.xml diff --git a/code/implementation/host-api/pom.xml b/code/implementation/host-api/pom.xml index 6842b2025e..bf4d222995 100644 --- a/code/implementation/host-api/pom.xml +++ b/code/implementation/host-api/pom.xml @@ -5,7 +5,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/implementation/host-stats/pom.xml b/code/implementation/host-stats/pom.xml index 09620e73c4..d0fbacaa52 100644 --- a/code/implementation/host-stats/pom.xml +++ b/code/implementation/host-stats/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/implementation/register/pom.xml b/code/implementation/register/pom.xml index b190295d45..0c7ba54660 100644 --- a/code/implementation/register/pom.xml +++ b/code/implementation/register/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/implementation/sample-setup/pom.xml b/code/implementation/sample-setup/pom.xml index 14767db3ff..02494cfab6 100644 --- a/code/implementation/sample-setup/pom.xml +++ b/code/implementation/sample-setup/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/implementation/settings-api/pom.xml b/code/implementation/settings-api/pom.xml index b2ed51b96d..77997b5cc1 100644 --- a/code/implementation/settings-api/pom.xml +++ b/code/implementation/settings-api/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/implementation/simulator/agent-connection/pom.xml b/code/implementation/simulator/agent-connection/pom.xml index 76c8fddaa2..e64648eb6a 100644 --- a/code/implementation/simulator/agent-connection/pom.xml +++ b/code/implementation/simulator/agent-connection/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../../parent/pom.xml diff --git a/code/implementation/simulator/storage/pom.xml b/code/implementation/simulator/storage/pom.xml index 5472f84df3..a28831347f 100644 --- a/code/implementation/simulator/storage/pom.xml +++ b/code/implementation/simulator/storage/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../../parent/pom.xml diff --git a/code/implementation/system-stack/pom.xml b/code/implementation/system-stack/pom.xml index ac6fef404b..c638d2454c 100644 --- a/code/implementation/system-stack/pom.xml +++ b/code/implementation/system-stack/pom.xml @@ -5,7 +5,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/implementation/vm/pom.xml b/code/implementation/vm/pom.xml index 67e0682622..cb35635c1b 100644 --- a/code/implementation/vm/pom.xml +++ b/code/implementation/vm/pom.xml @@ -3,7 +3,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../parent/pom.xml cattle-vm diff --git a/code/meta-parent/pom.xml b/code/meta-parent/pom.xml index ad194a7bb1..4c2e8de6e7 100644 --- a/code/meta-parent/pom.xml +++ b/code/meta-parent/pom.xml @@ -9,7 +9,7 @@ 4.0.0 io.cattle cattle-meta-parent - 0.183.329 + 0.183.330 pom PastureStack Orchestration Engine Compatibility orchestration engine for the PastureStack server. diff --git a/code/packaging/app-config/pom.xml b/code/packaging/app-config/pom.xml index 6e9c1e9c76..00ca12af48 100644 --- a/code/packaging/app-config/pom.xml +++ b/code/packaging/app-config/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml jar diff --git a/code/packaging/app/pom.xml b/code/packaging/app/pom.xml index 5bd8581098..8db931ac5c 100644 --- a/code/packaging/app/pom.xml +++ b/code/packaging/app/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../parent/pom.xml war diff --git a/code/packaging/bundle/pom.xml b/code/packaging/bundle/pom.xml index 7de63641a0..a228c45d30 100644 --- a/code/packaging/bundle/pom.xml +++ b/code/packaging/bundle/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/packaging/dev/pom.xml b/code/packaging/dev/pom.xml index 26d2bf66f7..cabafece98 100644 --- a/code/packaging/dev/pom.xml +++ b/code/packaging/dev/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/packaging/meta/pom.xml b/code/packaging/meta/pom.xml index 834467b112..5ad2704014 100644 --- a/code/packaging/meta/pom.xml +++ b/code/packaging/meta/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../../parent/pom.xml diff --git a/code/parent/pom.xml b/code/parent/pom.xml index e0f7426f2e..07b39a9c63 100644 --- a/code/parent/pom.xml +++ b/code/parent/pom.xml @@ -5,7 +5,7 @@ io.cattle cattle-meta-parent ../meta-parent/pom.xml - 0.183.329 + 0.183.330 pom diff --git a/docs/releases/orchestration-engine-0.183.330.md b/docs/releases/orchestration-engine-0.183.330.md new file mode 100644 index 0000000000..2f9f987ba5 --- /dev/null +++ b/docs/releases/orchestration-engine-0.183.330.md @@ -0,0 +1,50 @@ +# Orchestration Engine 0.183.330 + +## Imported container names + +Docker containers imported as independent native containers previously kept +their original platform display name after a Docker rename. This could make +different retained rollback containers appear to be duplicate entries. + +The existing ping inventory now uses its UUID field only as a change hint. +The Engine obtains the authoritative current name by inspecting the exact +64-character Docker ID using the existing Agent inspect contract. It does +not resolve a container by a mutable name or UUID and does not create a new +instance, remove rollback containers, or change existing state-sync/import +processing. No Agent, Web Console, schema, database migration or plugin +contract change is required. + +An update is limited to an existing, stable running/stopped, unmanaged native +container. Service, service-index, stack, system/managed-label, removed and +transitional records are excluded. The source Host/Agent/resource account +and reported host UUID must match, and the instance must have exactly one +nonremoved host mapping. Selection and the final update both check these +relations. The update sets only `instance.name` and compares every original +instance column, so a concurrent lifecycle or relationship change fails +closed without retry. Successful changes use the existing resource event; +a notification failure is distinguished from a failed database update. + +## Focused validation + +All 19 distinct focused offline cases have obtained PASS across incremental +runs: seven DAO cases, nine handler cases, and three monitor cases, including +the existing state-sync regression. Compilation and mock failures from the +initial attempts were retained; only affected cases were rerun. Coverage +includes exact-ID inspection, separate instances with the same hint, current +inspect authority over delayed hints, idempotence, source binding, managed +record exclusions, name-only/full-row CAS, and notification failures. + +The DAO uses MockConnection and the handler uses proxies in these tests. +This is not proof of real MariaDB concurrency, a published artifact, an +accepted Server deployment, or complete UI/resource/permission coverage. +Normal release builds, packaged-artifact checks, and real-host/browser +acceptance remain pending before this version is declared published. + +## Compatibility and rollback + +Authentication, authorization, cookie/session behavior, OIDC/MFA, nftables +and plugin ownership boundaries are unchanged. Existing numeric tags are +immutable. A consuming Server must retain its environment variables, data +volumes, restart policy, AppArmor and public origin. Rollback uses the +previous immutable Server image with the same preserved data; no SQL or +runtime patch is needed for this name-only change. diff --git a/pom.xml b/pom.xml index 3c8d58a8ea..1acb68f5dc 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ io.cattle cattle-parent - 0.183.329 + 0.183.330 code/parent/pom.xml cattle diff --git a/resources/pom.xml b/resources/pom.xml index f6036f290b..1ca8799a86 100644 --- a/resources/pom.xml +++ b/resources/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.329 + 0.183.330 ../code/parent/pom.xml diff --git a/scripts/build b/scripts/build index 9f6de53a79..8225d4029c 100755 --- a/scripts/build +++ b/scripts/build @@ -16,7 +16,7 @@ fi SOURCE_REVISION=${SOURCE_REVISION:-$(git rev-parse HEAD)} SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-$(git show -s --format=%ct HEAD)} -ENGINE_VERSION=${ENGINE_VERSION:-0.183.329} +ENGINE_VERSION=${ENGINE_VERSION:-0.183.330} case "$SOURCE_REVISION" in ''|*[!0-9a-f]*) diff --git a/scripts/check-pasturestack-source b/scripts/check-pasturestack-source index 9721dee535..21be9297ca 100755 --- a/scripts/check-pasturestack-source +++ b/scripts/check-pasturestack-source @@ -51,7 +51,7 @@ fi project_version=$(sed -n 's/^[[:space:]]*\([^<]*\)<\/version>[[:space:]]*$/\1/p' code/meta-parent/pom.xml | head -n 1) [[ "$project_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || fail non_numeric_project_version -require_line code/meta-parent/pom.xml ' 0.183.329' +require_line code/meta-parent/pom.xml ' 0.183.330' require_line code/meta-parent/pom.xml ' 2.3.35' require_line "$iaas_api_defaults" 'auth.service.external.id.types=github_user,github_org,github_team,shibboleth_user,shibboleth_group,ldap_user,ldap_group,oidc_user,oidc_group' require_line code/meta-parent/pom.xml ' https://github.com/PastureStack/orchestration-engine' @@ -416,4 +416,4 @@ require_line README.md 'modernize the Rancher 1.6 ecosystem. It is not affiliate require_line README.md 'by Rancher Labs or SUSE.' require_line ORIGIN.md '- Preserved upstream boundary: `82d154a53f4089fecfb9f320caad826bb4f6055f`' -printf 'PASTURESTACK_SOURCE_GATE_OK version=0.183.329 runtime_sources=github_release images=digest_pinned ubuntu=26.04 ubuntu_snapshot=20260826T000000Z jdk=25.0.4 maven=3.9.16 patched_hazelcast=5.7.5 docker_cli=29.7.2 docker_host_29_8_0=exact credential_secret_capacity=mediumtext port_preflight=authoritative volume_preflight=runtime_resolution_aligned volume_preflight_project_schema=authorized volume_preflight_type_set=registered v1_hardware_schema=container-and-launchConfig network_driver_rollback=launch-config-restored stack_driver_rollback=child-launch-config-restored auth_token_session_binding=authorized-create-only auth_token_transport=bare-or-bearer-normalized auth_token_frozen_v1_schema=base-superadmin-token oidc_external_types=validated-before-mutation-owned-stable-account oidc_account_activation=sync-before-mfa oidc_v1_project_member_schema=core-options-merged-scoped oidc_identity_link_owner=explicit-and-verified legacy_token_link_repair=exact-match-only oidc_required_local_recovery=active-admin-only default_project=shared-idempotent-role-preserving,atomic-identity-set oidc_restricted_project_membership=stable-account-aware-required-allowlist-only mfa_policy_confirmation=actor-purpose-digest-single-use auth_config_proxy_identity=caller-platform-credential project_member_collection_acl=requested-project-checked-before-load project_template_v1_public=readonly-frozen-field-merged network_purge=retry-removing dev_artifact=forbidden\n' +printf 'PASTURESTACK_SOURCE_GATE_OK version=0.183.330 runtime_sources=github_release images=digest_pinned ubuntu=26.04 ubuntu_snapshot=20260826T000000Z jdk=25.0.4 maven=3.9.16 patched_hazelcast=5.7.5 docker_cli=29.7.2 docker_host_29_8_0=exact credential_secret_capacity=mediumtext port_preflight=authoritative volume_preflight=runtime_resolution_aligned volume_preflight_project_schema=authorized volume_preflight_type_set=registered v1_hardware_schema=container-and-launchConfig network_driver_rollback=launch-config-restored stack_driver_rollback=child-launch-config-restored auth_token_session_binding=authorized-create-only auth_token_transport=bare-or-bearer-normalized auth_token_frozen_v1_schema=base-superadmin-token oidc_external_types=validated-before-mutation-owned-stable-account oidc_account_activation=sync-before-mfa oidc_v1_project_member_schema=core-options-merged-scoped oidc_identity_link_owner=explicit-and-verified legacy_token_link_repair=exact-match-only oidc_required_local_recovery=active-admin-only default_project=shared-idempotent-role-preserving,atomic-identity-set oidc_restricted_project_membership=stable-account-aware-required-allowlist-only mfa_policy_confirmation=actor-purpose-digest-single-use auth_config_proxy_identity=caller-platform-credential project_member_collection_acl=requested-project-checked-before-load project_template_v1_public=readonly-frozen-field-merged network_purge=retry-removing dev_artifact=forbidden\n' diff --git a/scripts/check-release-artifact b/scripts/check-release-artifact index f7e5fd72b9..8b83bf2f70 100755 --- a/scripts/check-release-artifact +++ b/scripts/check-release-artifact @@ -4,7 +4,7 @@ set -euo pipefail cd "$(dirname "$0")/.." artifact=${1:-dist/artifacts/cattle.jar} -expected_version=${EXPECTED_ENGINE_VERSION:-0.183.329} +expected_version=${EXPECTED_ENGINE_VERSION:-0.183.330} test -f "$artifact" artifact=$(realpath "$artifact")