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 extends Service> 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")