From ab33c429854186d122f1baeb2a85693646e59c33 Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Sun, 16 Aug 2026 02:13:14 +0900 Subject: [PATCH 1/7] [zns]: support automatic segment projection Implement the approved final Cloud network contexts, projection continuation, cascade safety, SDN pull contract, schema, generated consumers and tests. Include reviewed cascade fixes, dedicated failure codes and complete generated documentation. Resolves: ZCF-5485 Change-Id: Ie209fc6f84ca04cbb4eebc4ca55cae0ee05269b6 --- .../zstack/compute/vm/VmAllocateNicFlow.java | 81 +- .../org/zstack/compute/zone/ZoneBase.java | 9 +- conf/db/upgrade/V5.5.38__schema.sql | 102 ++ conf/serviceConfig/sdnController.xml | 4 + conf/springConfigXml/sdnController.xml | 1 + .../zstack/core/cascade/CascadeAction.java | 13 + .../core/cascade/CascadeFacadeImpl.java | 86 ++ .../cascade/CascadePreExtensionPoint.java | 9 + .../org/zstack/core/rest/RESTFacadeImpl.java | 62 +- .../rest/webhook/WebhookCallbackClient.java | 55 +- .../CompleteZnsSegmentMutationMsg.java | 69 ++ .../NetworkConfigLocalContinuation.java | 7 + .../header/network/NetworkConfigMutation.java | 330 ++++++ .../NetworkConfigMutationExtensionPoint.java | 11 + .../NetworkDeleteGuardExtensionPoint.java | 11 + .../network/PrepareZnsSegmentMutationMsg.java | 51 + .../PrepareZnsSegmentMutationReply.java | 15 + .../l2/AttachL2NetworkToClusterMsg.java | 18 + .../header/network/l2/CreateL2NetworkMsg.java | 80 ++ .../network/l2/CreateL2NetworkReply.java | 10 + .../header/network/l2/ExternalNetworkRef.java | 15 + .../l2/L2DeleteConfirmExtensionPoint.java | 28 + .../l2/L2NetworkCreateExtensionPoint.java | 8 + .../l2/L2NetworkDeleteExtensionPoint.java | 16 + .../network/l2/L2NetworkDeletionMsg.java | 18 + .../header/network/l2/L2NetworkFactory.java | 5 + .../l2/L2NetworkInventoryDoc_zh_cn.groovy | 26 + .../l2/L2NetworkUpdateExtensionPoint.java | 7 + .../network/l2/NetworkCreateContext.java | 75 ++ .../network/l2/NetworkDeletionContext.java | 104 ++ .../network/l2/NetworkOperationOrigin.java | 9 + .../header/network/l3/AddIpRangeMsg.java | 22 + .../header/network/l3/AddIpRangeReply.java | 10 + .../l3/AfterAddIpRangeExtensionPoint.java | 6 + .../l3/AfterDeleteIpRangeExtensionPoint.java | 4 + .../header/network/l3/AllocateIpMsg.java | 20 + .../network/l3/ConvertL3NetworkTypeMsg.java | 31 + .../header/network/l3/CreateL3NetworkMsg.java | 46 + .../network/l3/CreateL3NetworkReply.java | 10 + .../network/l3/DeleteProjectedIpRangeMsg.java | 20 + .../header/network/l3/IpAllocateMessage.java | 8 + .../l3/IpRangeDeletionExtensionPoint.java | 10 + .../header/network/l3/IpRangeDeletionMsg.java | 10 + .../header/network/l3/IpRangeFactory.java | 6 + .../l3/L3NetworkDeleteExtensionPoint.java | 13 + .../network/l3/L3NetworkDeletionMsg.java | 10 + .../header/network/l3/L3NetworkFactory.java | 6 + .../l3/SdnControllerDisableDHCPMsg.java | 11 +- .../header/network/l3/SdnControllerL3.java | 35 + .../network/l3/UpdateProjectedDnsMsg.java | 22 + .../network/l3/UpdateProjectedIpRangeMsg.java | 32 + .../org/zstack/header/rest/RESTFacade.java | 3 + .../zstack/header/rest/SyncHttpResponse.java | 13 + .../rest/SyncHttpStatusBodyCallHandler.java | 5 + .../vm/VmNicQosConfigExtensionPoint.java | 7 + .../network/l2/L2NetworkCascadeExtension.java | 194 +++- .../l2/L2NetworkExtensionPointEmitter.java | 39 + .../network/l2/L2NetworkManagerImpl.java | 105 +- .../network/l2/L2NoVlanL2NetworkFactory.java | 8 +- .../zstack/network/l2/L2NoVlanNetwork.java | 193 +++- .../network/l2/L2VlanNetworkFactory.java | 8 +- .../network/l2/NetworkDeletionContexts.java | 19 + .../l3/AbstractIpAllocatorStrategy.java | 6 +- .../network/l3/AddressPoolIpRangeFactory.java | 24 +- .../AscDelayRecycleIpAllocatorStrategy.java | 3 +- .../l3/AttachNetworkServiceToL3Msg.java | 10 + .../l3/FirstAvailableIpAllocatorStrategy.java | 3 +- .../FirstAvailableIpv6AllocatorStrategy.java | 3 +- .../network/l3/IpRangeCascadeExtension.java | 5 + .../org/zstack/network/l3/L3BasicNetwork.java | 989 ++++++++++++++++-- .../network/l3/L3NetworkCascadeExtension.java | 6 +- .../l3/L3NetworkExtensionPointEmitter.java | 20 +- .../zstack/network/l3/L3NetworkManager.java | 2 + .../network/l3/L3NetworkManagerImpl.java | 183 +++- .../network/l3/NormalIpRangeFactory.java | 32 +- .../network/l3/RandomIpAllocatorStrategy.java | 3 +- .../l3/RandomIpv6AllocatorStrategy.java | 3 +- .../zstack/sdnController/SdnController.java | 7 + .../SdnControllerApiInterceptor.java | 41 + .../sdnController/SdnControllerBase.java | 34 +- .../zstack/sdnController/SdnControllerL2.java | 34 + .../SdnControllerManagerImpl.java | 144 ++- .../header/APIPullSdnControllerEvent.java | 10 + .../APIPullSdnControllerEventDoc_zh_cn.groovy | 23 + .../header/APIPullSdnControllerMsg.java | 32 + .../APIPullSdnControllerMsgDoc_zh_cn.groovy | 77 ++ .../header/PullSdnControllerMsg.java | 27 + .../header/PullSdnControllerReply.java | 6 + sdk/src/main/java/SourceClassMap.java | 4 + .../zstack/sdk/PullSdnControllerAction.java | 107 ++ .../zstack/sdk/PullSdnControllerResult.java | 7 + .../QueryZnsSegmentCloudProjectionAction.java | 75 ++ .../QueryZnsSegmentCloudProjectionResult.java | 22 + .../ZnsSegmentCloudProjectionInventory.java | 153 +++ .../zns/ZnsSegmentSyncOperationInventory.java | 23 + .../rest/Zcf5485RestStatusContractCase.groovy | 99 ++ .../Zcf5485SdnPullContractCase.groovy | 29 + .../core/rest/RestFacadeCase.groovy | 45 + .../l2network/AttachL2NetworkCase.groovy | 1 + .../l2network/L2NetworkCascadeCase.groovy | 647 ++++++++++++ .../sdnController/SdnControllerCase.groovy | 17 + .../Zcf5485CanonicalContractCase.groovy | 47 + .../network/Zcf5485NetworkContextCase.groovy | 142 +++ .../test/network/TestSdnControllerL3.java | 52 + .../src/test/resources/zns/zcf5485/SHA256SUMS | 1 + .../zns/zcf5485/segment-cloud-contract.json | 330 ++++++ .../java/org/zstack/testlib/ApiHelper.groovy | 112 ++ .../CloudOperationsErrorCode.java | 181 ++++ 108 files changed, 5845 insertions(+), 237 deletions(-) create mode 100644 core/src/main/java/org/zstack/core/cascade/CascadePreExtensionPoint.java create mode 100644 header/src/main/java/org/zstack/header/network/CompleteZnsSegmentMutationMsg.java create mode 100644 header/src/main/java/org/zstack/header/network/NetworkConfigLocalContinuation.java create mode 100644 header/src/main/java/org/zstack/header/network/NetworkConfigMutation.java create mode 100644 header/src/main/java/org/zstack/header/network/NetworkConfigMutationExtensionPoint.java create mode 100644 header/src/main/java/org/zstack/header/network/NetworkDeleteGuardExtensionPoint.java create mode 100644 header/src/main/java/org/zstack/header/network/PrepareZnsSegmentMutationMsg.java create mode 100644 header/src/main/java/org/zstack/header/network/PrepareZnsSegmentMutationReply.java create mode 100644 header/src/main/java/org/zstack/header/network/l2/CreateL2NetworkMsg.java create mode 100644 header/src/main/java/org/zstack/header/network/l2/CreateL2NetworkReply.java create mode 100644 header/src/main/java/org/zstack/header/network/l2/ExternalNetworkRef.java create mode 100644 header/src/main/java/org/zstack/header/network/l2/L2DeleteConfirmExtensionPoint.java create mode 100644 header/src/main/java/org/zstack/header/network/l2/NetworkCreateContext.java create mode 100644 header/src/main/java/org/zstack/header/network/l2/NetworkDeletionContext.java create mode 100644 header/src/main/java/org/zstack/header/network/l2/NetworkOperationOrigin.java create mode 100644 header/src/main/java/org/zstack/header/network/l3/AddIpRangeMsg.java create mode 100644 header/src/main/java/org/zstack/header/network/l3/AddIpRangeReply.java create mode 100644 header/src/main/java/org/zstack/header/network/l3/ConvertL3NetworkTypeMsg.java create mode 100644 header/src/main/java/org/zstack/header/network/l3/CreateL3NetworkMsg.java create mode 100644 header/src/main/java/org/zstack/header/network/l3/CreateL3NetworkReply.java create mode 100644 header/src/main/java/org/zstack/header/network/l3/DeleteProjectedIpRangeMsg.java create mode 100644 header/src/main/java/org/zstack/header/network/l3/UpdateProjectedDnsMsg.java create mode 100644 header/src/main/java/org/zstack/header/network/l3/UpdateProjectedIpRangeMsg.java create mode 100644 header/src/main/java/org/zstack/header/rest/SyncHttpResponse.java create mode 100644 header/src/main/java/org/zstack/header/rest/SyncHttpStatusBodyCallHandler.java create mode 100644 header/src/main/java/org/zstack/header/vm/VmNicQosConfigExtensionPoint.java create mode 100644 network/src/main/java/org/zstack/network/l2/NetworkDeletionContexts.java create mode 100644 plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerEvent.java create mode 100644 plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerEventDoc_zh_cn.groovy create mode 100644 plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerMsg.java create mode 100644 plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerMsgDoc_zh_cn.groovy create mode 100644 plugin/sdnController/src/main/java/org/zstack/sdnController/header/PullSdnControllerMsg.java create mode 100644 plugin/sdnController/src/main/java/org/zstack/sdnController/header/PullSdnControllerReply.java create mode 100644 sdk/src/main/java/org/zstack/sdk/PullSdnControllerAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/PullSdnControllerResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/network/zns/QueryZnsSegmentCloudProjectionAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/network/zns/QueryZnsSegmentCloudProjectionResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/network/zns/ZnsSegmentCloudProjectionInventory.java create mode 100644 sdk/src/main/java/org/zstack/sdk/network/zns/ZnsSegmentSyncOperationInventory.java create mode 100644 test/src/test/groovy/org/zstack/core/rest/Zcf5485RestStatusContractCase.groovy create mode 100644 test/src/test/groovy/org/zstack/sdnController/Zcf5485SdnPullContractCase.groovy create mode 100644 test/src/test/groovy/org/zstack/test/integration/network/l2network/L2NetworkCascadeCase.groovy create mode 100644 test/src/test/groovy/org/zstack/test/unittest/network/Zcf5485CanonicalContractCase.groovy create mode 100644 test/src/test/groovy/org/zstack/test/unittest/network/Zcf5485NetworkContextCase.groovy create mode 100644 test/src/test/java/org/zstack/test/network/TestSdnControllerL3.java create mode 100644 test/src/test/resources/zns/zcf5485/SHA256SUMS create mode 100644 test/src/test/resources/zns/zcf5485/segment-cloud-contract.json diff --git a/compute/src/main/java/org/zstack/compute/vm/VmAllocateNicFlow.java b/compute/src/main/java/org/zstack/compute/vm/VmAllocateNicFlow.java index 0e307a30b5c..3aa81091713 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmAllocateNicFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmAllocateNicFlow.java @@ -10,6 +10,7 @@ import org.zstack.core.componentloader.PluginRegistry; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.SQLBatch; +import org.zstack.core.db.SQLBatchWithReturn; import org.zstack.core.errorcode.ErrorFacade; import org.zstack.header.core.Completion; import org.zstack.header.core.WhileDoneCompletion; @@ -18,9 +19,11 @@ import org.zstack.header.core.workflow.FlowTrigger; import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.errorcode.ErrorCodeList; +import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.image.ImagePlatform; import org.zstack.header.message.APIMessage; import org.zstack.header.network.l3.*; +import org.zstack.header.network.NetworkDeleteGuardExtensionPoint; import org.zstack.header.vm.*; import org.zstack.network.l3.L3NetworkManager; import org.zstack.resourceconfig.ResourceConfig; @@ -94,7 +97,6 @@ public void run(final FlowTrigger trigger, final Map data) { data.put(VmInstanceConstant.Params.VmAllocateNicFlow_nics.toString(), nics); List errs = new ArrayList<>(); List vmSystemTags = spec.getMessage() instanceof APIMessage ? ((APIMessage) spec.getMessage()).getSystemTags() : null; - new While<>(VmNicSpec.getFirstL3NetworkInventoryOfSpec(spec.getL3Networks())).each((nicSpec, wcomp) -> { L3NetworkInventory nw = nicSpec.getL3Invs().get(0); int deviceId = deviceIdBitmap.nextClearBit(0); @@ -123,20 +125,47 @@ public void run(final FlowTrigger trigger, final Map data) { // Persist VmNicVO first so that ResourceVO entry exists before extensions // (e.g. SDN controllers) attempt to create SystemTags referencing the NIC UUID. - VmNicVO nicVO = vnicFactory.createVmNic(nic, spec); + VmNicVO nicVO; + try { + nicVO = new SQLBatchWithReturn() { + @Override + protected VmNicVO scripts() { + ErrorCode guardError = checkNetworkDeleteGuards(nw.getUuid(), true); + if (guardError != null) { + throw new OperationFailureException(guardError); + } + return vnicFactory.createVmNic(nic, spec); + } + }.execute(); + } catch (OperationFailureException e) { + errs.add(e.getErrorCode()); + wcomp.allDone(); + return; + } callBeforeAllocateVmNicExtensions(nic, spec, new Completion(wcomp) { @Override public void success() { - new SQLBatch() { - @Override - protected void scripts() { - persistStaticIpIfNeeded(nic, nicVO, nw, nicNetworkInfoMap, spec); - nics.add(nic); - VmNicVO updated = dbf.updateAndRefresh(nicVO); - addVmNicConfig(updated, spec, nicSpec); - } - }.execute(); + try { + new SQLBatch() { + @Override + protected void scripts() { + ErrorCode guardError = checkNetworkDeleteGuards(nw.getUuid(), true); + if (guardError != null) { + throw new OperationFailureException(guardError); + } + persistStaticIpIfNeeded(nic, nicVO, nw, nicNetworkInfoMap, spec); + VmNicVO updated = dbf.updateAndRefresh(nicVO); + addVmNicConfig(updated, spec, nicSpec); + } + }.execute(); + } catch (OperationFailureException e) { + dbf.removeByPrimaryKey(nicVO.getUuid(), VmNicVO.class); + errs.add(e.getErrorCode()); + wcomp.allDone(); + return; + } + nics.add(nic); if (customMac != null) { mo.deleteCustomMacSystemTag(spec.getVmInventory().getUuid(), nw.getUuid(), customMac); } @@ -263,6 +292,13 @@ private void addVmNicConfig(VmNicVO vmNicVO, VmInstanceSpec vmSpec, VmNicSpec ni VmNicParam vmNicParm = vmNicParms.get(0); + if (vmNicParm.getInboundBandwidth() != null || vmNicParm.getOutboundBandwidth() != null) { + ErrorCode qosError = validateVmNicQos(vmNicVO.getL3NetworkUuid()); + if (qosError != null) { + throw new OperationFailureException(qosError); + } + } + // add vmnic bandwidth systemtag if (vmNicParm.getInboundBandwidth() != null || vmNicParm.getOutboundBandwidth() != null) { VmNicQosConfigBackend backend = vmMgr.getVmNicQosConfigBackend(vmSpec.getVmInventory().getType()); @@ -277,6 +313,29 @@ private void addVmNicConfig(VmNicVO vmNicVO, VmInstanceSpec vmSpec, VmNicSpec ni } } + private ErrorCode checkNetworkDeleteGuards(String l3NetworkUuid, boolean lock) { + for (NetworkDeleteGuardExtensionPoint extension : + pluginRgty.getExtensionList(NetworkDeleteGuardExtensionPoint.class)) { + ErrorCode errorCode = lock ? extension.checkL3NetworkWithLock(l3NetworkUuid) + : extension.checkL3Network(l3NetworkUuid); + if (errorCode != null) { + return errorCode; + } + } + return null; + } + + private ErrorCode validateVmNicQos(String l3NetworkUuid) { + for (VmNicQosConfigExtensionPoint extension : + pluginRgty.getExtensionList(VmNicQosConfigExtensionPoint.class)) { + ErrorCode errorCode = extension.validateVmNicQos(l3NetworkUuid); + if (errorCode != null) { + return errorCode; + } + } + return null; + } + private void callBeforeAllocateVmNicExtensions(VmNicInventory nic, VmInstanceSpec spec, Completion completion) { List exts = pluginRgty.getExtensionList(BeforeAllocateVmNicExtensionPoint.class); if (exts.isEmpty()) { diff --git a/compute/src/main/java/org/zstack/compute/zone/ZoneBase.java b/compute/src/main/java/org/zstack/compute/zone/ZoneBase.java index ba1f889a46f..fa483aced4e 100755 --- a/compute/src/main/java/org/zstack/compute/zone/ZoneBase.java +++ b/compute/src/main/java/org/zstack/compute/zone/ZoneBase.java @@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Configurable; import org.zstack.core.cascade.CascadeConstant; +import org.zstack.core.cascade.CascadeAction; import org.zstack.core.cascade.CascadeFacade; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.db.DatabaseFacade; @@ -178,13 +179,15 @@ protected void handle(APIDeleteZoneMsg msg) { final String issuer = ZoneVO.class.getSimpleName(); ZoneInventory zinv = ZoneInventory.valueOf(self); final List ctx = Arrays.asList(zinv); + final CascadeAction cascadeAction = new CascadeAction().setRootIssuer(issuer) + .setRootIssuerContext(ctx).setParentIssuer(issuer).setParentIssuerContext(ctx); FlowChain chain = FlowChainBuilder.newSimpleFlowChain(); chain.setName(String.format("delete-zone-%s", msg.getUuid())); if (msg.getDeletionMode() == APIDeleteMessage.DeletionMode.Permissive) { chain.then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_CHECK_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_CHECK_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -199,7 +202,7 @@ public void fail(ErrorCode errorCode) { }).then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_DELETE_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_DELETE_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -216,7 +219,7 @@ public void fail(ErrorCode errorCode) { chain.then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_FORCE_DELETE_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); diff --git a/conf/db/upgrade/V5.5.38__schema.sql b/conf/db/upgrade/V5.5.38__schema.sql index 1b8937fe5e9..2b71c870dbd 100644 --- a/conf/db/upgrade/V5.5.38__schema.sql +++ b/conf/db/upgrade/V5.5.38__schema.sql @@ -20,3 +20,105 @@ CREATE TABLE IF NOT EXISTS `zstack`.`SnmpEngineVO` ( `lastOpDate` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`uuid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- ZNS segment binding state and controller inventory + +CREATE TABLE IF NOT EXISTS `zstack`.`ZnsControllerStateVO` ( + `sdnControllerUuid` varchar(32) NOT NULL, + `activeScanToken` varchar(255) DEFAULT NULL, + `activeScanStartedAt` timestamp NULL DEFAULT NULL, + `previousCompleteScanToken` varchar(255) DEFAULT NULL, + `previousCompleteScanDigest` varchar(64) DEFAULT NULL, + `previousCompleteScanTotal` int DEFAULT NULL, + `migrationState` varchar(32) NOT NULL DEFAULT 'NotStarted', + `compatibilityMode` varchar(32) NOT NULL DEFAULT 'LEGACY', + `compatibilityEpoch` bigint NOT NULL DEFAULT 0, + `remoteCompatibilityEpoch` bigint DEFAULT NULL, + `capabilityDigest` varchar(64) DEFAULT NULL, + `migrationDigest` varchar(64) DEFAULT NULL, + `pendingContractEpoch` bigint DEFAULT NULL, + `pendingContractRequestDigest` varchar(64) DEFAULT NULL, + `lastSuccessfulScanDate` timestamp NULL DEFAULT NULL, + `lastErrorCode` varchar(128) DEFAULT NULL, + `lastErrorDetails` text DEFAULT NULL, + `lastOpDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00' ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`sdnControllerUuid`), + CONSTRAINT `fkZnsControllerStateVOSdnControllerVO` + FOREIGN KEY (`sdnControllerUuid`) REFERENCES `zstack`.`SdnControllerVO` (`uuid`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `zstack`.`ZnsSegmentInventoryVO` ( + `uuid` varchar(32) NOT NULL, + `sdnControllerUuid` varchar(32) NOT NULL, + `znsSegmentUuid` varchar(36) NOT NULL, + `name` varchar(255) NOT NULL, + `transportZoneUuid` varchar(36) NOT NULL, + `availability` varchar(32) NOT NULL, + `reasonCode` varchar(128) DEFAULT NULL, + `lastSeenToken` varchar(36) NOT NULL, + `lastSeenDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', + `createDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', + `lastOpDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00' ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`uuid`), + UNIQUE KEY `uk_zns_seg_inv_controller_segment` (`sdnControllerUuid`, `znsSegmentUuid`), + KEY `idx_zns_seg_inv_candidate` (`sdnControllerUuid`, `transportZoneUuid`, `availability`), + CONSTRAINT `fkZnsSegmentInventoryVOSdnControllerVO` + FOREIGN KEY (`sdnControllerUuid`) REFERENCES `zstack`.`SdnControllerVO` (`uuid`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `zstack`.`ZnsSegmentRefVO` ( + `uuid` varchar(32) NOT NULL, + `sdnControllerUuid` varchar(32) NOT NULL, + `znsSegmentUuid` varchar(36) DEFAULT NULL, + `zoneUuid` varchar(32) NOT NULL, + `l2NetworkUuid` varchar(32) NOT NULL, + `accountUuid` varchar(32) NOT NULL, + `l3NetworkUuid` varchar(32) DEFAULT NULL, + `znsTenantRouterUuid` varchar(36) DEFAULT NULL, + `state` varchar(32) NOT NULL, + `operationUuid` varchar(36) DEFAULT NULL, + `operationStep` varchar(32) DEFAULT NULL, + `operationDigest` varchar(64) DEFAULT NULL, + `currentConfigVersion` bigint DEFAULT NULL, + `appliedConfigVersion` bigint DEFAULT NULL, + `lastErrorCode` varchar(128) DEFAULT NULL, + `lastErrorDetails` text DEFAULT NULL, + `createDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', + `lastOpDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00' ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`uuid`), + UNIQUE KEY `uk_zns_seg_ref_controller_segment` + (`sdnControllerUuid`, `znsSegmentUuid`), + UNIQUE KEY `uk_zns_seg_ref_l2` (`l2NetworkUuid`), + KEY `idx_zns_seg_ref_account` (`accountUuid`), + KEY `idx_zns_seg_ref_state` (`sdnControllerUuid`, `state`), + CONSTRAINT `fkZnsSegmentRefVOSdnControllerVO` + FOREIGN KEY (`sdnControllerUuid`) REFERENCES `zstack`.`SdnControllerVO` (`uuid`) ON DELETE CASCADE, + CONSTRAINT `chk_zns_seg_ref_segment_state` + CHECK (`znsSegmentUuid` IS NOT NULL OR `state` = 'MigrationFailed') +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TRIGGER IF EXISTS `zstack`.`trg_zns_seg_ref_validate_insert`; +DELIMITER $$ +CREATE TRIGGER `zstack`.`trg_zns_seg_ref_validate_insert` +BEFORE INSERT ON `zstack`.`ZnsSegmentRefVO` +FOR EACH ROW +BEGIN + IF NEW.`znsSegmentUuid` IS NULL AND NEW.`state` <> 'MigrationFailed' THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'ZnsSegmentRefVO requires znsSegmentUuid unless migration failed'; + END IF; +END$$ +DELIMITER ; + +DROP TRIGGER IF EXISTS `zstack`.`trg_zns_seg_ref_validate_update`; +DELIMITER $$ +CREATE TRIGGER `zstack`.`trg_zns_seg_ref_validate_update` +BEFORE UPDATE ON `zstack`.`ZnsSegmentRefVO` +FOR EACH ROW +BEGIN + IF NEW.`znsSegmentUuid` IS NULL AND NEW.`state` <> 'MigrationFailed' THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'ZnsSegmentRefVO requires znsSegmentUuid unless migration failed'; + END IF; +END$$ +DELIMITER ; diff --git a/conf/serviceConfig/sdnController.xml b/conf/serviceConfig/sdnController.xml index 2ae849eb987..0f8b7cd4712 100644 --- a/conf/serviceConfig/sdnController.xml +++ b/conf/serviceConfig/sdnController.xml @@ -45,6 +45,10 @@ query + + org.zstack.sdnController.header.APIPullSdnControllerMsg + + org.zstack.network.hostNetworkInterface.APIQueryPhysicalSwitchMsg query diff --git a/conf/springConfigXml/sdnController.xml b/conf/springConfigXml/sdnController.xml index e76be084ac6..0372fc43e06 100644 --- a/conf/springConfigXml/sdnController.xml +++ b/conf/springConfigXml/sdnController.xml @@ -26,6 +26,7 @@ + diff --git a/core/src/main/java/org/zstack/core/cascade/CascadeAction.java b/core/src/main/java/org/zstack/core/cascade/CascadeAction.java index 75722e03661..e6486d66b4c 100755 --- a/core/src/main/java/org/zstack/core/cascade/CascadeAction.java +++ b/core/src/main/java/org/zstack/core/cascade/CascadeAction.java @@ -2,6 +2,9 @@ import org.zstack.header.exception.CloudRuntimeException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + /** */ public class CascadeAction implements Cloneable { @@ -11,6 +14,7 @@ public class CascadeAction implements Cloneable { private Object rootIssuerContext; private String actionCode; private boolean fullTraverse; + private Map contexts = new ConcurrentHashMap<>(); public boolean isFullTraverse() { return fullTraverse; @@ -65,6 +69,15 @@ public CascadeAction copy() { } } + public CascadeAction putContext(String key, Object value) { + contexts.put(key, value); + return this; + } + + public T getContext(String key) { + return (T) contexts.get(key); + } + public String getActionCode() { return actionCode; } diff --git a/core/src/main/java/org/zstack/core/cascade/CascadeFacadeImpl.java b/core/src/main/java/org/zstack/core/cascade/CascadeFacadeImpl.java index f7dad373ce1..6cff990ad62 100755 --- a/core/src/main/java/org/zstack/core/cascade/CascadeFacadeImpl.java +++ b/core/src/main/java/org/zstack/core/cascade/CascadeFacadeImpl.java @@ -8,6 +8,7 @@ import org.zstack.header.core.workflow.*; import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.exception.CloudRuntimeException; +import org.zstack.header.errorcode.OperationFailureException; import org.zstack.utils.Bucket; import org.zstack.utils.DebugUtils; import org.zstack.utils.Utils; @@ -16,6 +17,9 @@ import java.util.*; import java.util.concurrent.Callable; +import static org.zstack.core.Platform.inerr; +import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_CORE_10002; + /** */ public class CascadeFacadeImpl implements CascadeFacade, Component { @@ -212,6 +216,27 @@ public void asyncCascade(CascadeAction action, final Completion completion) { TreeNode root = cascadeTree.get(action.getRootIssuer()); DebugUtils.Assert(root != null, String.format("found no CascadeExtension for %s", action.getRootIssuer())); + List prepared = new ArrayList<>(); + ErrorCode prepareError; + try { + prepareError = prepareAsyncCascade(root, true, action.isFullTraverse(), action, + prepared, new HashSet<>()); + } catch (OperationFailureException e) { + rollbackPreparedCascade(prepared); + completion.fail(e.getErrorCode()); + return; + } catch (RuntimeException e) { + logger.warn(String.format("failed to prepare cascade[%s]", action.getActionCode()), e); + rollbackPreparedCascade(prepared); + completion.fail(inerr(ORG_ZSTACK_CORE_10002, + "failed to prepare cascade[%s]: %s", action.getActionCode(), e.getMessage())); + return; + } + if (prepareError != null) { + rollbackPreparedCascade(prepared); + completion.fail(prepareError); + return; + } List paths = new ArrayList<>(); collectPathsForAsyncCascade(root, true, action.isFullTraverse(), action, paths, new HashSet<>()); FlowChain chain = FlowChainBuilder.newSimpleFlowChain(); @@ -248,11 +273,72 @@ public void handle(Map data) { }).error(new FlowErrorHandler(completion) { @Override public void handle(ErrorCode errCode, Map data) { + rollbackPreparedCascade(prepared); completion.fail(errCode); } }).setName(String.format("Cascade: %s", action.getActionCode())).start(); } + private ErrorCode prepareAsyncCascade(TreeNode treeNode, boolean init, boolean fullTraverse, + CascadeAction action, List prepared, + Set mergePaths) { + Node node = treeNode.node; + String cascadePath = getCascadePath(action.getParentIssuer(), node.getName()); + if (mergePaths.add(cascadePath)) { + CascadeExtensionPoint extension = extensionForAction(node, action); + if (extension instanceof CascadePreExtensionPoint) { + ErrorCode error = ((CascadePreExtensionPoint) extension).beforeCascade(action); + if (error != null) { + return error; + } + prepared.add(Bucket.newBucket(extension, action)); + } + } + + CascadeAction childAction = init ? action : node.getExtension().createActionForChildResource(action); + if (fullTraverse && childAction == null) { + childAction = new CascadeAction(); + childAction.setActionCode(action.getActionCode()); + childAction.setRootIssuer(action.getRootIssuer()); + childAction.setRootIssuerContext(action.getRootIssuerContext()); + childAction.setParentIssuer(node.getName()); + childAction.setParentIssuerContext(null); + } + + if (childAction != null) { + checkForNullElement(node, childAction); + for (TreeNode child : treeNode.leafs) { + ErrorCode error = prepareAsyncCascade(child, false, fullTraverse, childAction, + prepared, mergePaths); + if (error != null) { + return error; + } + } + } + return null; + } + + private CascadeExtensionPoint extensionForAction(Node node, CascadeAction action) { + CascadeExtensionPoint extension = node.getExtension(); + if (extension instanceof CascadeWrapper) { + return ((CascadeWrapper) extension).findExtensionPointByParent(action.getParentIssuer()); + } + return extension; + } + + private void rollbackPreparedCascade(List prepared) { + for (int i = prepared.size() - 1; i >= 0; i--) { + CascadePreExtensionPoint extension = prepared.get(i).get(0); + CascadeAction action = prepared.get(i).get(1); + try { + extension.afterCascadeFailure(action); + } catch (RuntimeException e) { + logger.warn(String.format("failed to roll back cascade preparation[%s]", + extension.getClass().getName()), e); + } + } + } + private void runNode(Node node, CascadeAction caction, Completion completion) { List branches = asyncBranchCascadeExtensionPoints.get(node.getName()); boolean skipNode = false; diff --git a/core/src/main/java/org/zstack/core/cascade/CascadePreExtensionPoint.java b/core/src/main/java/org/zstack/core/cascade/CascadePreExtensionPoint.java new file mode 100644 index 00000000000..c1c547911c2 --- /dev/null +++ b/core/src/main/java/org/zstack/core/cascade/CascadePreExtensionPoint.java @@ -0,0 +1,9 @@ +package org.zstack.core.cascade; + +import org.zstack.header.errorcode.ErrorCode; + +public interface CascadePreExtensionPoint { + ErrorCode beforeCascade(CascadeAction action); + + void afterCascadeFailure(CascadeAction action); +} diff --git a/core/src/main/java/org/zstack/core/rest/RESTFacadeImpl.java b/core/src/main/java/org/zstack/core/rest/RESTFacadeImpl.java index 26dbb7ec675..d227d888caa 100755 --- a/core/src/main/java/org/zstack/core/rest/RESTFacadeImpl.java +++ b/core/src/main/java/org/zstack/core/rest/RESTFacadeImpl.java @@ -107,6 +107,19 @@ private interface AsyncHttpWrapper { private interface HttpCallHandlerWrapper { String handle(HttpEntity entity); + default void handle(HttpEntity entity, HttpServletResponse response) { + String body = handle(entity); + response.setStatus(HttpStatus.SC_OK); + if (body != null) { + response.setContentType(RESTConstant.APP_JSON_UTF8); + try { + response.getWriter().write(body); + } catch (IOException e) { + throw new CloudRuntimeException(e); + } + } + } + HttpCallHandler getHandler(); } @@ -346,12 +359,7 @@ void sendCommand(HttpServletRequest req, HttpServletResponse rsp) { return; } - String ret = handler.handle(entity); - if (ret == null) { - rsp.setStatus(HttpStatus.SC_OK); - } else { - rsp.setStatus(HttpStatus.SC_OK, ret); - } + handler.handle(entity, rsp); } catch (IOException e) { logger.warn(e.getMessage(), e); } catch (Throwable t) { @@ -1051,6 +1059,48 @@ public HttpCallHandler getHandler() { httpCallhandlers.put(path, wrapper); } + @Override + public void registerSyncHttpStatusBodyCallHandler(String path, final Class objectType, + final SyncHttpStatusBodyCallHandler handler) { + if (httpCallhandlers.containsKey(path)) { + throw new CloudRuntimeException("duplicate SyncHttpStatusBodyCallHandler for path[" + path + "]"); + } + httpCallhandlers.put(path, new HttpCallHandlerWrapper() { + @Override + public String handle(HttpEntity entity) { + SyncHttpResponse response = handler.handleSyncHttpCall(JSONObjectUtil.toObject(entity.getBody(), objectType)); + return response == null ? null : response.getBody(); + } + + @Override + public void handle(HttpEntity entity, HttpServletResponse servletResponse) { + writeSyncHttpResponse(handler.handleSyncHttpCall( + JSONObjectUtil.toObject(entity.getBody(), objectType)), servletResponse); + } + + @Override + public HttpCallHandler getHandler() { return handler; } + }); + } + + static void writeSyncHttpResponse(SyncHttpResponse response, + HttpServletResponse servletResponse) { + if (response == null) { + servletResponse.setStatus(HttpStatus.SC_OK); + return; + } + servletResponse.setStatus(response.getStatus()); + if (response.getBody() == null) { + return; + } + servletResponse.setHeader("Content-Type", "application/json"); + try { + servletResponse.getWriter().write(response.getBody()); + } catch (IOException e) { + throw new CloudRuntimeException(e); + } + } + @Override public String getBaseUrl() { return baseUrl; diff --git a/core/src/main/java/org/zstack/core/rest/webhook/WebhookCallbackClient.java b/core/src/main/java/org/zstack/core/rest/webhook/WebhookCallbackClient.java index 0559312b6d2..42b237664f5 100644 --- a/core/src/main/java/org/zstack/core/rest/webhook/WebhookCallbackClient.java +++ b/core/src/main/java/org/zstack/core/rest/webhook/WebhookCallbackClient.java @@ -13,6 +13,7 @@ import java.util.concurrent.TimeUnit; import static org.zstack.core.Platform.operr; +import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_CORE_10003; /** * Generic async callback client for external systems that use a webhook pattern: @@ -59,12 +60,17 @@ public class WebhookCallbackClient { private static class PendingEntry { final ReturnValueCompletion completion; - final ThreadFacadeImpl.TimeoutTaskReceipt timeoutReceipt; + volatile ThreadFacadeImpl.TimeoutTaskReceipt timeoutReceipt; - PendingEntry(ReturnValueCompletion completion, - ThreadFacadeImpl.TimeoutTaskReceipt timeoutReceipt) { + PendingEntry(ReturnValueCompletion completion) { this.completion = completion; - this.timeoutReceipt = timeoutReceipt; + } + + void cancelTimeout() { + ThreadFacadeImpl.TimeoutTaskReceipt receipt = timeoutReceipt; + if (receipt != null) { + receipt.cancel(); + } } } @@ -99,17 +105,44 @@ public void start() { * @return the generated taskId */ public String submit(ReturnValueCompletion completion, TimeUnit unit, long timeout) { - String taskId = Platform.getUuid(); + return submit(Platform.getUuid(), completion, unit, timeout); + } - ThreadFacadeImpl.TimeoutTaskReceipt timeoutReceipt = thdf.submitTimeoutTask(() -> { - fail(taskId, operr("[Webhook Timeout] callback timed out for taskId[%s], path[%s]", + public String submit(String taskId, ReturnValueCompletion completion, + TimeUnit unit, long timeout) { + if (taskId == null || taskId.trim().isEmpty()) { + throw new IllegalArgumentException("taskId cannot be empty"); + } + + PendingEntry entry = new PendingEntry<>(completion); + PendingEntry previous = pendingCalls.putIfAbsent(taskId, entry); + if (previous != null) { + throw new IllegalStateException(String.format( + "webhook taskId[%s] is already pending for path[%s]", taskId, protocol.getCallbackPath())); - }, unit, timeout); + } - pendingCalls.put(taskId, new PendingEntry<>(completion, timeoutReceipt)); + try { + entry.timeoutReceipt = thdf.submitTimeoutTask(() -> fail(taskId, entry, + operr(ORG_ZSTACK_CORE_10003, "[Webhook Timeout] callback timed out for taskId[%s], path[%s]", + taskId, protocol.getCallbackPath())), unit, timeout); + } catch (RuntimeException e) { + pendingCalls.remove(taskId, entry); + throw e; + } + if (pendingCalls.get(taskId) != entry) { + entry.cancelTimeout(); + } return taskId; } + private void fail(String taskId, PendingEntry expected, ErrorCode error) { + if (pendingCalls.remove(taskId, expected)) { + expected.cancelTimeout(); + expected.completion.fail(error); + } + } + /** * Actively fail a pending call (e.g. when the HTTP send fails). * @@ -119,7 +152,7 @@ public String submit(ReturnValueCompletion completion, TimeUnit unit, long ti public void fail(String taskId, ErrorCode error) { PendingEntry entry = pendingCalls.remove(taskId); if (entry != null) { - entry.timeoutReceipt.cancel(); + entry.cancelTimeout(); entry.completion.fail(error); } } @@ -173,7 +206,7 @@ private String onCallback(T cmd) { return null; } - entry.timeoutReceipt.cancel(); + entry.cancelTimeout(); if (protocol.isSuccess(cmd)) { entry.completion.success(cmd); diff --git a/header/src/main/java/org/zstack/header/network/CompleteZnsSegmentMutationMsg.java b/header/src/main/java/org/zstack/header/network/CompleteZnsSegmentMutationMsg.java new file mode 100644 index 00000000000..0c160f481aa --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/CompleteZnsSegmentMutationMsg.java @@ -0,0 +1,69 @@ +package org.zstack.header.network; + +import org.zstack.header.message.NeedReplyMessage; + +public class CompleteZnsSegmentMutationMsg extends NeedReplyMessage { + private String controllerUuid; + private String relationUuid; + private String operationUuid; + private long acceptedConfigVersion; + private boolean success; + private String errorCode; + private String errorDetails; + + public String getControllerUuid() { + return controllerUuid; + } + + public void setControllerUuid(String controllerUuid) { + this.controllerUuid = controllerUuid; + } + + public String getRelationUuid() { + return relationUuid; + } + + public void setRelationUuid(String relationUuid) { + this.relationUuid = relationUuid; + } + + public String getOperationUuid() { + return operationUuid; + } + + public void setOperationUuid(String operationUuid) { + this.operationUuid = operationUuid; + } + + public long getAcceptedConfigVersion() { + return acceptedConfigVersion; + } + + public void setAcceptedConfigVersion(long acceptedConfigVersion) { + this.acceptedConfigVersion = acceptedConfigVersion; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getErrorCode() { + return errorCode; + } + + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } + + public String getErrorDetails() { + return errorDetails; + } + + public void setErrorDetails(String errorDetails) { + this.errorDetails = errorDetails; + } +} diff --git a/header/src/main/java/org/zstack/header/network/NetworkConfigLocalContinuation.java b/header/src/main/java/org/zstack/header/network/NetworkConfigLocalContinuation.java new file mode 100644 index 00000000000..26b00a0ac58 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/NetworkConfigLocalContinuation.java @@ -0,0 +1,7 @@ +package org.zstack.header.network; + +import org.zstack.header.core.Completion; + +public interface NetworkConfigLocalContinuation { + void run(Completion completion); +} diff --git a/header/src/main/java/org/zstack/header/network/NetworkConfigMutation.java b/header/src/main/java/org/zstack/header/network/NetworkConfigMutation.java new file mode 100644 index 00000000000..53035115c17 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/NetworkConfigMutation.java @@ -0,0 +1,330 @@ +package org.zstack.header.network; + +import org.zstack.header.network.l2.NetworkOperationOrigin; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class NetworkConfigMutation { + public enum Kind { + ENCAP, + L2_METADATA, + L3_CREATE, + IPAM, + DHCP_DNS + } + + public static final class MetadataTarget { + private final String name; + private final String description; + + private MetadataTarget(String name, String description) { + this.name = name; + this.description = description; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + } + + public static final class EncapsulationTarget { + private final String l2Type; + private final int virtualNetworkId; + + private EncapsulationTarget(String l2Type, int virtualNetworkId) { + this.l2Type = l2Type; + this.virtualNetworkId = virtualNetworkId; + } + + public String getL2Type() { + return l2Type; + } + + public int getVirtualNetworkId() { + return virtualNetworkId; + } + } + + public static final class L3CreateTarget { + private final String l3Uuid; + private final String l3Type; + private final List systemTags; + + private L3CreateTarget(String l3Uuid, String l3Type, List systemTags) { + this.l3Uuid = l3Uuid; + this.l3Type = l3Type; + this.systemTags = Collections.unmodifiableList(new ArrayList<>( + systemTags == null ? Collections.emptyList() : systemTags)); + } + + public String getL3Uuid() { + return l3Uuid; + } + + public String getL3Type() { + return l3Type; + } + + public List getSystemTags() { + return systemTags; + } + } + + public static final class IpRangeTarget { + private final String rangeUuid; + private final String startIp; + private final String endIp; + + public IpRangeTarget(String rangeUuid, String startIp, String endIp) { + this.rangeUuid = rangeUuid; + this.startIp = startIp; + this.endIp = endIp; + } + + public String getRangeUuid() { + return rangeUuid; + } + + public String getStartIp() { + return startIp; + } + + public String getEndIp() { + return endIp; + } + } + + public static final class IpamTarget { + private final String l3Uuid; + private final int ipVersion; + private final String gatewayAddress; + private final List ranges; + private final boolean delete; + + private IpamTarget(String l3Uuid, int ipVersion, String gatewayAddress, + List ranges, boolean delete) { + this.l3Uuid = l3Uuid; + this.ipVersion = ipVersion; + this.gatewayAddress = gatewayAddress; + this.ranges = Collections.unmodifiableList(new ArrayList<>(ranges)); + this.delete = delete; + } + + public String getL3Uuid() { + return l3Uuid; + } + + public int getIpVersion() { + return ipVersion; + } + + public String getGatewayAddress() { + return gatewayAddress; + } + + public List getRanges() { + return ranges; + } + + public boolean isDelete() { + return delete; + } + } + + public static final class DhcpTarget { + private final String l3Uuid; + private final boolean enabled; + private final List systemTags; + private final Integer ipVersion; + private final List dnsServers; + + private DhcpTarget(String l3Uuid, boolean enabled, List systemTags, + Integer ipVersion, List dnsServers) { + this.l3Uuid = l3Uuid; + this.enabled = enabled; + this.systemTags = Collections.unmodifiableList(new ArrayList<>( + systemTags == null ? Collections.emptyList() : systemTags)); + this.ipVersion = ipVersion; + this.dnsServers = dnsServers == null ? null + : Collections.unmodifiableList(new ArrayList<>(dnsServers)); + } + + public String getL3Uuid() { + return l3Uuid; + } + + public boolean isEnabled() { + return enabled; + } + + public List getSystemTags() { + return systemTags; + } + + public Integer getIpVersion() { + return ipVersion; + } + + public List getDnsServers() { + return dnsServers; + } + } + + private final Kind kind; + private final String l2Uuid; + private final NetworkOperationOrigin origin; + private final String operationUuid; + private final String accountUuid; + private final EncapsulationTarget encapsulation; + private final MetadataTarget metadata; + private final L3CreateTarget l3Create; + private final IpamTarget ipam; + private final DhcpTarget dhcp; + + private NetworkConfigMutation(Kind kind, String l2Uuid, NetworkOperationOrigin origin, + String operationUuid, String accountUuid, + EncapsulationTarget encapsulation, + MetadataTarget metadata, + L3CreateTarget l3Create, + IpamTarget ipam, + DhcpTarget dhcp) { + this.kind = kind; + this.l2Uuid = l2Uuid; + this.origin = origin; + this.operationUuid = operationUuid; + this.accountUuid = accountUuid; + this.encapsulation = encapsulation; + this.metadata = metadata; + this.l3Create = l3Create; + this.ipam = ipam; + this.dhcp = dhcp; + } + + public static NetworkConfigMutation encapsulation(String l2Uuid, + NetworkOperationOrigin origin, + String operationUuid, + String accountUuid, + String l2Type, + int virtualNetworkId) { + return new NetworkConfigMutation(Kind.ENCAP, l2Uuid, origin, operationUuid, + accountUuid, new EncapsulationTarget(l2Type, virtualNetworkId), null, + null, null, null); + } + + public static NetworkConfigMutation metadata(String l2Uuid, + NetworkOperationOrigin origin, + String operationUuid, + String accountUuid, + String name, + String description) { + return new NetworkConfigMutation(Kind.L2_METADATA, l2Uuid, origin, operationUuid, + accountUuid, null, new MetadataTarget(name, description), + null, null, null); + } + + public static NetworkConfigMutation l3Create(String l2Uuid, + NetworkOperationOrigin origin, + String operationUuid, + String accountUuid, + String l3Uuid, + String l3Type, + List systemTags) { + return new NetworkConfigMutation(Kind.L3_CREATE, l2Uuid, origin, operationUuid, + accountUuid, null, null, + new L3CreateTarget(l3Uuid, l3Type, systemTags), null, null); + } + + public static NetworkConfigMutation ipam(String l2Uuid, + NetworkOperationOrigin origin, + String operationUuid, + String accountUuid, + String l3Uuid, + int ipVersion, + String gatewayAddress, + List ranges) { + return new NetworkConfigMutation(Kind.IPAM, l2Uuid, origin, operationUuid, + accountUuid, null, null, null, + new IpamTarget(l3Uuid, ipVersion, gatewayAddress, ranges, false), null); + } + + public static NetworkConfigMutation deleteIpam(String l2Uuid, + NetworkOperationOrigin origin, + String operationUuid, + String accountUuid, + String l3Uuid, + int ipVersion) { + return new NetworkConfigMutation(Kind.IPAM, l2Uuid, origin, operationUuid, + accountUuid, null, null, null, + new IpamTarget(l3Uuid, ipVersion, null, Collections.emptyList(), true), null); + } + + public static NetworkConfigMutation dhcp(String l2Uuid, + NetworkOperationOrigin origin, + String operationUuid, + String accountUuid, + String l3Uuid, + boolean enabled, + List systemTags) { + return new NetworkConfigMutation(Kind.DHCP_DNS, l2Uuid, origin, operationUuid, + accountUuid, null, null, null, null, + new DhcpTarget(l3Uuid, enabled, systemTags, null, null)); + } + + public static NetworkConfigMutation dhcpDns(String l2Uuid, + NetworkOperationOrigin origin, + String operationUuid, + String accountUuid, + String l3Uuid, + int ipVersion, + List dnsServers) { + return new NetworkConfigMutation(Kind.DHCP_DNS, l2Uuid, origin, operationUuid, + accountUuid, null, null, null, null, + new DhcpTarget(l3Uuid, true, Collections.emptyList(), ipVersion, dnsServers)); + } + + public Kind getKind() { + return kind; + } + + public String getL2Uuid() { + return l2Uuid; + } + + public NetworkOperationOrigin getOrigin() { + return origin; + } + + public String getOperationUuid() { + return operationUuid; + } + + public String getAccountUuid() { + return accountUuid; + } + + public EncapsulationTarget getEncapsulation() { + return encapsulation; + } + + public MetadataTarget getMetadata() { + return metadata; + } + + public L3CreateTarget getL3Create() { + return l3Create; + } + + public IpamTarget getIpam() { + return ipam; + } + + public DhcpTarget getDhcp() { + return dhcp; + } +} diff --git a/header/src/main/java/org/zstack/header/network/NetworkConfigMutationExtensionPoint.java b/header/src/main/java/org/zstack/header/network/NetworkConfigMutationExtensionPoint.java new file mode 100644 index 00000000000..2bc95a80914 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/NetworkConfigMutationExtensionPoint.java @@ -0,0 +1,11 @@ +package org.zstack.header.network; + +import org.zstack.header.core.Completion; + +public interface NetworkConfigMutationExtensionPoint { + boolean supports(NetworkConfigMutation mutation); + + void mutate(NetworkConfigMutation mutation, + NetworkConfigLocalContinuation continuation, + Completion completion); +} diff --git a/header/src/main/java/org/zstack/header/network/NetworkDeleteGuardExtensionPoint.java b/header/src/main/java/org/zstack/header/network/NetworkDeleteGuardExtensionPoint.java new file mode 100644 index 00000000000..4b599fbc09b --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/NetworkDeleteGuardExtensionPoint.java @@ -0,0 +1,11 @@ +package org.zstack.header.network; + +import org.zstack.header.errorcode.ErrorCode; + +public interface NetworkDeleteGuardExtensionPoint { + ErrorCode checkL3Network(String l3NetworkUuid); + + default ErrorCode checkL3NetworkWithLock(String l3NetworkUuid) { + return checkL3Network(l3NetworkUuid); + } +} diff --git a/header/src/main/java/org/zstack/header/network/PrepareZnsSegmentMutationMsg.java b/header/src/main/java/org/zstack/header/network/PrepareZnsSegmentMutationMsg.java new file mode 100644 index 00000000000..0286c4845e6 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/PrepareZnsSegmentMutationMsg.java @@ -0,0 +1,51 @@ +package org.zstack.header.network; + +import org.zstack.header.message.NeedReplyMessage; + +public class PrepareZnsSegmentMutationMsg extends NeedReplyMessage { + private NetworkConfigMutation mutation; + private String controllerUuid; + private String segmentUuid; + private String relationUuid; + private long expectedConfigVersion; + + public NetworkConfigMutation getMutation() { + return mutation; + } + + public void setMutation(NetworkConfigMutation mutation) { + this.mutation = mutation; + } + + public String getControllerUuid() { + return controllerUuid; + } + + public void setControllerUuid(String controllerUuid) { + this.controllerUuid = controllerUuid; + } + + public String getSegmentUuid() { + return segmentUuid; + } + + public void setSegmentUuid(String segmentUuid) { + this.segmentUuid = segmentUuid; + } + + public String getRelationUuid() { + return relationUuid; + } + + public void setRelationUuid(String relationUuid) { + this.relationUuid = relationUuid; + } + + public long getExpectedConfigVersion() { + return expectedConfigVersion; + } + + public void setExpectedConfigVersion(long expectedConfigVersion) { + this.expectedConfigVersion = expectedConfigVersion; + } +} diff --git a/header/src/main/java/org/zstack/header/network/PrepareZnsSegmentMutationReply.java b/header/src/main/java/org/zstack/header/network/PrepareZnsSegmentMutationReply.java new file mode 100644 index 00000000000..479280f8d14 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/PrepareZnsSegmentMutationReply.java @@ -0,0 +1,15 @@ +package org.zstack.header.network; + +import org.zstack.header.message.MessageReply; + +public class PrepareZnsSegmentMutationReply extends MessageReply { + private long acceptedConfigVersion; + + public long getAcceptedConfigVersion() { + return acceptedConfigVersion; + } + + public void setAcceptedConfigVersion(long acceptedConfigVersion) { + this.acceptedConfigVersion = acceptedConfigVersion; + } +} diff --git a/header/src/main/java/org/zstack/header/network/l2/AttachL2NetworkToClusterMsg.java b/header/src/main/java/org/zstack/header/network/l2/AttachL2NetworkToClusterMsg.java index af5a9290e55..ab310a04ae0 100644 --- a/header/src/main/java/org/zstack/header/network/l2/AttachL2NetworkToClusterMsg.java +++ b/header/src/main/java/org/zstack/header/network/l2/AttachL2NetworkToClusterMsg.java @@ -8,6 +8,8 @@ public class AttachL2NetworkToClusterMsg extends NeedReplyMessage implements L2N private String l2NetworkUuid; private String clusterUuid; private String l2ProviderType; + private String operationUuid; + private String operationStep; public String getL2NetworkUuid() { return l2NetworkUuid; @@ -32,4 +34,20 @@ public String getL2ProviderType() { public void setL2ProviderType(String l2ProviderType) { this.l2ProviderType = l2ProviderType; } + + public String getOperationUuid() { + return operationUuid; + } + + public void setOperationUuid(String operationUuid) { + this.operationUuid = operationUuid; + } + + public String getOperationStep() { + return operationStep; + } + + public void setOperationStep(String operationStep) { + this.operationStep = operationStep; + } } diff --git a/header/src/main/java/org/zstack/header/network/l2/CreateL2NetworkMsg.java b/header/src/main/java/org/zstack/header/network/l2/CreateL2NetworkMsg.java new file mode 100644 index 00000000000..85ce18295f4 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l2/CreateL2NetworkMsg.java @@ -0,0 +1,80 @@ +package org.zstack.header.network.l2; + +import org.zstack.header.identity.SessionInventory; +import org.zstack.header.log.NoLogging; +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.utils.gson.JSONObjectUtil; + +/** Internal, typed local L2 create request used by projection/recovery flows. */ +public class CreateL2NetworkMsg extends NeedReplyMessage { + private String name; + private String description; + private String zoneUuid; + private String physicalInterface; + private String type; + private String vSwitchType = L2NetworkConstant.VSWITCH_TYPE_LINUX_BRIDGE; + private Boolean isolated = Boolean.FALSE; + private String pvlan; + private Integer vlan; + private String resourceUuid; + private String accountUuid; + private NetworkCreateContext context; + private SessionInventory session; + @NoLogging(behavior = NoLogging.Behavior.Auto) + private Object subtypeMessage; + private String subtypeMessageClassName; + + public String getName() { return name; } + public void setName(String value) { name = value; } + public String getDescription() { return description; } + public void setDescription(String value) { description = value; } + public String getZoneUuid() { return zoneUuid; } + public void setZoneUuid(String value) { zoneUuid = value; } + public String getPhysicalInterface() { return physicalInterface; } + public void setPhysicalInterface(String value) { physicalInterface = value; } + public String getType() { return type; } + public void setType(String value) { type = value; } + public String getvSwitchType() { return vSwitchType; } + public void setvSwitchType(String value) { vSwitchType = value; } + public Boolean getIsolated() { return isolated; } + public void setIsolated(Boolean value) { isolated = value; } + public String getPvlan() { return pvlan; } + public void setPvlan(String value) { pvlan = value; } + public Integer getVlan() { return vlan; } + public void setVlan(Integer value) { vlan = value; } + public String getResourceUuid() { return resourceUuid; } + public void setResourceUuid(String value) { resourceUuid = value; } + public String getAccountUuid() { return accountUuid; } + public void setAccountUuid(String value) { accountUuid = value; } + public NetworkCreateContext getContext() { return context; } + public void setContext(NetworkCreateContext value) { context = value; } + public SessionInventory getSession() { return session; } + public void setSession(SessionInventory value) { session = value; } + public APICreateL2NetworkMsg getSubtypeMessage() { + if (subtypeMessage == null || subtypeMessage instanceof APICreateL2NetworkMsg) { + return (APICreateL2NetworkMsg) subtypeMessage; + } + if (subtypeMessageClassName == null) { + throw new IllegalArgumentException("missing l2 subtype message class"); + } + + try { + Class subtypeClass = Class.forName(subtypeMessageClassName, false, + CreateL2NetworkMsg.class.getClassLoader()); + if (!APICreateL2NetworkMsg.class.isAssignableFrom(subtypeClass)) { + throw new IllegalArgumentException(String.format( + "invalid l2 subtype message class[%s]", subtypeMessageClassName)); + } + subtypeMessage = JSONObjectUtil.rehashObject(subtypeMessage, subtypeClass); + return (APICreateL2NetworkMsg) subtypeMessage; + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException(String.format( + "cannot find l2 subtype message class[%s]", subtypeMessageClassName), e); + } + } + + public void setSubtypeMessage(APICreateL2NetworkMsg value) { + subtypeMessage = value; + subtypeMessageClassName = value == null ? null : value.getClass().getName(); + } +} diff --git a/header/src/main/java/org/zstack/header/network/l2/CreateL2NetworkReply.java b/header/src/main/java/org/zstack/header/network/l2/CreateL2NetworkReply.java new file mode 100644 index 00000000000..64fe737e036 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l2/CreateL2NetworkReply.java @@ -0,0 +1,10 @@ +package org.zstack.header.network.l2; + +import org.zstack.header.message.MessageReply; + +public class CreateL2NetworkReply extends MessageReply { + private L2NetworkInventory inventory; + + public L2NetworkInventory getInventory() { return inventory; } + public void setInventory(L2NetworkInventory value) { inventory = value; } +} diff --git a/header/src/main/java/org/zstack/header/network/l2/ExternalNetworkRef.java b/header/src/main/java/org/zstack/header/network/l2/ExternalNetworkRef.java new file mode 100644 index 00000000000..60d74831518 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l2/ExternalNetworkRef.java @@ -0,0 +1,15 @@ +package org.zstack.header.network.l2; + +/** Stable external identity carried by a projection create without changing the public API message. */ +public class ExternalNetworkRef { + private final String resourceUuid; + private final String accountUuid; + + public ExternalNetworkRef(String resourceUuid, String accountUuid) { + this.resourceUuid = resourceUuid; + this.accountUuid = accountUuid; + } + + public String getResourceUuid() { return resourceUuid; } + public String getAccountUuid() { return accountUuid; } +} diff --git a/header/src/main/java/org/zstack/header/network/l2/L2DeleteConfirmExtensionPoint.java b/header/src/main/java/org/zstack/header/network/l2/L2DeleteConfirmExtensionPoint.java new file mode 100644 index 00000000000..b1fa1d36974 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l2/L2DeleteConfirmExtensionPoint.java @@ -0,0 +1,28 @@ +package org.zstack.header.network.l2; + +import org.zstack.header.errorcode.ErrorCode; + +/** Provider hook for confirmed remote-first L2 deletion. */ +public interface L2DeleteConfirmExtensionPoint { + boolean supports(L2NetworkInventory inventory); + ErrorCode begin(L2NetworkInventory inventory); + default ErrorCode begin(L2NetworkInventory inventory, NetworkDeletionContext context) { + return begin(inventory); + } + ErrorCode check(L2NetworkInventory inventory); + default ErrorCode check(L2NetworkInventory inventory, NetworkDeletionContext context) { + return check(inventory); + } + ErrorCode delete(L2NetworkInventory inventory); + default ErrorCode delete(L2NetworkInventory inventory, NetworkDeletionContext context) { + return delete(inventory); + } + ErrorCode cancel(L2NetworkInventory inventory); + default ErrorCode cancel(L2NetworkInventory inventory, NetworkDeletionContext context) { + return cancel(inventory); + } + void deleteLocalMetadata(L2NetworkInventory inventory); + default void deleteLocalMetadata(L2NetworkInventory inventory, NetworkDeletionContext context) { + deleteLocalMetadata(inventory); + } +} diff --git a/header/src/main/java/org/zstack/header/network/l2/L2NetworkCreateExtensionPoint.java b/header/src/main/java/org/zstack/header/network/l2/L2NetworkCreateExtensionPoint.java index d50559128d9..2d38a75bcb5 100755 --- a/header/src/main/java/org/zstack/header/network/l2/L2NetworkCreateExtensionPoint.java +++ b/header/src/main/java/org/zstack/header/network/l2/L2NetworkCreateExtensionPoint.java @@ -6,6 +6,14 @@ public interface L2NetworkCreateExtensionPoint { void beforeCreateL2Network(APICreateL2NetworkMsg msg) throws NetworkException; + default void beforeCreateL2Network(APICreateL2NetworkMsg msg, NetworkCreateContext context) throws NetworkException { + beforeCreateL2Network(msg); + } + default void postCreateL2Network(L2NetworkInventory l2Network, APICreateL2NetworkMsg msg, Completion completion) {completion.success();} + default void postCreateL2Network(L2NetworkInventory l2Network, APICreateL2NetworkMsg msg, + NetworkCreateContext context, Completion completion) { + postCreateL2Network(l2Network, msg, completion); + } void afterCreateL2Network(L2NetworkInventory l2Network); } diff --git a/header/src/main/java/org/zstack/header/network/l2/L2NetworkDeleteExtensionPoint.java b/header/src/main/java/org/zstack/header/network/l2/L2NetworkDeleteExtensionPoint.java index 3e46f74b1e7..8137ac6f61c 100755 --- a/header/src/main/java/org/zstack/header/network/l2/L2NetworkDeleteExtensionPoint.java +++ b/header/src/main/java/org/zstack/header/network/l2/L2NetworkDeleteExtensionPoint.java @@ -10,5 +10,21 @@ public interface L2NetworkDeleteExtensionPoint { default void deleteL2Network(L2NetworkInventory inv, NoErrorCompletion completion) {completion.done();} + default boolean requiresConfirmedDelete(L2NetworkInventory inv) { return false; } + + default void deleteL2Network(L2NetworkInventory inv, String operationUuid, Completion completion) { + deleteL2Network(inv, new NoErrorCompletion(completion) { + @Override + public void done() { + completion.success(); + } + }); + } + + default void deleteL2Network(L2NetworkInventory inv, NetworkDeletionContext context, + Completion completion) { + deleteL2Network(inv, context == null ? null : context.getOperationUuid(), completion); + } + void afterDeleteL2Network(L2NetworkInventory inventory); } diff --git a/header/src/main/java/org/zstack/header/network/l2/L2NetworkDeletionMsg.java b/header/src/main/java/org/zstack/header/network/l2/L2NetworkDeletionMsg.java index 08273df862f..b760f43376d 100755 --- a/header/src/main/java/org/zstack/header/network/l2/L2NetworkDeletionMsg.java +++ b/header/src/main/java/org/zstack/header/network/l2/L2NetworkDeletionMsg.java @@ -6,6 +6,8 @@ */ public class L2NetworkDeletionMsg extends DeletionMessage implements L2NetworkMessage { private String l2NetworkUuid; + private String operationUuid; + private NetworkDeletionContext networkDeletionContext; public void setL2NetworkUuid(String l2NetworkUuid) { this.l2NetworkUuid = l2NetworkUuid; @@ -15,4 +17,20 @@ public void setL2NetworkUuid(String l2NetworkUuid) { public String getL2NetworkUuid() { return l2NetworkUuid; } + + public String getOperationUuid() { + return operationUuid; + } + + public void setOperationUuid(String operationUuid) { + this.operationUuid = operationUuid; + } + + public NetworkDeletionContext getNetworkDeletionContext() { + return networkDeletionContext; + } + + public void setNetworkDeletionContext(NetworkDeletionContext networkDeletionContext) { + this.networkDeletionContext = networkDeletionContext; + } } diff --git a/header/src/main/java/org/zstack/header/network/l2/L2NetworkFactory.java b/header/src/main/java/org/zstack/header/network/l2/L2NetworkFactory.java index c013389967a..97e36c3f6a1 100755 --- a/header/src/main/java/org/zstack/header/network/l2/L2NetworkFactory.java +++ b/header/src/main/java/org/zstack/header/network/l2/L2NetworkFactory.java @@ -7,5 +7,10 @@ public interface L2NetworkFactory { void createL2Network(L2NetworkVO vo, APICreateL2NetworkMsg msg, ReturnValueCompletion completion); + default void createL2Network(L2NetworkVO vo, APICreateL2NetworkMsg msg, NetworkCreateContext context, + ReturnValueCompletion completion) { + createL2Network(vo, msg, completion); + } + L2Network getL2Network(L2NetworkVO vo); } diff --git a/header/src/main/java/org/zstack/header/network/l2/L2NetworkInventoryDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/network/l2/L2NetworkInventoryDoc_zh_cn.groovy index 5ee174aa8e8..1987bdf0174 100644 --- a/header/src/main/java/org/zstack/header/network/l2/L2NetworkInventoryDoc_zh_cn.groovy +++ b/header/src/main/java/org/zstack/header/network/l2/L2NetworkInventoryDoc_zh_cn.groovy @@ -1,5 +1,7 @@ package org.zstack.header.network.l2 +import java.lang.Boolean +import java.lang.Integer import java.sql.Timestamp import java.sql.Timestamp @@ -43,6 +45,30 @@ doc { type "String" since "0.6" } + field { + name "vSwitchType" + desc "二层网络虚拟交换机类型" + type "String" + since "5.3.0" + } + field { + name "virtualNetworkId" + desc "VLAN/VNI 的虚拟网络 ID" + type "Integer" + since "5.3.0" + } + field { + name "isolated" + desc "二层网络是否开启隔离" + type "Boolean" + since "5.3.0" + } + field { + name "pvlan" + desc "二层网络私有 VLAN ID" + type "String" + since "5.3.0" + } field { name "createDate" desc "创建时间" diff --git a/header/src/main/java/org/zstack/header/network/l2/L2NetworkUpdateExtensionPoint.java b/header/src/main/java/org/zstack/header/network/l2/L2NetworkUpdateExtensionPoint.java index 1336bea1907..df817cf8302 100644 --- a/header/src/main/java/org/zstack/header/network/l2/L2NetworkUpdateExtensionPoint.java +++ b/header/src/main/java/org/zstack/header/network/l2/L2NetworkUpdateExtensionPoint.java @@ -1,5 +1,7 @@ package org.zstack.header.network.l2; +import org.zstack.header.core.Completion; + /** * Created by boce.wang on 03/25/2024. */ @@ -7,5 +9,10 @@ public interface L2NetworkUpdateExtensionPoint { void beforeChangeL2NetworkVlanId(L2NetworkInventory l2Inv); + default void beforeChangeL2NetworkVlanId(L2NetworkInventory l2Inv, Completion completion) { + beforeChangeL2NetworkVlanId(l2Inv); + completion.success(); + } + void afterChangeL2NetworkVlanId(L2NetworkInventory l2Inv); } diff --git a/header/src/main/java/org/zstack/header/network/l2/NetworkCreateContext.java b/header/src/main/java/org/zstack/header/network/l2/NetworkCreateContext.java new file mode 100644 index 00000000000..ccaf383617a --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l2/NetworkCreateContext.java @@ -0,0 +1,75 @@ +package org.zstack.header.network.l2; + +/** Additive context for local and projection network creation. */ +public class NetworkCreateContext { + public static final String APPLY_LOCAL_STEP = "APPLY_LOCAL"; + + private final NetworkOperationOrigin origin; + private final ExternalNetworkRef externalRef; + private final String operationUuid; + private final Long expectedConfigVersion; + private final boolean remoteCommitted; + private final String continuationStep; + + private NetworkCreateContext(NetworkOperationOrigin origin, ExternalNetworkRef externalRef, + String operationUuid, Long expectedConfigVersion, + boolean remoteCommitted, String continuationStep) { + this.origin = origin; + this.externalRef = externalRef; + this.operationUuid = operationUuid; + this.expectedConfigVersion = expectedConfigVersion; + this.remoteCommitted = remoteCommitted; + this.continuationStep = continuationStep; + } + + public static NetworkCreateContext api() { + return new NetworkCreateContext(NetworkOperationOrigin.API, null, null, + null, false, null); + } + + public static NetworkCreateContext cloudCommit(String operationUuid) { + return cloudCommit(operationUuid, null, APPLY_LOCAL_STEP); + } + + public static NetworkCreateContext cloudCommit(String operationUuid, + Long expectedConfigVersion, + String continuationStep) { + if (operationUuid == null || operationUuid.isEmpty()) { + throw new IllegalArgumentException("cloud commit context requires an operation UUID"); + } + return new NetworkCreateContext(NetworkOperationOrigin.CLOUD_COMMIT, null, + operationUuid, expectedConfigVersion, true, continuationStep); + } + + public static NetworkCreateContext projection(NetworkOperationOrigin origin, ExternalNetworkRef ref) { + return projection(origin, ref, null, null, null); + } + + public static NetworkCreateContext projection(NetworkOperationOrigin origin, ExternalNetworkRef ref, + String operationUuid, String operationStep) { + return projection(origin, ref, operationUuid, null, operationStep); + } + + public static NetworkCreateContext projection(NetworkOperationOrigin origin, ExternalNetworkRef ref, + String operationUuid, Long expectedConfigVersion, + String continuationStep) { + if (origin != NetworkOperationOrigin.ZNS_PROJECTION && origin != NetworkOperationOrigin.ZNS_REFRESH) { + throw new IllegalArgumentException("projection context requires a ZNS projection origin"); + } + if (ref == null || ref.getResourceUuid() == null || ref.getResourceUuid().isEmpty()) { + throw new IllegalArgumentException("projection context requires an external resource identity"); + } + return new NetworkCreateContext(origin, ref, operationUuid, expectedConfigVersion, + true, continuationStep); + } + + public NetworkOperationOrigin getOrigin() { return origin; } + public ExternalNetworkRef getExternalRef() { return externalRef; } + public String getOperationUuid() { return operationUuid; } + public Long getExpectedConfigVersion() { return expectedConfigVersion; } + public boolean isRemoteCommitted() { return remoteCommitted; } + public String getContinuationStep() { return continuationStep; } + public String getOperationStep() { return continuationStep; } + public boolean isProjection() { return origin == NetworkOperationOrigin.ZNS_PROJECTION || origin == NetworkOperationOrigin.ZNS_REFRESH; } + public boolean isRemoteWriteSuppressed() { return isProjection() || origin == NetworkOperationOrigin.CLOUD_COMMIT; } +} diff --git a/header/src/main/java/org/zstack/header/network/l2/NetworkDeletionContext.java b/header/src/main/java/org/zstack/header/network/l2/NetworkDeletionContext.java new file mode 100644 index 00000000000..efb918cc7e3 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l2/NetworkDeletionContext.java @@ -0,0 +1,104 @@ +package org.zstack.header.network.l2; + +public class NetworkDeletionContext { + public enum Origin { + WHOLE_L2_SEGMENT_DELETE + } + + private Origin origin; + private String operationUuid; + private String l2NetworkUuid; + private String rootIssuer; + private boolean forceDelete; + private boolean sourceUnavailableCleanup; + private Long expectedConfigVersion; + private boolean remoteCommitted; + private String continuationStep; + + public NetworkDeletionContext() { + } + + public NetworkDeletionContext(Origin origin, String operationUuid, String l2NetworkUuid, + String rootIssuer) { + this.origin = origin; + this.operationUuid = operationUuid; + this.l2NetworkUuid = l2NetworkUuid; + this.rootIssuer = rootIssuer; + } + + public Origin getOrigin() { + return origin; + } + + public void setOrigin(Origin origin) { + this.origin = origin; + } + + public String getOperationUuid() { + return operationUuid; + } + + public void setOperationUuid(String operationUuid) { + this.operationUuid = operationUuid; + } + + public String getL2NetworkUuid() { + return l2NetworkUuid; + } + + public void setL2NetworkUuid(String l2NetworkUuid) { + this.l2NetworkUuid = l2NetworkUuid; + } + + public String getRootIssuer() { + return rootIssuer; + } + + public void setRootIssuer(String rootIssuer) { + this.rootIssuer = rootIssuer; + } + + public boolean isWholeL2SegmentDelete() { + return Origin.WHOLE_L2_SEGMENT_DELETE == origin; + } + + public boolean isForceDelete() { + return forceDelete; + } + + public void setForceDelete(boolean forceDelete) { + this.forceDelete = forceDelete; + } + + public boolean isSourceUnavailableCleanup() { + return sourceUnavailableCleanup; + } + + public void setSourceUnavailableCleanup(boolean sourceUnavailableCleanup) { + this.sourceUnavailableCleanup = sourceUnavailableCleanup; + } + + public Long getExpectedConfigVersion() { + return expectedConfigVersion; + } + + public void setExpectedConfigVersion(Long expectedConfigVersion) { + this.expectedConfigVersion = expectedConfigVersion; + } + + public boolean isRemoteCommitted() { + return remoteCommitted; + } + + public void setRemoteCommitted(boolean remoteCommitted) { + this.remoteCommitted = remoteCommitted; + } + + public String getContinuationStep() { + return continuationStep; + } + + public void setContinuationStep(String continuationStep) { + this.continuationStep = continuationStep; + } +} diff --git a/header/src/main/java/org/zstack/header/network/l2/NetworkOperationOrigin.java b/header/src/main/java/org/zstack/header/network/l2/NetworkOperationOrigin.java new file mode 100644 index 00000000000..afa7be382c6 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l2/NetworkOperationOrigin.java @@ -0,0 +1,9 @@ +package org.zstack.header.network.l2; + +/** Identifies whether a local L2 create came from the public API or a typed projection. */ +public enum NetworkOperationOrigin { + API, + CLOUD_COMMIT, + ZNS_PROJECTION, + ZNS_REFRESH +} diff --git a/header/src/main/java/org/zstack/header/network/l3/AddIpRangeMsg.java b/header/src/main/java/org/zstack/header/network/l3/AddIpRangeMsg.java new file mode 100644 index 00000000000..d1e29605a97 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l3/AddIpRangeMsg.java @@ -0,0 +1,22 @@ +package org.zstack.header.network.l3; + +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.network.l2.NetworkCreateContext; + +/** Internal typed IP range create request for ZNS projection and recovery. */ +public class AddIpRangeMsg extends NeedReplyMessage implements L3NetworkMessage { + private String l3NetworkUuid; + private String operationUuid; + private IpRangeInventory inventory; + private NetworkCreateContext context; + + @Override + public String getL3NetworkUuid() { return l3NetworkUuid; } + public void setL3NetworkUuid(String value) { l3NetworkUuid = value; } + public String getOperationUuid() { return operationUuid; } + public void setOperationUuid(String value) { operationUuid = value; } + public IpRangeInventory getInventory() { return inventory; } + public void setInventory(IpRangeInventory value) { inventory = value; } + public NetworkCreateContext getContext() { return context; } + public void setContext(NetworkCreateContext value) { context = value; } +} diff --git a/header/src/main/java/org/zstack/header/network/l3/AddIpRangeReply.java b/header/src/main/java/org/zstack/header/network/l3/AddIpRangeReply.java new file mode 100644 index 00000000000..a65390c371e --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l3/AddIpRangeReply.java @@ -0,0 +1,10 @@ +package org.zstack.header.network.l3; + +import org.zstack.header.message.MessageReply; + +public class AddIpRangeReply extends MessageReply { + private IpRangeInventory inventory; + + public IpRangeInventory getInventory() { return inventory; } + public void setInventory(IpRangeInventory value) { inventory = value; } +} diff --git a/header/src/main/java/org/zstack/header/network/l3/AfterAddIpRangeExtensionPoint.java b/header/src/main/java/org/zstack/header/network/l3/AfterAddIpRangeExtensionPoint.java index 318d27d9e68..c61af9160f6 100755 --- a/header/src/main/java/org/zstack/header/network/l3/AfterAddIpRangeExtensionPoint.java +++ b/header/src/main/java/org/zstack/header/network/l3/AfterAddIpRangeExtensionPoint.java @@ -1,5 +1,7 @@ package org.zstack.header.network.l3; +import org.zstack.header.network.l2.NetworkCreateContext; + import java.util.List; /** @@ -7,4 +9,8 @@ */ public interface AfterAddIpRangeExtensionPoint { void afterAddIpRange(IpRangeInventory ipr, List systemTags); + + default void afterAddIpRange(IpRangeInventory ipr, List systemTags, NetworkCreateContext context) { + afterAddIpRange(ipr, systemTags); + } } diff --git a/header/src/main/java/org/zstack/header/network/l3/AfterDeleteIpRangeExtensionPoint.java b/header/src/main/java/org/zstack/header/network/l3/AfterDeleteIpRangeExtensionPoint.java index 70833859dfc..42fba54ce47 100644 --- a/header/src/main/java/org/zstack/header/network/l3/AfterDeleteIpRangeExtensionPoint.java +++ b/header/src/main/java/org/zstack/header/network/l3/AfterDeleteIpRangeExtensionPoint.java @@ -1,10 +1,14 @@ package org.zstack.header.network.l3; import org.zstack.header.core.Completion; +import org.zstack.header.network.l2.NetworkDeletionContext; /** * Created by boce.wang on 07/01/2025. */ public interface AfterDeleteIpRangeExtensionPoint { void afterDeleteIpRange(IpRangeInventory ipr); + default void afterDeleteIpRange(IpRangeInventory ipr, NetworkDeletionContext context) { + afterDeleteIpRange(ipr); + } } diff --git a/header/src/main/java/org/zstack/header/network/l3/AllocateIpMsg.java b/header/src/main/java/org/zstack/header/network/l3/AllocateIpMsg.java index 78af601bea6..e75bea0b25d 100755 --- a/header/src/main/java/org/zstack/header/network/l3/AllocateIpMsg.java +++ b/header/src/main/java/org/zstack/header/network/l3/AllocateIpMsg.java @@ -16,6 +16,8 @@ public class AllocateIpMsg extends NeedReplyMessage implements L3NetworkMessage, private String gateway; private String ipv6Gateway; private String ipv6Prefix; + private String operationUuid; + private String operationStep; public String getRequiredIp() { return requiredIp; @@ -114,4 +116,22 @@ public String getIpv6Prefix() { public void setIpv6Prefix(String ipv6Prefix) { this.ipv6Prefix = ipv6Prefix; } + + @Override + public String getOperationUuid() { + return operationUuid; + } + + public void setOperationUuid(String operationUuid) { + this.operationUuid = operationUuid; + } + + @Override + public String getOperationStep() { + return operationStep; + } + + public void setOperationStep(String operationStep) { + this.operationStep = operationStep; + } } diff --git a/header/src/main/java/org/zstack/header/network/l3/ConvertL3NetworkTypeMsg.java b/header/src/main/java/org/zstack/header/network/l3/ConvertL3NetworkTypeMsg.java new file mode 100644 index 00000000000..44904ac410e --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l3/ConvertL3NetworkTypeMsg.java @@ -0,0 +1,31 @@ +package org.zstack.header.network.l3; + +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.network.l2.NetworkCreateContext; + +public class ConvertL3NetworkTypeMsg extends NeedReplyMessage implements L3NetworkMessage { + private String l3NetworkUuid; + private String expectedSourceType; + private String expectedSourceCategory; + private String targetType; + private String targetCategory; + private String managedSystemTagPrefix; + private String targetSystemTag; + private NetworkCreateContext context; + public String getL3NetworkUuid() { return l3NetworkUuid; } + public void setL3NetworkUuid(String value) { l3NetworkUuid = value; } + public String getExpectedSourceType() { return expectedSourceType; } + public void setExpectedSourceType(String value) { expectedSourceType = value; } + public String getExpectedSourceCategory() { return expectedSourceCategory; } + public void setExpectedSourceCategory(String value) { expectedSourceCategory = value; } + public String getTargetType() { return targetType; } + public void setTargetType(String value) { targetType = value; } + public String getTargetCategory() { return targetCategory; } + public void setTargetCategory(String value) { targetCategory = value; } + public String getManagedSystemTagPrefix() { return managedSystemTagPrefix; } + public void setManagedSystemTagPrefix(String value) { managedSystemTagPrefix = value; } + public String getTargetSystemTag() { return targetSystemTag; } + public void setTargetSystemTag(String value) { targetSystemTag = value; } + public NetworkCreateContext getContext() { return context; } + public void setContext(NetworkCreateContext value) { context = value; } +} diff --git a/header/src/main/java/org/zstack/header/network/l3/CreateL3NetworkMsg.java b/header/src/main/java/org/zstack/header/network/l3/CreateL3NetworkMsg.java new file mode 100644 index 00000000000..405f65f6b46 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l3/CreateL3NetworkMsg.java @@ -0,0 +1,46 @@ +package org.zstack.header.network.l3; + +import org.zstack.header.identity.SessionInventory; +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.network.l2.NetworkCreateContext; + +/** Internal typed L3 create request for ZNS projection and recovery. */ +public class CreateL3NetworkMsg extends NeedReplyMessage { + private String name; + private String description; + private String type = L3NetworkConstant.L3_BASIC_NETWORK_TYPE; + private String l2NetworkUuid; + private String category = L3NetworkCategory.Private.toString(); + private Integer ipVersion; + private boolean system; + private String dnsDomain; + private Boolean enableIPAM = Boolean.TRUE; + private String resourceUuid; + private SessionInventory session; + private NetworkCreateContext context; + + public String getName() { return name; } + public void setName(String value) { name = value; } + public String getDescription() { return description; } + public void setDescription(String value) { description = value; } + public String getType() { return type; } + public void setType(String value) { type = value; } + public String getL2NetworkUuid() { return l2NetworkUuid; } + public void setL2NetworkUuid(String value) { l2NetworkUuid = value; } + public String getCategory() { return category; } + public void setCategory(String value) { category = value; } + public Integer getIpVersion() { return ipVersion; } + public void setIpVersion(Integer value) { ipVersion = value; } + public boolean isSystem() { return system; } + public void setSystem(boolean value) { system = value; } + public String getDnsDomain() { return dnsDomain; } + public void setDnsDomain(String value) { dnsDomain = value; } + public Boolean getEnableIPAM() { return enableIPAM; } + public void setEnableIPAM(Boolean value) { enableIPAM = value; } + public String getResourceUuid() { return resourceUuid; } + public void setResourceUuid(String value) { resourceUuid = value; } + public SessionInventory getSession() { return session; } + public void setSession(SessionInventory value) { session = value; } + public NetworkCreateContext getContext() { return context; } + public void setContext(NetworkCreateContext value) { context = value; } +} diff --git a/header/src/main/java/org/zstack/header/network/l3/CreateL3NetworkReply.java b/header/src/main/java/org/zstack/header/network/l3/CreateL3NetworkReply.java new file mode 100644 index 00000000000..17b49b1e727 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l3/CreateL3NetworkReply.java @@ -0,0 +1,10 @@ +package org.zstack.header.network.l3; + +import org.zstack.header.message.MessageReply; + +public class CreateL3NetworkReply extends MessageReply { + private L3NetworkInventory inventory; + + public L3NetworkInventory getInventory() { return inventory; } + public void setInventory(L3NetworkInventory value) { inventory = value; } +} diff --git a/header/src/main/java/org/zstack/header/network/l3/DeleteProjectedIpRangeMsg.java b/header/src/main/java/org/zstack/header/network/l3/DeleteProjectedIpRangeMsg.java new file mode 100644 index 00000000000..5c4aed519c1 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l3/DeleteProjectedIpRangeMsg.java @@ -0,0 +1,20 @@ +package org.zstack.header.network.l3; + +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.network.l2.NetworkCreateContext; + +public class DeleteProjectedIpRangeMsg extends NeedReplyMessage implements L3NetworkMessage { + private String l3NetworkUuid; + private String rangeUuid; + private String expectedSourceType; + private NetworkCreateContext context; + @Override + public String getL3NetworkUuid() { return l3NetworkUuid; } + public void setL3NetworkUuid(String value) { l3NetworkUuid = value; } + public String getRangeUuid() { return rangeUuid; } + public void setRangeUuid(String value) { rangeUuid = value; } + public String getExpectedSourceType() { return expectedSourceType; } + public void setExpectedSourceType(String value) { expectedSourceType = value; } + public NetworkCreateContext getContext() { return context; } + public void setContext(NetworkCreateContext value) { context = value; } +} diff --git a/header/src/main/java/org/zstack/header/network/l3/IpAllocateMessage.java b/header/src/main/java/org/zstack/header/network/l3/IpAllocateMessage.java index 0d886bab7b5..2df457e9755 100755 --- a/header/src/main/java/org/zstack/header/network/l3/IpAllocateMessage.java +++ b/header/src/main/java/org/zstack/header/network/l3/IpAllocateMessage.java @@ -31,6 +31,14 @@ default String getIpv6Prefix() { return null; } + default String getOperationUuid() { + return null; + } + + default String getOperationStep() { + return null; + } + void setIpRangeUuid(String ipRangeUuid); void setRequiredIp(String requiredIp); diff --git a/header/src/main/java/org/zstack/header/network/l3/IpRangeDeletionExtensionPoint.java b/header/src/main/java/org/zstack/header/network/l3/IpRangeDeletionExtensionPoint.java index 3761e566dac..6ce6541aee4 100755 --- a/header/src/main/java/org/zstack/header/network/l3/IpRangeDeletionExtensionPoint.java +++ b/header/src/main/java/org/zstack/header/network/l3/IpRangeDeletionExtensionPoint.java @@ -1,16 +1,26 @@ package org.zstack.header.network.l3; import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.network.l2.NetworkDeletionContext; /** * Created by frank on 11/15/2015. */ public interface IpRangeDeletionExtensionPoint { void preDeleteIpRange(IpRangeInventory ipRange); + default void preDeleteIpRange(IpRangeInventory ipRange, NetworkDeletionContext context) { + preDeleteIpRange(ipRange); + } void beforeDeleteIpRange(IpRangeInventory ipRange); + default void beforeDeleteIpRange(IpRangeInventory ipRange, NetworkDeletionContext context) { + beforeDeleteIpRange(ipRange); + } void afterDeleteIpRange(IpRangeInventory ipRange); + default void afterDeleteIpRange(IpRangeInventory ipRange, NetworkDeletionContext context) { + afterDeleteIpRange(ipRange); + } void failedToDeleteIpRange(IpRangeInventory ipRange, ErrorCode errorCode); } diff --git a/header/src/main/java/org/zstack/header/network/l3/IpRangeDeletionMsg.java b/header/src/main/java/org/zstack/header/network/l3/IpRangeDeletionMsg.java index d14bdd3591e..3984455f3ab 100755 --- a/header/src/main/java/org/zstack/header/network/l3/IpRangeDeletionMsg.java +++ b/header/src/main/java/org/zstack/header/network/l3/IpRangeDeletionMsg.java @@ -1,12 +1,14 @@ package org.zstack.header.network.l3; import org.zstack.header.message.DeletionMessage; +import org.zstack.header.network.l2.NetworkDeletionContext; /** */ public class IpRangeDeletionMsg extends DeletionMessage implements L3NetworkMessage { private String l3NetworkUuid; private String ipRangeUuid; + private NetworkDeletionContext networkDeletionContext; @Override public String getL3NetworkUuid() { @@ -24,4 +26,12 @@ public String getIpRangeUuid() { public void setIpRangeUuid(String ipRangeUuid) { this.ipRangeUuid = ipRangeUuid; } + + public NetworkDeletionContext getNetworkDeletionContext() { + return networkDeletionContext; + } + + public void setNetworkDeletionContext(NetworkDeletionContext networkDeletionContext) { + this.networkDeletionContext = networkDeletionContext; + } } diff --git a/header/src/main/java/org/zstack/header/network/l3/IpRangeFactory.java b/header/src/main/java/org/zstack/header/network/l3/IpRangeFactory.java index 6ad1a5507b7..b227d591926 100644 --- a/header/src/main/java/org/zstack/header/network/l3/IpRangeFactory.java +++ b/header/src/main/java/org/zstack/header/network/l3/IpRangeFactory.java @@ -2,6 +2,7 @@ import org.zstack.header.core.ReturnValueCompletion; import org.zstack.header.message.APICreateMessage; +import org.zstack.header.network.l2.NetworkCreateContext; import java.util.List; @@ -9,4 +10,9 @@ public interface IpRangeFactory { IpRangeType getType(); void createIpRange(List iprs, APICreateMessage msg, ReturnValueCompletion> completion); + + default void createIpRange(List iprs, APICreateMessage msg, NetworkCreateContext context, + ReturnValueCompletion> completion) { + createIpRange(iprs, msg, completion); + } } diff --git a/header/src/main/java/org/zstack/header/network/l3/L3NetworkDeleteExtensionPoint.java b/header/src/main/java/org/zstack/header/network/l3/L3NetworkDeleteExtensionPoint.java index 443018bd4fc..3b619ddc7f7 100755 --- a/header/src/main/java/org/zstack/header/network/l3/L3NetworkDeleteExtensionPoint.java +++ b/header/src/main/java/org/zstack/header/network/l3/L3NetworkDeleteExtensionPoint.java @@ -1,9 +1,22 @@ package org.zstack.header.network.l3; +import org.zstack.header.network.l2.NetworkDeletionContext; + public interface L3NetworkDeleteExtensionPoint { String preDeleteL3Network(L3NetworkInventory inventory) throws L3NetworkException; + default String preDeleteL3Network(L3NetworkInventory inventory, NetworkDeletionContext context) + throws L3NetworkException { + return preDeleteL3Network(inventory); + } + void beforeDeleteL3Network(L3NetworkInventory inventory); + default void beforeDeleteL3Network(L3NetworkInventory inventory, NetworkDeletionContext context) { + beforeDeleteL3Network(inventory); + } void afterDeleteL3Network(L3NetworkInventory inventory); + default void afterDeleteL3Network(L3NetworkInventory inventory, NetworkDeletionContext context) { + afterDeleteL3Network(inventory); + } } diff --git a/header/src/main/java/org/zstack/header/network/l3/L3NetworkDeletionMsg.java b/header/src/main/java/org/zstack/header/network/l3/L3NetworkDeletionMsg.java index bb3d0ecf474..67fb0630dc4 100755 --- a/header/src/main/java/org/zstack/header/network/l3/L3NetworkDeletionMsg.java +++ b/header/src/main/java/org/zstack/header/network/l3/L3NetworkDeletionMsg.java @@ -1,11 +1,13 @@ package org.zstack.header.network.l3; import org.zstack.header.message.DeletionMessage; +import org.zstack.header.network.l2.NetworkDeletionContext; /** */ public class L3NetworkDeletionMsg extends DeletionMessage implements L3NetworkMessage { private String l3NetworkUuid; + private NetworkDeletionContext networkDeletionContext; public String getL3NetworkUuid() { return l3NetworkUuid; @@ -14,4 +16,12 @@ public String getL3NetworkUuid() { public void setL3NetworkUuid(String l3NetworkUuid) { this.l3NetworkUuid = l3NetworkUuid; } + + public NetworkDeletionContext getNetworkDeletionContext() { + return networkDeletionContext; + } + + public void setNetworkDeletionContext(NetworkDeletionContext networkDeletionContext) { + this.networkDeletionContext = networkDeletionContext; + } } diff --git a/header/src/main/java/org/zstack/header/network/l3/L3NetworkFactory.java b/header/src/main/java/org/zstack/header/network/l3/L3NetworkFactory.java index 7398f552874..717f48bf810 100755 --- a/header/src/main/java/org/zstack/header/network/l3/L3NetworkFactory.java +++ b/header/src/main/java/org/zstack/header/network/l3/L3NetworkFactory.java @@ -1,10 +1,16 @@ package org.zstack.header.network.l3; +import org.zstack.header.network.l2.NetworkCreateContext; + public interface L3NetworkFactory { L3NetworkType getType(); L3NetworkInventory createL3Network(L3NetworkVO l3vo, APICreateL3NetworkMsg msg); + default L3NetworkInventory createL3Network(L3NetworkVO l3vo, APICreateL3NetworkMsg msg, NetworkCreateContext context) { + return createL3Network(l3vo, msg); + } + L3Network getL3Network(L3NetworkVO vo); boolean applyNetworkServiceWhenVmStateChange(); diff --git a/header/src/main/java/org/zstack/header/network/l3/SdnControllerDisableDHCPMsg.java b/header/src/main/java/org/zstack/header/network/l3/SdnControllerDisableDHCPMsg.java index 7d0e0c9dcb7..d530e62b8bb 100644 --- a/header/src/main/java/org/zstack/header/network/l3/SdnControllerDisableDHCPMsg.java +++ b/header/src/main/java/org/zstack/header/network/l3/SdnControllerDisableDHCPMsg.java @@ -2,6 +2,7 @@ import org.zstack.header.message.NeedReplyMessage; import org.zstack.header.network.sdncontroller.SdnControllerMessage; +import org.zstack.header.network.l2.NetworkDeletionContext; import org.zstack.utils.network.IPv6Constants; public class SdnControllerDisableDHCPMsg extends NeedReplyMessage implements SdnControllerMessage { @@ -9,6 +10,7 @@ public class SdnControllerDisableDHCPMsg extends NeedReplyMessage implements Sdn private Integer ipVersion = IPv6Constants.DUAL_STACK; private String sdnControllerUuid; private boolean checkIpRange = false; + private NetworkDeletionContext networkDeletionContext; public Integer getIpVersion() { return ipVersion; @@ -42,5 +44,12 @@ public boolean isCheckIpRange() { public void setCheckIpRange(boolean checkIpRange) { this.checkIpRange = checkIpRange; } -} + public NetworkDeletionContext getNetworkDeletionContext() { + return networkDeletionContext; + } + + public void setNetworkDeletionContext(NetworkDeletionContext networkDeletionContext) { + this.networkDeletionContext = networkDeletionContext; + } +} diff --git a/header/src/main/java/org/zstack/header/network/l3/SdnControllerL3.java b/header/src/main/java/org/zstack/header/network/l3/SdnControllerL3.java index 228b05aa98d..ed8a2d281d6 100644 --- a/header/src/main/java/org/zstack/header/network/l3/SdnControllerL3.java +++ b/header/src/main/java/org/zstack/header/network/l3/SdnControllerL3.java @@ -1,12 +1,47 @@ package org.zstack.header.network.l3; import org.zstack.header.core.Completion; +import org.zstack.header.network.l2.NetworkCreateContext; +import org.zstack.header.network.l2.NetworkDeletionContext; import java.util.List; public interface SdnControllerL3 { void createL3Network(L3NetworkInventory inv, List systemTags, Completion completion); + default void createL3Network(L3NetworkInventory inv, List systemTags, NetworkCreateContext context, Completion completion) { + createL3Network(inv, systemTags, completion); + } void deleteL3Network(L3NetworkInventory inv, Completion completion); + default void deleteL3Network(L3NetworkInventory inv, NetworkDeletionContext context, + Completion completion) { + deleteL3Network(inv, completion); + } void createIpRange(IpRangeInventory inv, Completion completion); + default void createIpRange(IpRangeInventory inv, NetworkCreateContext context, Completion completion) { + createIpRange(inv, completion); + } void deleteIpRange(IpRangeInventory inv, Completion completion); + + default void deleteIpRange(IpRangeInventory inv, boolean lastIpRange, + NetworkDeletionContext context, Completion completion) { + deleteIpRange(inv, lastIpRange, completion); + } + + default void deleteIpRange(IpRangeInventory inv, boolean lastIpRange, Completion completion) { + if (lastIpRange) { + deleteIpRange(inv, new Completion(completion) { + @Override + public void success() { + completion.success(); + } + + @Override + public void fail(org.zstack.header.errorcode.ErrorCode errorCode) { + completion.success(); + } + }); + } else { + completion.success(); + } + } } diff --git a/header/src/main/java/org/zstack/header/network/l3/UpdateProjectedDnsMsg.java b/header/src/main/java/org/zstack/header/network/l3/UpdateProjectedDnsMsg.java new file mode 100644 index 00000000000..29c1b153413 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l3/UpdateProjectedDnsMsg.java @@ -0,0 +1,22 @@ +package org.zstack.header.network.l3; + +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.network.l2.NetworkCreateContext; + +import java.util.ArrayList; +import java.util.List; + +/** Internal typed DNS projection request. */ +public class UpdateProjectedDnsMsg extends NeedReplyMessage implements L3NetworkMessage { + private String l3NetworkUuid; + private List dns = new ArrayList<>(); + private NetworkCreateContext context; + + @Override + public String getL3NetworkUuid() { return l3NetworkUuid; } + public void setL3NetworkUuid(String value) { l3NetworkUuid = value; } + public List getDns() { return dns; } + public void setDns(List value) { dns = value; } + public NetworkCreateContext getContext() { return context; } + public void setContext(NetworkCreateContext value) { context = value; } +} diff --git a/header/src/main/java/org/zstack/header/network/l3/UpdateProjectedIpRangeMsg.java b/header/src/main/java/org/zstack/header/network/l3/UpdateProjectedIpRangeMsg.java new file mode 100644 index 00000000000..85345401895 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l3/UpdateProjectedIpRangeMsg.java @@ -0,0 +1,32 @@ +package org.zstack.header.network.l3; + +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.network.l2.NetworkCreateContext; + +public class UpdateProjectedIpRangeMsg extends NeedReplyMessage implements L3NetworkMessage { + private String l3NetworkUuid; + private String rangeUuid; + private String startIp; + private String endIp; + private String gateway; + private String netmask; + private String expectedSourceType; + private NetworkCreateContext context; + @Override + public String getL3NetworkUuid() { return l3NetworkUuid; } + public void setL3NetworkUuid(String value) { l3NetworkUuid = value; } + public String getRangeUuid() { return rangeUuid; } + public void setRangeUuid(String value) { rangeUuid = value; } + public String getStartIp() { return startIp; } + public void setStartIp(String value) { startIp = value; } + public String getEndIp() { return endIp; } + public void setEndIp(String value) { endIp = value; } + public String getGateway() { return gateway; } + public void setGateway(String value) { gateway = value; } + public String getNetmask() { return netmask; } + public void setNetmask(String value) { netmask = value; } + public String getExpectedSourceType() { return expectedSourceType; } + public void setExpectedSourceType(String value) { expectedSourceType = value; } + public NetworkCreateContext getContext() { return context; } + public void setContext(NetworkCreateContext value) { context = value; } +} diff --git a/header/src/main/java/org/zstack/header/rest/RESTFacade.java b/header/src/main/java/org/zstack/header/rest/RESTFacade.java index 23fee5208c2..94569183b18 100755 --- a/header/src/main/java/org/zstack/header/rest/RESTFacade.java +++ b/header/src/main/java/org/zstack/header/rest/RESTFacade.java @@ -85,6 +85,9 @@ public interface RESTFacade { void registerSyncHttpCallHandler(String path, Class objectType, SyncHttpCallHandler handler); + void registerSyncHttpStatusBodyCallHandler(String path, Class objectType, + SyncHttpStatusBodyCallHandler handler); + String getBaseUrl(); String getSendCommandUrl(); diff --git a/header/src/main/java/org/zstack/header/rest/SyncHttpResponse.java b/header/src/main/java/org/zstack/header/rest/SyncHttpResponse.java new file mode 100644 index 00000000000..ece0b4f928e --- /dev/null +++ b/header/src/main/java/org/zstack/header/rest/SyncHttpResponse.java @@ -0,0 +1,13 @@ +package org.zstack.header.rest; + +public final class SyncHttpResponse { + private final int status; + private final String body; + + public SyncHttpResponse(int status, String body) { + this.status = status; + this.body = body; + } + public int getStatus() { return status; } + public String getBody() { return body; } +} diff --git a/header/src/main/java/org/zstack/header/rest/SyncHttpStatusBodyCallHandler.java b/header/src/main/java/org/zstack/header/rest/SyncHttpStatusBodyCallHandler.java new file mode 100644 index 00000000000..41e0a9e39a7 --- /dev/null +++ b/header/src/main/java/org/zstack/header/rest/SyncHttpStatusBodyCallHandler.java @@ -0,0 +1,5 @@ +package org.zstack.header.rest; + +public interface SyncHttpStatusBodyCallHandler extends HttpCallHandler { + SyncHttpResponse handleSyncHttpCall(T cmd); +} diff --git a/header/src/main/java/org/zstack/header/vm/VmNicQosConfigExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/VmNicQosConfigExtensionPoint.java new file mode 100644 index 00000000000..c657b19f0d3 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/VmNicQosConfigExtensionPoint.java @@ -0,0 +1,7 @@ +package org.zstack.header.vm; + +import org.zstack.header.errorcode.ErrorCode; + +public interface VmNicQosConfigExtensionPoint { + ErrorCode validateVmNicQos(String l3NetworkUuid); +} diff --git a/network/src/main/java/org/zstack/network/l2/L2NetworkCascadeExtension.java b/network/src/main/java/org/zstack/network/l2/L2NetworkCascadeExtension.java index 1c78cb16f67..3300fc40b44 100755 --- a/network/src/main/java/org/zstack/network/l2/L2NetworkCascadeExtension.java +++ b/network/src/main/java/org/zstack/network/l2/L2NetworkCascadeExtension.java @@ -5,16 +5,20 @@ import org.zstack.core.cascade.AbstractAsyncCascadeExtension; import org.zstack.core.cascade.CascadeAction; import org.zstack.core.cascade.CascadeConstant; +import org.zstack.core.cascade.CascadePreExtensionPoint; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.CloudBusListCallBack; import org.zstack.core.componentloader.PluginRegistry; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.SQL; import org.zstack.core.db.SimpleQuery; +import org.zstack.core.db.SQLBatch; import org.zstack.core.errorcode.ErrorFacade; import org.zstack.header.core.Completion; +import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.identity.AccountInventory; import org.zstack.header.identity.AccountVO; +import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.message.MessageReply; import org.zstack.header.network.l2.*; import org.zstack.header.network.sdncontroller.SdnControllerInventory; @@ -29,15 +33,18 @@ import javax.persistence.TypedQuery; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.Callable; +import java.util.stream.Collectors; import static org.zstack.core.Platform.inerr; import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.*; /** */ -public class L2NetworkCascadeExtension extends AbstractAsyncCascadeExtension { +public class L2NetworkCascadeExtension extends AbstractAsyncCascadeExtension implements CascadePreExtensionPoint { private static final CLogger logger = Utils.getLogger(L2NetworkCascadeExtension.class); @Autowired @@ -53,6 +60,82 @@ public class L2NetworkCascadeExtension extends AbstractAsyncCascadeExtension { private static final String NAME = L2NetworkVO.class.getSimpleName(); + @Override + public ErrorCode beforeCascade(CascadeAction action) { + if (!CascadeConstant.DELETION_CODES.contains(action.getActionCode())) { + return null; + } + List inventories = l2NetworkFromAction(action); + if (inventories == null) { + return null; + } + inventories = filterInventories(inventories, action); + List begun = new ArrayList<>(); + try { + for (L2NetworkInventory inventory : inventories) { + NetworkDeletionContext context = NetworkDeletionContexts.get(action, inventory.getUuid()); + if (context != null) { + continue; + } + context = new NetworkDeletionContext(NetworkDeletionContext.Origin.WHOLE_L2_SEGMENT_DELETE, + org.zstack.core.Platform.getUuid(), inventory.getUuid(), action.getRootIssuer()); + context.setForceDelete(action.isActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE)); + List extensions = confirmedExtensions(inventory); + for (L2DeleteConfirmExtensionPoint extension : extensions) { + ErrorCode error = extension.begin(inventory, context); + if (error != null) { + cancelConfirmedDeletes(begun); + return error; + } + begun.add(new ConfirmedDelete(inventory, extension, context)); + } + if (!extensions.isEmpty()) { + NetworkDeletionContexts.put(action, context); + } + if (action.isActionCode(CascadeConstant.DELETION_DELETE_CODE, + CascadeConstant.DELETION_FORCE_DELETE_CODE)) { + for (L2DeleteConfirmExtensionPoint extension : extensions) { + ErrorCode error = extension.check(inventory, context); + if (error != null) { + cancelConfirmedDeletes(begun); + return error; + } + } + } + } + } catch (OperationFailureException e) { + cancelConfirmedDeletes(begun); + return e.getErrorCode(); + } catch (RuntimeException e) { + cancelConfirmedDeletes(begun); + return inerr(ORG_ZSTACK_NETWORK_L2_10023, + "failed to prepare confirmed l2 network deletion: %s", e.getMessage()); + } + return null; + } + + @Override + public void afterCascadeFailure(CascadeAction action) { + if (!CascadeConstant.DELETION_CODES.contains(action.getActionCode())) { + return; + } + List inventories = l2NetworkFromAction(action); + if (inventories == null) { + return; + } + List begun = new ArrayList<>(); + for (L2NetworkInventory inventory : filterInventories(inventories, action)) { + NetworkDeletionContext context = NetworkDeletionContexts.get(action, inventory.getUuid()); + if (context == null) { + continue; + } + for (L2DeleteConfirmExtensionPoint extension : confirmedExtensions(inventory)) { + begun.add(new ConfirmedDelete(inventory, extension, context)); + } + } + cancelConfirmedDeletes(begun); + } + @Override public void asyncCascade(CascadeAction action, Completion completion) { if (action.isActionCode(CascadeConstant.DELETION_CHECK_CODE)) { @@ -102,7 +185,9 @@ private void handleDeletionCleanup(CascadeAction action, Completion completion) try { final List l2invs = l2NetworkFromAction(action); if (l2invs != null) { - l2invs.forEach(l -> dbf.eoCleanup(L2NetworkVO.class, l.getUuid())); + l2invs.forEach(l -> { + dbf.eoCleanup(L2NetworkVO.class, l.getUuid()); + }); } else { dbf.eoCleanup(L2NetworkVO.class); } @@ -121,10 +206,12 @@ private void handleDeletion(final CascadeAction action, final Completion complet return; } - if (!l2invs.isEmpty()) { - for (L2NetworkCascadeFilterExtensionPoint ext : pluginRgty.getExtensionList(L2NetworkCascadeFilterExtensionPoint.class)) { - l2invs = ext.filterL2NetworkCascade(l2invs, action); - } + l2invs = filterInventories(l2invs, action); + + Map> confirmedExtensions = new HashMap<>(); + for (L2NetworkInventory l2inv : l2invs) { + List extensions = confirmedExtensions(l2inv); + confirmedExtensions.put(l2inv.getUuid(), extensions); } List msgs = new ArrayList(); @@ -132,6 +219,9 @@ private void handleDeletion(final CascadeAction action, final Completion complet L2NetworkDeletionMsg msg = new L2NetworkDeletionMsg(); msg.setForceDelete(action.isActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE)); msg.setL2NetworkUuid(l2inv.getUuid()); + NetworkDeletionContext context = NetworkDeletionContexts.get(action, l2inv.getUuid()); + msg.setNetworkDeletionContext(context); + msg.setOperationUuid(context == null ? org.zstack.core.Platform.getUuid() : context.getOperationUuid()); bus.makeTargetServiceIdByResourceUuid(msg, L2NetworkConstant.SERVICE_ID, l2inv.getUuid()); msgs.add(msg); } @@ -152,12 +242,42 @@ public void run(List replies) { List uuids = new ArrayList(); for (MessageReply r : replies) { L2NetworkInventory inv = finalL2invs.get(replies.indexOf(r)); + if (!r.isSuccess()) { + if (!confirmedExtensions.get(inv.getUuid()).isEmpty()) { + completion.fail(r.getError()); + return; + } + } + for (L2DeleteConfirmExtensionPoint ext : confirmedExtensions.get(inv.getUuid())) { + ErrorCode errorCode = ext.delete(inv, + NetworkDeletionContexts.get(action, inv.getUuid())); + if (errorCode != null) { + completion.fail(errorCode); + return; + } + } uuids.add(inv.getUuid()); logger.debug(String.format("delete l2 network[uuid:%s, name:%s]", inv.getUuid(), inv.getName())); } - dbf.removeByPrimaryKeys(uuids, L2NetworkVO.class); - completion.success(); + try { + new SQLBatch() { + @Override + protected void scripts() { + for (L2NetworkInventory inv : finalL2invs) { + for (L2DeleteConfirmExtensionPoint ext : confirmedExtensions.get(inv.getUuid())) { + ext.deleteLocalMetadata(inv, + NetworkDeletionContexts.get(action, inv.getUuid())); + } + } + databaseFacade.removeByPrimaryKeys(uuids, L2NetworkVO.class); + } + }.execute(); + completion.success(); + } catch (RuntimeException e) { + completion.fail(inerr(ORG_ZSTACK_NETWORK_L2_10022, + "failed to commit confirmed l2 network deletion: %s", e.getMessage())); + } } }); } @@ -170,8 +290,16 @@ private void handleDeletionCheck(CascadeAction action, Completion completion) { } try { - for (L2NetworkInventory prinv : l2invs) { + for (L2NetworkInventory prinv : filterInventories(l2invs, action)) { extpEmitter.preDelete(prinv); + for (L2DeleteConfirmExtensionPoint ext : confirmedExtensions(prinv)) { + ErrorCode errorCode = ext.check(prinv, + NetworkDeletionContexts.get(action, prinv.getUuid())); + if (errorCode != null) { + completion.fail(errorCode); + return; + } + } } completion.success(); @@ -180,6 +308,54 @@ private void handleDeletionCheck(CascadeAction action, Completion completion) { } } + private void cancelConfirmedDeletes(List begun) { + for (int i = begun.size() - 1; i >= 0; i--) { + ConfirmedDelete delete = begun.get(i); + try { + ErrorCode errorCode = delete.extension.cancel(delete.inventory, delete.context); + if (errorCode != null) { + logger.warn(String.format("failed to cancel confirmed l2 network deletion[uuid:%s]: %s", + delete.inventory.getUuid(), errorCode)); + } + } catch (RuntimeException e) { + logger.warn(String.format("failed to cancel confirmed l2 network deletion[uuid:%s]", + delete.inventory.getUuid()), e); + } + } + } + + private static class ConfirmedDelete { + private final L2NetworkInventory inventory; + private final L2DeleteConfirmExtensionPoint extension; + private final NetworkDeletionContext context; + + private ConfirmedDelete(L2NetworkInventory inventory, L2DeleteConfirmExtensionPoint extension, + NetworkDeletionContext context) { + this.inventory = inventory; + this.extension = extension; + this.context = context; + } + } + + private List confirmedExtensions(L2NetworkInventory inventory) { + return pluginRgty.getExtensionList(L2DeleteConfirmExtensionPoint.class).stream() + .filter(ext -> ext.supports(inventory)) + .collect(Collectors.toList()); + } + + private List filterInventories(List inventories, + CascadeAction action) { + if (inventories.isEmpty()) { + return inventories; + } + List result = inventories; + for (L2NetworkCascadeFilterExtensionPoint ext : + pluginRgty.getExtensionList(L2NetworkCascadeFilterExtensionPoint.class)) { + result = ext.filterL2NetworkCascade(result, action); + } + return result; + } + @Override public List getEdgeNames() { return Arrays.asList(ZoneVO.class.getSimpleName(), diff --git a/network/src/main/java/org/zstack/network/l2/L2NetworkExtensionPointEmitter.java b/network/src/main/java/org/zstack/network/l2/L2NetworkExtensionPointEmitter.java index b1eecae4d5e..14beaaa63f3 100755 --- a/network/src/main/java/org/zstack/network/l2/L2NetworkExtensionPointEmitter.java +++ b/network/src/main/java/org/zstack/network/l2/L2NetworkExtensionPointEmitter.java @@ -3,6 +3,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.zstack.core.componentloader.PluginRegistry; import org.zstack.header.Component; +import org.zstack.header.core.Completion; +import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.network.l2.L2NetworkDeleteExtensionPoint; import org.zstack.header.network.l2.L2NetworkException; import org.zstack.header.network.l2.L2NetworkInventory; @@ -14,6 +17,9 @@ import java.util.List; +import static org.zstack.core.Platform.inerr; +import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_NETWORK_L2_10024; + public class L2NetworkExtensionPointEmitter implements Component { private static final CLogger logger = Utils.getLogger(L2NetworkExtensionPointEmitter.class); private List extensions = null; @@ -67,6 +73,39 @@ public void beforeUpdate(final L2NetworkInventory inv) { } } + public void beforeUpdate(final L2NetworkInventory inv, Completion completion) { + beforeUpdate(inv, 0, completion); + } + + private void beforeUpdate(final L2NetworkInventory inv, int index, Completion completion) { + if (index >= updateExtensions.size()) { + completion.success(); + return; + } + L2NetworkUpdateExtensionPoint extension = updateExtensions.get(index); + try { + extension.beforeChangeL2NetworkVlanId(inv, new Completion(completion) { + @Override + public void success() { + beforeUpdate(inv, index + 1, completion); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + }); + } catch (OperationFailureException e) { + completion.fail(e.getErrorCode()); + } catch (RuntimeException e) { + logger.warn(String.format("exception in L2NetworkUpdateExtensionPoint.beforeChangeL2NetworkVlanId of %s", + extension.getClass().getCanonicalName()), e); + completion.fail(inerr(ORG_ZSTACK_NETWORK_L2_10024, + "l2 network update extension[%s] failed before changing vlan id: %s", + extension.getClass().getCanonicalName(), e.getMessage())); + } + } + public void afterUpdate(final L2NetworkInventory inv) { CollectionUtils.safeForEach(updateExtensions, arg -> arg.afterChangeL2NetworkVlanId(inv)); } diff --git a/network/src/main/java/org/zstack/network/l2/L2NetworkManagerImpl.java b/network/src/main/java/org/zstack/network/l2/L2NetworkManagerImpl.java index a62e87ddc4f..45e49302861 100755 --- a/network/src/main/java/org/zstack/network/l2/L2NetworkManagerImpl.java +++ b/network/src/main/java/org/zstack/network/l2/L2NetworkManagerImpl.java @@ -76,13 +76,62 @@ public void handleMessage(Message msg) { } private void handleLocalMessage(Message msg) { - if (msg instanceof L2NetworkMessage) { + if (msg instanceof CreateL2NetworkMsg) { + handle((CreateL2NetworkMsg) msg); + } else if (msg instanceof L2NetworkMessage) { passThrough((L2NetworkMessage)msg); } else { bus.dealWithUnknownMessage(msg); } } + private void handle(CreateL2NetworkMsg msg) { + APICreateL2NetworkMsg apiMsg = msg.getSubtypeMessage(); + if (apiMsg != null && !msg.getType().equals(apiMsg.getType())) { + bus.replyErrorByMessageType(msg, err(ORG_ZSTACK_NETWORK_L2_10025, SysErrors.INVALID_ARGUMENT_ERROR, + "internal l2 type[%s] does not match subtype message[%s]", + msg.getType(), apiMsg.getType())); + return; + } else if (apiMsg == null && L2NetworkConstant.L2_VLAN_NETWORK_TYPE.equals(msg.getType())) { + APICreateL2VlanNetworkMsg vlanMsg = new APICreateL2VlanNetworkMsg(); + vlanMsg.setVlan(msg.getVlan() == null ? 0 : msg.getVlan()); + apiMsg = vlanMsg; + } else if (apiMsg == null && L2NetworkConstant.L2_NO_VLAN_NETWORK_TYPE.equals(msg.getType())) { + apiMsg = new APICreateL2NoVlanNetworkMsg(); + } else if (apiMsg == null) { + bus.replyErrorByMessageType(msg, err(ORG_ZSTACK_NETWORK_L2_10026, SysErrors.INVALID_ARGUMENT_ERROR, + "unsupported internal l2 network type[%s]", msg.getType())); + return; + } + apiMsg.setName(msg.getName()); + apiMsg.setDescription(msg.getDescription()); + apiMsg.setZoneUuid(msg.getZoneUuid()); + apiMsg.setPhysicalInterface(msg.getPhysicalInterface()); + apiMsg.setvSwitchType(msg.getvSwitchType()); + apiMsg.setIsolated(msg.getIsolated()); + apiMsg.setPvlan(msg.getPvlan()); + apiMsg.setResourceUuid(msg.getResourceUuid()); + apiMsg.setSession(msg.getSession()); + apiMsg.setSystemTags(msg.getSystemTags()); + NetworkCreateContext context = msg.getContext(); + if (context == null) { + context = NetworkCreateContext.api(); + } + handle(apiMsg, context, new ReturnValueCompletion(msg) { + @Override + public void success(L2NetworkInventory returnValue) { + CreateL2NetworkReply reply = new CreateL2NetworkReply(); + reply.setInventory(returnValue); + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + bus.replyErrorByMessageType(msg, errorCode); + } + }); + } + private void handleApiMessage(APIMessage msg) { if (msg instanceof APICreateL2NetworkMsg) { handle((APICreateL2NetworkMsg)msg); @@ -417,14 +466,33 @@ private void passThrough(L2NetworkMessage msg) { } private void handle(APICreateL2NetworkMsg msg) { - for (L2NetworkCreateExtensionPoint extp : createExtensions) { - try { - extp.beforeCreateL2Network(msg); + handle(msg, NetworkCreateContext.api(), new ReturnValueCompletion(msg) { + @Override + public void success(L2NetworkInventory returnValue) { + APICreateL2NetworkEvent evt = new APICreateL2NetworkEvent(msg.getId()); + evt.setInventory(returnValue); + bus.publish(evt); + } + + @Override + public void fail(ErrorCode errorCode) { + APICreateL2NetworkEvent evt = new APICreateL2NetworkEvent(msg.getId()); + evt.setError(errorCode); + bus.publish(evt); + } + }); + } + + private void handle(APICreateL2NetworkMsg msg, NetworkCreateContext context, + ReturnValueCompletion completion) { + for (L2NetworkCreateExtensionPoint extp : createExtensions) { + try { + extp.beforeCreateL2Network(msg, context); } catch (NetworkException e) { - APICreateL2NetworkEvent evt = new APICreateL2NetworkEvent(msg.getId()); - evt.setError(err(ORG_ZSTACK_NETWORK_L2_10002, SysErrors.CREATE_RESOURCE_ERROR, "unable to create l2network[name:%s, type:%s], %s", msg.getName(), msg.getType(), e.getMessage())); - logger.warn(evt.getError().getDetails(), e); - bus.publish(evt); + ErrorCode error = err(ORG_ZSTACK_NETWORK_L2_10027, SysErrors.CREATE_RESOURCE_ERROR, + "unable to create l2network[name:%s, type:%s], %s", msg.getName(), msg.getType(), e.getMessage()); + logger.warn(error.getDetails(), e); + completion.fail(error); return; } } @@ -444,10 +512,19 @@ private void handle(APICreateL2NetworkMsg msg) { vo.setType(type.toString()); vo.setvSwitchType(vSwitchType.toString()); vo.setZoneUuid(msg.getZoneUuid()); - vo.setAccountUuid(msg.getSession().getAccountUuid()); + String accountUuid = msg.getSession() == null ? null : msg.getSession().getAccountUuid(); + if (accountUuid == null && context.getExternalRef() != null) { + accountUuid = context.getExternalRef().getAccountUuid(); + } + if (accountUuid == null) { + completion.fail(err(ORG_ZSTACK_NETWORK_L2_10028, SysErrors.INVALID_ARGUMENT_ERROR, + "account uuid is required for l2 create")); + return; + } + vo.setAccountUuid(accountUuid); vo.setIsolated(msg.getIsolated()); vo.setPvlan(msg.getPvlan()); - factory.createL2Network(vo, msg, new ReturnValueCompletion(msg) { + factory.createL2Network(vo, msg, context, new ReturnValueCompletion(msg) { @Override public void success(L2NetworkInventory returnValue) { tagMgr.createTagsFromAPICreateMessage(msg, returnValue.getUuid(), L2NetworkVO.class.getSimpleName()); @@ -460,16 +537,12 @@ public void success(L2NetworkInventory returnValue) { } } - APICreateL2NetworkEvent evt = new APICreateL2NetworkEvent(msg.getId()); - evt.setInventory(returnValue); - bus.publish(evt); + completion.success(returnValue); } @Override public void fail(ErrorCode errorCode) { - APICreateL2NetworkEvent evt = new APICreateL2NetworkEvent(msg.getId()); - evt.setError(errorCode); - bus.publish(evt); + completion.fail(errorCode); } }); diff --git a/network/src/main/java/org/zstack/network/l2/L2NoVlanL2NetworkFactory.java b/network/src/main/java/org/zstack/network/l2/L2NoVlanL2NetworkFactory.java index 3d5720ccd26..207c3ae7e13 100755 --- a/network/src/main/java/org/zstack/network/l2/L2NoVlanL2NetworkFactory.java +++ b/network/src/main/java/org/zstack/network/l2/L2NoVlanL2NetworkFactory.java @@ -43,6 +43,12 @@ public L2NetworkType getType() { @Override public void createL2Network(L2NetworkVO vo, APICreateL2NetworkMsg msg, ReturnValueCompletion completion) { + createL2Network(vo, msg, NetworkCreateContext.api(), completion); + } + + @Override + public void createL2Network(L2NetworkVO vo, APICreateL2NetworkMsg msg, NetworkCreateContext context, + ReturnValueCompletion completion) { FlowChain chain = new SimpleFlowChain(); chain.setName("create-no-vlan-network"); @@ -65,7 +71,7 @@ public void rollback(FlowRollback trigger, Map data) { @Override public void run(FlowTrigger trigger, Map data) { new While<>(pluginRgty.getExtensionList(L2NetworkCreateExtensionPoint.class)) - .each((exp, wcompl) -> exp.postCreateL2Network(L2NetworkInventory.valueOf(vo), msg, new Completion(trigger) { + .each((exp, wcompl) -> exp.postCreateL2Network(L2NetworkInventory.valueOf(vo), msg, context, new Completion(trigger) { @Override public void success() { wcompl.done(); diff --git a/network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java b/network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java index ec9a7a5be0b..e97167d448b 100755 --- a/network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java +++ b/network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java @@ -5,6 +5,7 @@ import org.springframework.beans.factory.annotation.Configurable; import org.zstack.core.asyncbatch.While; import org.zstack.core.cascade.CascadeConstant; +import org.zstack.core.cascade.CascadeAction; import org.zstack.core.cascade.CascadeFacade; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.CloudBusCallBack; @@ -39,6 +40,9 @@ import org.zstack.header.network.l2.*; import org.zstack.header.network.l3.L3NetworkVO; import org.zstack.header.network.l3.L3NetworkVO_; +import org.zstack.header.network.NetworkConfigLocalContinuation; +import org.zstack.header.network.NetworkConfigMutation; +import org.zstack.header.network.NetworkConfigMutationExtensionPoint; import org.zstack.network.l3.ServiceTypeExtensionPoint; import org.zstack.utils.Utils; import org.zstack.utils.logging.CLogger; @@ -156,13 +160,15 @@ private void handle(DeleteL2NetworkMsg msg) { DeleteL2NetworkReply reply = new DeleteL2NetworkReply(); final String issuer = L2NetworkVO.class.getSimpleName(); final List ctx = L2NetworkInventory.valueOf(Arrays.asList(self)); + final CascadeAction cascadeAction = new CascadeAction().setRootIssuer(issuer) + .setRootIssuerContext(ctx).setParentIssuer(issuer).setParentIssuerContext(ctx); FlowChain chain = FlowChainBuilder.newSimpleFlowChain(); chain.setName(String.format("delete-l2Network-%s", msg.getL2NetworkUuid())); if (msg.isForceDelete() == false) { chain.then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_CHECK_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_CHECK_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -177,7 +183,7 @@ public void fail(ErrorCode errorCode) { }).then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_DELETE_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_DELETE_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -194,7 +200,7 @@ public void fail(ErrorCode errorCode) { chain.then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_FORCE_DELETE_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -352,12 +358,29 @@ private void handle(L2NetworkDeletionMsg msg) { @Override public void run(FlowTrigger trigger, Map data) { new While<>(pluginRgty.getExtensionList(L2NetworkDeleteExtensionPoint.class)) - .each((exp, wcompl) -> exp.deleteL2Network(inv, new NoErrorCompletion(trigger) { - @Override - public void done() { - wcompl.done(); + .each((exp, wcompl) -> { + if (exp.requiresConfirmedDelete(inv)) { + exp.deleteL2Network(inv, msg.getNetworkDeletionContext(), new Completion(trigger) { + @Override + public void success() { + wcompl.done(); + } + + @Override + public void fail(ErrorCode errorCode) { + wcompl.addError(errorCode); + wcompl.allDone(); + } + }); + return; } - })).run(new WhileDoneCompletion(trigger) { + exp.deleteL2Network(inv, new NoErrorCompletion(trigger) { + @Override + public void done() { + wcompl.done(); + } + }); + }).run(new WhileDoneCompletion(trigger) { @Override public void done(ErrorCodeList errorCodeList) { if (errorCodeList.getCauses().isEmpty()) { @@ -443,12 +466,39 @@ public String getSyncSignature() { @Override public void run(SyncTaskChain chain) { - extpEmitter.beforeUpdate(getSelfInventory()); - changeL2NetworkVlanId(msg, new Completion(chain) { + L2NetworkInventory target = getSelfInventory(); + target.setType(msg.getType()); + target.setVirtualNetworkId(msg.getVlan() == null ? 0 : msg.getVlan()); + NetworkConfigMutation mutation = NetworkConfigMutation.encapsulation( + msg.getL2NetworkUuid(), NetworkOperationOrigin.API, msg.getId(), + msg.getSession() == null ? null : msg.getSession().getAccountUuid(), + target.getType(), target.getVirtualNetworkId()); + mutateNetworkConfig(mutation, localCompletion -> { + try { + extpEmitter.beforeUpdate(target); + } catch (OperationFailureException e) { + localCompletion.fail(e.getErrorCode()); + return; + } catch (Exception e) { + localCompletion.fail(errf.throwableToInternalError(e)); + return; + } + changeL2NetworkVlanId(msg, new Completion(localCompletion) { + @Override + public void success() { + changeL2NetworkVlanIdInDb(msg); + extpEmitter.afterUpdate(getSelfInventory()); + localCompletion.success(); + } + + @Override + public void fail(ErrorCode errorCode) { + localCompletion.fail(errorCode); + } + }); + }, new Completion(chain) { @Override public void success() { - changeL2NetworkVlanIdInDb(msg); - extpEmitter.afterUpdate(getSelfInventory()); event.setInventory(getSelfInventory()); bus.publish(event); chain.next(); @@ -470,23 +520,85 @@ public String getName() { }); } - private void handle(APIUpdateL2NetworkMsg msg) { - boolean update = false; - if (msg.getName() != null) { - self.setName(msg.getName()); - update = true; - } - if (msg.getDescription() != null) { - self.setDescription(msg.getDescription()); - update = true; + protected void mutateNetworkConfig(NetworkConfigMutation mutation, + NetworkConfigLocalContinuation continuation, + Completion completion) { + List providers = pluginRgty + .getExtensionList(NetworkConfigMutationExtensionPoint.class).stream() + .filter(provider -> provider.supports(mutation)) + .collect(Collectors.toList()); + if (providers.isEmpty()) { + continuation.run(completion); + return; } - if (update) { - self = dbf.updateAndRefresh(self); + if (providers.size() != 1) { + completion.fail(errf.stringToInternalError(String.format( + "network mutation[%s] matched[%s] providers", + mutation.getKind(), providers.size()))); + return; } + providers.get(0).mutate(mutation, continuation, completion); + } + private void handle(APIUpdateL2NetworkMsg msg) { APIUpdateL2NetworkEvent evt = new APIUpdateL2NetworkEvent(msg.getId()); - evt.setInventory(getSelfInventory()); - bus.publish(evt); + if (msg.getName() == null && msg.getDescription() == null) { + evt.setInventory(getSelfInventory()); + bus.publish(evt); + return; + } + + thdf.chainSubmit(new ChainTask(msg) { + @Override + public String getSyncSignature() { + return String.format("update-l2-network-%s-metadata", msg.getL2NetworkUuid()); + } + + @Override + public void run(SyncTaskChain chain) { + NetworkConfigMutation mutation = NetworkConfigMutation.metadata( + msg.getL2NetworkUuid(), NetworkOperationOrigin.API, msg.getId(), + msg.getSession() == null ? null : msg.getSession().getAccountUuid(), + msg.getName(), msg.getDescription()); + mutateNetworkConfig(mutation, localCompletion -> { + String originalName = self.getName(); + String originalDescription = self.getDescription(); + try { + if (msg.getName() != null) { + self.setName(msg.getName()); + } + if (msg.getDescription() != null) { + self.setDescription(msg.getDescription()); + } + self = dbf.updateAndRefresh(self); + localCompletion.success(); + } catch (Exception e) { + self.setName(originalName); + self.setDescription(originalDescription); + localCompletion.fail(errf.throwableToInternalError(e)); + } + }, new Completion(chain) { + @Override + public void success() { + evt.setInventory(getSelfInventory()); + bus.publish(evt); + chain.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + evt.setError(errorCode); + bus.publish(evt); + chain.next(); + } + }); + } + + @Override + public String getName() { + return getSyncSignature(); + } + }); } private void handle(final APIDetachL2NetworkFromClusterMsg msg) { @@ -856,13 +968,15 @@ private void handle(APIDeleteL2NetworkMsg msg) { final APIDeleteL2NetworkEvent evt = new APIDeleteL2NetworkEvent(msg.getId()); final String issuer = L2NetworkVO.class.getSimpleName(); final List ctx = L2NetworkInventory.valueOf(Arrays.asList(self)); + final CascadeAction cascadeAction = new CascadeAction().setRootIssuer(issuer) + .setRootIssuerContext(ctx).setParentIssuer(issuer).setParentIssuerContext(ctx); FlowChain chain = FlowChainBuilder.newSimpleFlowChain(); chain.setName(String.format("delete-l2Network-%s", msg.getL2NetworkUuid())); if (msg.getDeletionMode() == APIDeleteMessage.DeletionMode.Permissive) { chain.then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_CHECK_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_CHECK_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -877,7 +991,7 @@ public void fail(ErrorCode errorCode) { }).then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_DELETE_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_DELETE_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -894,7 +1008,7 @@ public void fail(ErrorCode errorCode) { chain.then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_FORCE_DELETE_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -1039,11 +1153,24 @@ protected void scripts() { prepareL2NetworkOnHosts(hvinvs, msg.getL2ProviderType(), new Completion(msg,completion) { @Override public void success() { - L2NetworkClusterRefVO rvo = new L2NetworkClusterRefVO(); - rvo.setClusterUuid(msg.getClusterUuid()); - rvo.setL2NetworkUuid(self.getUuid()); - rvo.setL2ProviderType(msg.getL2ProviderType()); - dbf.persist(rvo); + try { + new SQLBatch() { + @Override + protected void scripts() { + L2NetworkClusterRefVO rvo = new L2NetworkClusterRefVO(); + rvo.setClusterUuid(msg.getClusterUuid()); + rvo.setL2NetworkUuid(self.getUuid()); + rvo.setL2ProviderType(msg.getL2ProviderType()); + persist(rvo); + } + }.execute(); + } catch (OperationFailureException e) { + completion.fail(e.getErrorCode()); + return; + } catch (Exception e) { + completion.fail(errf.throwableToInternalError(e)); + return; + } logger.debug(String.format("successfully attached L2Network[uuid:%s] to cluster [uuid:%s]", self.getUuid(), msg.getClusterUuid())); self = dbf.findByUuid(self.getUuid(), L2NetworkVO.class); completion.success(); diff --git a/network/src/main/java/org/zstack/network/l2/L2VlanNetworkFactory.java b/network/src/main/java/org/zstack/network/l2/L2VlanNetworkFactory.java index 35c34f1799a..6112ccba530 100755 --- a/network/src/main/java/org/zstack/network/l2/L2VlanNetworkFactory.java +++ b/network/src/main/java/org/zstack/network/l2/L2VlanNetworkFactory.java @@ -51,6 +51,12 @@ public L2NetworkType getType() { @Override public void createL2Network(L2NetworkVO ovo, APICreateL2NetworkMsg msg, ReturnValueCompletion completion) { + createL2Network(ovo, msg, NetworkCreateContext.api(), completion); + } + + @Override + public void createL2Network(L2NetworkVO ovo, APICreateL2NetworkMsg msg, NetworkCreateContext context, + ReturnValueCompletion completion) { FlowChain chain = new SimpleFlowChain(); chain.setName("create-no-vlan-network"); chain.then(new Flow() { @@ -78,7 +84,7 @@ public void run(FlowTrigger trigger, Map data) { L2VlanNetworkInventory inv = L2VlanNetworkInventory.valueOf( dbf.findByUuid(ovo.getUuid(), L2VlanNetworkVO.class)); new While<>(pluginRgty.getExtensionList(L2NetworkCreateExtensionPoint.class)) - .each((exp, wcompl) -> exp.postCreateL2Network(inv, msg, new Completion(trigger) { + .each((exp, wcompl) -> exp.postCreateL2Network(inv, msg, context, new Completion(trigger) { @Override public void success() { wcompl.done(); diff --git a/network/src/main/java/org/zstack/network/l2/NetworkDeletionContexts.java b/network/src/main/java/org/zstack/network/l2/NetworkDeletionContexts.java new file mode 100644 index 00000000000..f2dc5c081bc --- /dev/null +++ b/network/src/main/java/org/zstack/network/l2/NetworkDeletionContexts.java @@ -0,0 +1,19 @@ +package org.zstack.network.l2; + +import org.zstack.core.cascade.CascadeAction; +import org.zstack.header.network.l2.NetworkDeletionContext; + +public final class NetworkDeletionContexts { + private static final String KEY_PREFIX = NetworkDeletionContext.class.getName() + ":"; + + private NetworkDeletionContexts() { + } + + public static NetworkDeletionContext get(CascadeAction action, String l2NetworkUuid) { + return action.getContext(KEY_PREFIX + l2NetworkUuid); + } + + public static void put(CascadeAction action, NetworkDeletionContext context) { + action.putContext(KEY_PREFIX + context.getL2NetworkUuid(), context); + } +} diff --git a/network/src/main/java/org/zstack/network/l3/AbstractIpAllocatorStrategy.java b/network/src/main/java/org/zstack/network/l3/AbstractIpAllocatorStrategy.java index 490408c5c93..a94e657d056 100755 --- a/network/src/main/java/org/zstack/network/l3/AbstractIpAllocatorStrategy.java +++ b/network/src/main/java/org/zstack/network/l3/AbstractIpAllocatorStrategy.java @@ -107,7 +107,8 @@ public IpRangeVO call(IpRangeVO arg) { )); } - return l3NwMgr.reserveIp(ipr, msg.getRequiredIp(), msg.isDuplicatedIpAllowed()); + return l3NwMgr.reserveIp(ipr, msg.getRequiredIp(), msg.isDuplicatedIpAllowed(), + msg.getOperationUuid(), msg.getOperationStep()); } protected UsedIpInventory allocateRequiredIpv6(IpAllocateMessage msg) { @@ -138,7 +139,8 @@ public IpRangeVO call(IpRangeVO arg) { )); } - return l3NwMgr.reserveIp(ipr, msg.getRequiredIp(), msg.isDuplicatedIpAllowed()); + return l3NwMgr.reserveIp(ipr, msg.getRequiredIp(), msg.isDuplicatedIpAllowed(), + msg.getOperationUuid(), msg.getOperationStep()); } public static int compareIpv4Range(IpRangeVO r1, IpRangeVO r2) { diff --git a/network/src/main/java/org/zstack/network/l3/AddressPoolIpRangeFactory.java b/network/src/main/java/org/zstack/network/l3/AddressPoolIpRangeFactory.java index aae2711248b..51bb42171f8 100644 --- a/network/src/main/java/org/zstack/network/l3/AddressPoolIpRangeFactory.java +++ b/network/src/main/java/org/zstack/network/l3/AddressPoolIpRangeFactory.java @@ -8,12 +8,16 @@ import org.zstack.header.core.ReturnValueCompletion; import org.zstack.header.message.APICreateMessage; import org.zstack.header.network.l3.*; +import org.zstack.header.network.l2.NetworkCreateContext; import org.zstack.utils.CollectionUtils; import org.zstack.utils.function.ForEachFunction; import java.util.ArrayList; import java.util.List; +import static org.zstack.core.Platform.argerr; +import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_NETWORK_L3_10093; + public class AddressPoolIpRangeFactory implements IpRangeFactory { @Autowired DatabaseFacade dbf; @@ -27,6 +31,22 @@ public IpRangeType getType() { @Override public void createIpRange(List iprs, APICreateMessage msg, ReturnValueCompletion> completion) { + createIpRange(iprs, msg, NetworkCreateContext.api(), completion); + } + + @Override + public void createIpRange(List iprs, APICreateMessage msg, NetworkCreateContext context, + ReturnValueCompletion> completion) { + String resolvedAccountUuid = msg.getSession() == null ? null : msg.getSession().getAccountUuid(); + if (resolvedAccountUuid == null && context != null && context.getExternalRef() != null) { + resolvedAccountUuid = context.getExternalRef().getAccountUuid(); + } + if (resolvedAccountUuid == null) { + completion.fail(argerr(ORG_ZSTACK_NETWORK_L3_10093, + "account uuid is required for address pool creation")); + return; + } + String accountUuid = resolvedAccountUuid; List vos = new ArrayList<>(); for (IpRangeInventory ipr : iprs) { AddressPoolVO vo = new SQLBatchWithReturn() { @@ -42,7 +62,7 @@ protected AddressPoolVO scripts() { vo.setNetmask(ipr.getNetmask()); vo.setStartIp(ipr.getStartIp()); vo.setNetworkCidr(ipr.getNetworkCidr()); - vo.setAccountUuid(msg.getSession().getAccountUuid()); + vo.setAccountUuid(accountUuid); vo.setIpVersion(ipr.getIpVersion()); vo.setAddressMode(ipr.getAddressMode()); vo.setPrefixLen(ipr.getPrefixLen()); @@ -60,7 +80,7 @@ protected AddressPoolVO scripts() { CollectionUtils.safeForEach(pluginRgty.getExtensionList(AfterAddIpRangeExtensionPoint.class), new ForEachFunction() { @Override public void run(AfterAddIpRangeExtensionPoint ext) { - ext.afterAddIpRange(finalIpr, msg.getSystemTags()); + ext.afterAddIpRange(finalIpr, msg.getSystemTags(), context); } }); } diff --git a/network/src/main/java/org/zstack/network/l3/AscDelayRecycleIpAllocatorStrategy.java b/network/src/main/java/org/zstack/network/l3/AscDelayRecycleIpAllocatorStrategy.java index 47337b221e3..71ca46140d3 100755 --- a/network/src/main/java/org/zstack/network/l3/AscDelayRecycleIpAllocatorStrategy.java +++ b/network/src/main/java/org/zstack/network/l3/AscDelayRecycleIpAllocatorStrategy.java @@ -148,7 +148,8 @@ private UsedIpInventory allocateIpByAsc(IpAllocateMessage msg, List r ip = allocateIpByAsc(r, excludeIp); tr = r; if (ip != null) { - UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed()); + UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed(), + msg.getOperationUuid(), msg.getOperationStep()); if (inv != null) { return inv; } diff --git a/network/src/main/java/org/zstack/network/l3/AttachNetworkServiceToL3Msg.java b/network/src/main/java/org/zstack/network/l3/AttachNetworkServiceToL3Msg.java index 24a40b3faf2..e344716d358 100644 --- a/network/src/main/java/org/zstack/network/l3/AttachNetworkServiceToL3Msg.java +++ b/network/src/main/java/org/zstack/network/l3/AttachNetworkServiceToL3Msg.java @@ -1,6 +1,7 @@ package org.zstack.network.l3; import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.network.l2.NetworkCreateContext; import org.zstack.header.network.l3.L3NetworkMessage; import java.util.ArrayList; @@ -16,6 +17,7 @@ public class AttachNetworkServiceToL3Msg extends NeedReplyMessage implements L3N private Map> networkServices; private List apiSystemTags = new ArrayList<>(); + private NetworkCreateContext context; public void setL3NetworkUuid(String l3NetworkUuid) { this.l3NetworkUuid = l3NetworkUuid; @@ -41,4 +43,12 @@ public List getApiSystemTags() { public void setApiSystemTags(List apiSystemTags) { this.apiSystemTags = apiSystemTags; } + + public NetworkCreateContext getContext() { + return context; + } + + public void setContext(NetworkCreateContext context) { + this.context = context; + } } diff --git a/network/src/main/java/org/zstack/network/l3/FirstAvailableIpAllocatorStrategy.java b/network/src/main/java/org/zstack/network/l3/FirstAvailableIpAllocatorStrategy.java index 20a9cffc1fb..7207a229b11 100755 --- a/network/src/main/java/org/zstack/network/l3/FirstAvailableIpAllocatorStrategy.java +++ b/network/src/main/java/org/zstack/network/l3/FirstAvailableIpAllocatorStrategy.java @@ -66,7 +66,8 @@ public UsedIpInventory allocateIp(IpAllocateMessage msg) { return null; } - UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed()); + UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed(), + msg.getOperationUuid(), msg.getOperationStep()); if (inv != null) { return inv; } diff --git a/network/src/main/java/org/zstack/network/l3/FirstAvailableIpv6AllocatorStrategy.java b/network/src/main/java/org/zstack/network/l3/FirstAvailableIpv6AllocatorStrategy.java index 63723dc7073..71bd3b7c659 100755 --- a/network/src/main/java/org/zstack/network/l3/FirstAvailableIpv6AllocatorStrategy.java +++ b/network/src/main/java/org/zstack/network/l3/FirstAvailableIpv6AllocatorStrategy.java @@ -69,7 +69,8 @@ public UsedIpInventory allocateIp(IpAllocateMessage msg) { return null; } - UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed()); + UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed(), + msg.getOperationUuid(), msg.getOperationStep()); if (inv != null) { return inv; } diff --git a/network/src/main/java/org/zstack/network/l3/IpRangeCascadeExtension.java b/network/src/main/java/org/zstack/network/l3/IpRangeCascadeExtension.java index 115dea698ee..977e7659f47 100755 --- a/network/src/main/java/org/zstack/network/l3/IpRangeCascadeExtension.java +++ b/network/src/main/java/org/zstack/network/l3/IpRangeCascadeExtension.java @@ -16,6 +16,7 @@ import org.zstack.header.errorcode.ErrorCodeList; import org.zstack.header.message.MessageReply; import org.zstack.header.network.l3.*; +import org.zstack.network.l2.NetworkDeletionContexts; import org.zstack.utils.CollectionUtils; import org.zstack.utils.Utils; import org.zstack.utils.function.Function; @@ -62,6 +63,10 @@ private void deleteIpRanges(final CascadeAction action,List ip msg.setForceDelete(action.isActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE)); msg.setL3NetworkUuid(iprinv.getL3NetworkUuid()); msg.setIpRangeUuid(iprinv.getUuid()); + String l2NetworkUuid = Q.New(L3NetworkVO.class) + .select(L3NetworkVO_.l2NetworkUuid) + .eq(L3NetworkVO_.uuid, iprinv.getL3NetworkUuid()).findValue(); + msg.setNetworkDeletionContext(NetworkDeletionContexts.get(action, l2NetworkUuid)); bus.makeTargetServiceIdByResourceUuid(msg, L3NetworkConstant.SERVICE_ID, iprinv.getL3NetworkUuid()); msgs.add(msg); } diff --git a/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java b/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java index ae0258de955..ff3af42a9e5 100755 --- a/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java +++ b/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java @@ -30,6 +30,7 @@ import org.zstack.header.core.workflow.*; import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.errorcode.ErrorCodeList; +import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.host.HostVO; import org.zstack.header.host.HostVO_; import org.zstack.header.identity.SharedResourceVO; @@ -40,9 +41,16 @@ import org.zstack.header.network.l2.L2NetworkClusterRefVO_; import org.zstack.header.network.l2.L2NetworkConstant; import org.zstack.header.network.l2.L2NetworkVO; +import org.zstack.header.network.l2.NetworkCreateContext; +import org.zstack.header.network.NetworkConfigLocalContinuation; +import org.zstack.header.network.NetworkConfigMutation; +import org.zstack.header.network.NetworkConfigMutationExtensionPoint; +import org.zstack.header.network.l2.NetworkOperationOrigin; import org.zstack.header.network.l3.*; import org.zstack.header.network.sdncontroller.SdnControllerConstant; import org.zstack.header.network.service.*; +import org.zstack.header.tag.SystemTagVO; +import org.zstack.header.vm.VmNicVO; import org.zstack.identity.AccountManager; import org.zstack.network.service.NetworkServiceManager; import org.zstack.resourceconfig.ResourceConfigFacade; @@ -59,6 +67,7 @@ import org.zstack.utils.network.NetworkUtils; import org.zstack.utils.stopwatch.StopWatch; +import javax.persistence.LockModeType; import javax.persistence.Tuple; import java.math.BigInteger; import java.util.*; @@ -163,7 +172,27 @@ private void handle(APIAddIpRangeMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public void run(SyncTaskChain chain) { - doAddIpRange(msg, chain); + if (msg.getResourceUuid() == null) { + msg.setResourceUuid(Platform.getUuid()); + } + IpRangeInventory ipr = IpRangeInventory.fromMessage(msg); + addIpRange(ipr, msg, NetworkCreateContext.api(), new ReturnValueCompletion(msg) { + @Override + public void success(IpRangeInventory inv) { + APIAddIpRangeEvent evt = new APIAddIpRangeEvent(msg.getId()); + evt.setInventory(inv); + bus.publish(evt); + chain.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + APIAddIpRangeEvent evt = new APIAddIpRangeEvent(msg.getId()); + evt.setError(errorCode); + bus.publish(evt); + chain.next(); + } + }); } @Override @@ -178,38 +207,183 @@ public String getName() { }); } - private void doAddIpRange(APIAddIpRangeMsg msg, SyncTaskChain chain) { - IpRangeInventory ipr = IpRangeInventory.fromMessage(msg); - APIAddIpRangeEvent evt = new APIAddIpRangeEvent(msg.getId()); - ErrorCode errorCode = validateManagementNetworkIpRangeVersionBeforeCreate(ipr); - if (errorCode != null) { - evt.setError(errorCode); - bus.publish(evt); - chain.next(); + private void handle(AddIpRangeMsg msg) { + thdf.chainSubmit(new ChainTask(msg) { + @Override + public void run(SyncTaskChain chain) { + IpRangeInventory ipr = msg.getInventory(); + if (ipr == null || !Objects.equals(msg.getL3NetworkUuid(), ipr.getL3NetworkUuid())) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L3_10083, + "internal IP range must belong to L3 network[uuid:%s]", msg.getL3NetworkUuid())); + chain.next(); + return; + } + + APIAddIpRangeMsg create = new APIAddIpRangeMsg(); + create.setL3NetworkUuid(msg.getL3NetworkUuid()); + create.setName(ipr.getName()); + create.setDescription(ipr.getDescription()); + create.setStartIp(ipr.getStartIp()); + create.setEndIp(ipr.getEndIp()); + create.setNetmask(ipr.getNetmask()); + create.setGateway(ipr.getGateway()); + create.setIpRangeType(ipr.getIpRangeType().toString()); + create.setResourceUuid(ipr.getUuid()); + create.setSystemTags(msg.getSystemTags()); + create.setUserTags(msg.getUserTags()); + addIpRange(ipr, create, msg.getContext(), new ReturnValueCompletion(msg) { + @Override + public void success(IpRangeInventory inv) { + AddIpRangeReply reply = new AddIpRangeReply(); + reply.setInventory(inv); + bus.reply(msg, reply); + chain.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + bus.replyErrorByMessageType(msg, errorCode); + chain.next(); + } + }); + } + + @Override + public String getSyncSignature() { + return syncThreadName; + } + + @Override + public String getName() { + return "add-projected-ip-range"; + } + }); + } + + private void addIpRange(IpRangeInventory ipr, APICreateMessage msg, NetworkCreateContext context, + ReturnValueCompletion completion) { + addIpRanges(Collections.singletonList(ipr), msg, context, + new ReturnValueCompletion>(completion) { + @Override + public void success(List inventories) { + completion.success(inventories.get(0)); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + }); + } + + private void addIpRanges(List iprs, APICreateMessage msg, + NetworkCreateContext context, + ReturnValueCompletion> completion) { + if (context == null) { + completion.fail(Platform.inerr(ORG_ZSTACK_NETWORK_L3_10084, + "network create context is required for internal IP range creation")); return; } + for (IpRangeInventory ipr : iprs) { + ErrorCode errorCode = validateManagementNetworkIpRangeVersionBeforeCreate(ipr); + if (errorCode != null) { + completion.fail(errorCode); + return; + } + } - IpRangeFactory factory = l3NwMgr.getIpRangeFactory(ipr.getIpRangeType()); - factory.createIpRange(Collections.singletonList(ipr), msg, new ReturnValueCompletion>(msg) { + if (context.getOrigin() == NetworkOperationOrigin.API + && iprs.stream().allMatch(ipr -> ipr.getIpRangeType() == IpRangeType.Normal)) { + IpRangeInventory first = iprs.get(0); + List existingRanges = Q.New(NormalIpRangeVO.class) + .eq(IpRangeVO_.l3NetworkUuid, first.getL3NetworkUuid()) + .eq(IpRangeVO_.ipVersion, first.getIpVersion()) + .list(); + List ranges = existingRanges.stream() + .map(existing -> new NetworkConfigMutation.IpRangeTarget( + existing.getUuid(), existing.getStartIp(), existing.getEndIp())) + .collect(Collectors.toList()); + iprs.forEach(ipr -> ranges.add(new NetworkConfigMutation.IpRangeTarget( + ipr.getUuid(), ipr.getStartIp(), ipr.getEndIp()))); + int prefix = first.getIpVersion() == IPv6Constants.IPv6 + ? first.getPrefixLen() + : NetworkUtils.getPrefixLengthFromNetmask(first.getNetmask()); + NetworkConfigMutation mutation = NetworkConfigMutation.ipam( + self.getL2NetworkUuid(), context.getOrigin(), msg.getId(), + msg.getSession() == null ? null : msg.getSession().getAccountUuid(), + first.getL3NetworkUuid(), + first.getIpVersion(), first.getGateway() + "/" + prefix, ranges); + java.util.concurrent.atomic.AtomicReference> created = + new java.util.concurrent.atomic.AtomicReference<>(); + if (mutateNetworkConfig(mutation, + localCompletion -> addIpRanges(iprs, msg, + NetworkCreateContext.cloudCommit(msg.getId()), + new ReturnValueCompletion>(localCompletion) { + @Override + public void success(List inventories) { + created.set(inventories); + localCompletion.success(); + } + + @Override + public void fail(ErrorCode errorCode) { + localCompletion.fail(errorCode); + } + }), created, completion)) { + return; + } + } + + IpRangeFactory factory = l3NwMgr.getIpRangeFactory(iprs.get(0).getIpRangeType()); + factory.createIpRange(iprs, msg, context, new ReturnValueCompletion>(completion) { @Override public void success(List invs) { - IpRangeInventory inv = invs.get(0); - tagMgr.createTagsFromAPICreateMessage(msg, inv.getL3NetworkUuid(), L3NetworkVO.class.getSimpleName()); - setIpRangeSharedResource(msg.getL3NetworkUuid(), inv.getUuid()); - evt.setInventory(inv); - bus.publish(evt); - chain.next(); + for (IpRangeInventory inv : invs) { + tagMgr.createTagsFromAPICreateMessage( + msg, inv.getL3NetworkUuid(), L3NetworkVO.class.getSimpleName()); + setIpRangeSharedResource(inv.getL3NetworkUuid(), inv.getUuid()); + } + completion.success(invs); } @Override public void fail(ErrorCode errorCode) { - evt.setError(errorCode); - bus.publish(evt); - chain.next(); + completion.fail(errorCode); } }); } + private boolean mutateNetworkConfig(NetworkConfigMutation mutation, + NetworkConfigLocalContinuation continuation, + java.util.concurrent.atomic.AtomicReference result, + ReturnValueCompletion completion) { + List providers = pluginRgty + .getExtensionList(NetworkConfigMutationExtensionPoint.class).stream() + .filter(provider -> provider.supports(mutation)) + .collect(Collectors.toList()); + if (providers.isEmpty()) { + return false; + } + if (providers.size() != 1) { + completion.fail(errf.stringToInternalError(String.format( + "network mutation[%s] matched[%s] providers", + mutation.getKind(), providers.size()))); + return true; + } + providers.get(0).mutate(mutation, continuation, new Completion(completion) { + @Override + public void success() { + completion.success(result.get()); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + }); + return true; + } + private ErrorCode validateManagementNetworkIpRangeVersionBeforeCreate(IpRangeInventory ipr) { if (self.getCategory() != L3NetworkCategory.System) { return null; @@ -234,7 +408,17 @@ private String getIpVersionName(int ipVersion) { } private void handleLocalMessage(Message msg) { - if (msg instanceof AllocateIpMsg) { + if (msg instanceof AddIpRangeMsg) { + handle((AddIpRangeMsg) msg); + } else if (msg instanceof UpdateProjectedIpRangeMsg) { + handle((UpdateProjectedIpRangeMsg) msg); + } else if (msg instanceof DeleteProjectedIpRangeMsg) { + handle((DeleteProjectedIpRangeMsg) msg); + } else if (msg instanceof UpdateProjectedDnsMsg) { + handle((UpdateProjectedDnsMsg) msg); + } else if (msg instanceof ConvertL3NetworkTypeMsg) { + handle((ConvertL3NetworkTypeMsg) msg); + } else if (msg instanceof AllocateIpMsg) { handle((AllocateIpMsg)msg); } else if (msg instanceof ReturnIpMsg) { handle((ReturnIpMsg)msg); @@ -251,6 +435,269 @@ private void handleLocalMessage(Message msg) { } } + private void handle(ConvertL3NetworkTypeMsg msg) { + thdf.chainSubmit(new ChainTask(msg) { + @Override + public void run(SyncTaskChain chain) { + L3NetworkCategory targetCategory; + try { + targetCategory = L3NetworkCategory.valueOf(msg.getTargetCategory()); + } catch (RuntimeException e) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L3_10094, + "invalid target category for projected L3 network conversion")); + chain.next(); + return; + } + if (msg.getContext() == null || !msg.getContext().isProjection() + || msg.getExpectedSourceType() == null + || msg.getExpectedSourceCategory() == null + || msg.getTargetType() == null + || !L3NetworkType.hasType(msg.getTargetType()) + || targetCategory == L3NetworkCategory.System + || msg.getManagedSystemTagPrefix() == null + || msg.getManagedSystemTagPrefix().isEmpty() + || (msg.getTargetSystemTag() != null + && (!msg.getTargetSystemTag().startsWith(msg.getManagedSystemTagPrefix()) + || msg.getTargetSystemTag().length() > 128))) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L3_10095, + "projected L3 network conversion has an invalid typed contract")); + chain.next(); + return; + } + + String failure; + try { + failure = new SQLBatchWithReturn() { + @Override + protected String scripts() { + L3NetworkVO current = databaseFacade.getEntityManager().find( + L3NetworkVO.class, msg.getL3NetworkUuid(), LockModeType.PESSIMISTIC_WRITE); + if (current == null) { + return String.format("L3 network[uuid:%s] no longer exists", + msg.getL3NetworkUuid()); + } + + List managedTags = databaseFacade.getEntityManager() + .createQuery("select tag from SystemTagVO tag where tag.resourceUuid = :resourceUuid" + + " and tag.resourceType = :resourceType", SystemTagVO.class) + .setParameter("resourceUuid", current.getUuid()) + .setParameter("resourceType", L3NetworkVO.class.getSimpleName()) + .getResultList().stream() + .filter(tag -> tag.getTag().startsWith(msg.getManagedSystemTagPrefix())) + .collect(Collectors.toList()); + boolean targetTagMatches = msg.getTargetSystemTag() == null + ? managedTags.isEmpty() + : managedTags.size() == 1 + && msg.getTargetSystemTag().equals(managedTags.get(0).getTag()); + if (msg.getTargetType().equals(current.getType()) + && targetCategory == current.getCategory() + && targetTagMatches) { + return null; + } + if (!msg.getExpectedSourceType().equals(current.getType()) + || !msg.getExpectedSourceCategory().equals(current.getCategory().toString())) { + return String.format("L3 network[uuid:%s] changed from expected source[%s/%s] to[%s/%s]", + current.getUuid(), msg.getExpectedSourceType(), + msg.getExpectedSourceCategory(), current.getType(), current.getCategory()); + } + + Long usedIpCount = databaseFacade.getEntityManager() + .createQuery("select count(ip) from UsedIpVO ip where ip.l3NetworkUuid = :l3Uuid", + Long.class) + .setParameter("l3Uuid", current.getUuid()) + .getSingleResult(); + Long vmNicCount = databaseFacade.getEntityManager() + .createQuery("select count(nic) from VmNicVO nic where nic.l3NetworkUuid = :l3Uuid", + Long.class) + .setParameter("l3Uuid", current.getUuid()) + .getSingleResult(); + if (usedIpCount > 0 || vmNicCount > 0) { + return String.format("L3 network[uuid:%s] has active dependencies[usedIp:%d, vmNic:%d]", + current.getUuid(), usedIpCount, vmNicCount); + } + + current.setType(msg.getTargetType()); + current.setCategory(targetCategory); + managedTags.forEach(tag -> tagMgr.deleteSystemTag(tag.getUuid())); + if (msg.getTargetSystemTag() != null) { + tagMgr.createNonInherentSystemTag(current.getUuid(), + msg.getTargetSystemTag(), L3NetworkVO.class.getSimpleName()); + } + databaseFacade.getEntityManager().flush(); + return null; + } + }.execute(); + } catch (RuntimeException e) { + bus.replyErrorByMessageType(msg, Platform.inerr(ORG_ZSTACK_NETWORK_L3_10096, + "failed to atomically convert projected L3 network[uuid:%s]: %s", + msg.getL3NetworkUuid(), e.getMessage())); + chain.next(); + return; + } + if (failure != null) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L3_10097, failure)); + chain.next(); + return; + } + + self = dbf.findByUuid(msg.getL3NetworkUuid(), L3NetworkVO.class); + bus.reply(msg, new MessageReply()); + chain.next(); + } + + @Override + public String getSyncSignature() { + return syncThreadName; + } + + @Override + public String getName() { + return String.format("convert-projected-l3-network-%s", msg.getL3NetworkUuid()); + } + }); + } + + private void handle(UpdateProjectedIpRangeMsg msg) { + thdf.chainSubmit(new ChainTask(msg) { + @Override + public void run(SyncTaskChain chain) { + IpRangeVO range = dbf.findByUuid(msg.getRangeUuid(), IpRangeVO.class); + if (msg.getContext() == null || !msg.getContext().isProjection() + || !IpRangeVO.class.getSimpleName().equals(msg.getExpectedSourceType()) + || !Objects.equals(self.getUuid(), msg.getL3NetworkUuid()) + || range == null || !Objects.equals(range.getL3NetworkUuid(), msg.getL3NetworkUuid())) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L3_10087, + "IP range projection update requires the expected range on L3 network[uuid:%s]", + msg.getL3NetworkUuid())); + chain.next(); + return; + } + + boolean valid; + try { + valid = NetworkUtils.isInRange(msg.getStartIp(), msg.getStartIp(), msg.getEndIp()); + } catch (RuntimeException e) { + valid = false; + } + if (!valid) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L3_10088, + "projected IP range[%s, %s] is invalid", msg.getStartIp(), msg.getEndIp())); + chain.next(); + return; + } + + List rangeUsedIps = Q.New(UsedIpVO.class) + .eq(UsedIpVO_.ipRangeUuid, range.getUuid()).list(); + UsedIpVO outside = rangeUsedIps.stream() + .filter(ip -> !NetworkUtils.isInRange(ip.getIp(), msg.getStartIp(), msg.getEndIp())) + .findFirst().orElse(null); + if (outside != null) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L3_10089, + "used IP[%s] is outside projected IP range[uuid:%s]", + outside.getIp(), range.getUuid())); + chain.next(); + return; + } + + range.setStartIp(msg.getStartIp()); + range.setEndIp(msg.getEndIp()); + range.setGateway(msg.getGateway()); + range.setNetmask(msg.getNetmask()); + dbf.update(range); + bus.reply(msg, new MessageReply()); + chain.next(); + } + + @Override + public String getSyncSignature() { + return syncThreadName; + } + + @Override + public String getName() { + return "update-projected-ip-range"; + } + }); + } + + private void handle(DeleteProjectedIpRangeMsg msg) { + thdf.chainSubmit(new ChainTask(msg) { + @Override + public void run(SyncTaskChain chain) { + if (msg.getContext() == null || !msg.getContext().isProjection() + || !IpRangeVO.class.getSimpleName().equals(msg.getExpectedSourceType()) + || !Objects.equals(self.getUuid(), msg.getL3NetworkUuid())) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L3_10098, + "IP range projection delete requires L3 network[uuid:%s] ownership", + msg.getL3NetworkUuid())); + chain.next(); + return; + } + + IpRangeVO range = dbf.findByUuid(msg.getRangeUuid(), IpRangeVO.class); + if (range == null) { + bus.reply(msg, new MessageReply()); + chain.next(); + return; + } + if (!Objects.equals(range.getL3NetworkUuid(), msg.getL3NetworkUuid())) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L3_10099, + "IP range[uuid:%s] does not belong to L3 network[uuid:%s]", + range.getUuid(), msg.getL3NetworkUuid())); + chain.next(); + return; + } + + List alternatives = Q.New(IpRangeVO.class) + .eq(IpRangeVO_.l3NetworkUuid, msg.getL3NetworkUuid()) + .notEq(IpRangeVO_.uuid, range.getUuid()).list(); + List usedIps = Q.New(UsedIpVO.class) + .eq(UsedIpVO_.ipRangeUuid, range.getUuid()).list(); + Map replacements = new LinkedHashMap<>(); + for (UsedIpVO usedIp : usedIps) { + IpRangeVO replacement = alternatives.stream() + .filter(candidate -> Objects.equals(candidate.getIpVersion(), usedIp.getIpVersion())) + .filter(candidate -> NetworkUtils.isInRange( + usedIp.getIp(), candidate.getStartIp(), candidate.getEndIp())) + .findFirst().orElse(null); + if (replacement == null) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L3_10100, + "used IP[%s] prevents deleting projected IP range[uuid:%s]", + usedIp.getIp(), range.getUuid())); + chain.next(); + return; + } + replacements.put(usedIp, replacement); + } + + new SQLBatch() { + @Override + protected void scripts() { + replacements.forEach((usedIp, replacement) -> + usedIp.setIpRangeUuid(replacement.getUuid())); + if (!usedIps.isEmpty()) { + dbf.updateCollection(usedIps); + } + dbf.remove(range); + IpRangeHelper.updateL3NetworkIpversion(range); + } + }.execute(); + bus.reply(msg, new MessageReply()); + chain.next(); + } + + @Override + public String getSyncSignature() { + return syncThreadName; + } + + @Override + public String getName() { + return "delete-projected-ip-range"; + } + }); + } + private void handle(CheckIpAvailabilityMsg msg) { CheckIpAvailabilityReply reply = checkIpAvailability(msg); bus.reply(msg, reply); @@ -296,13 +743,13 @@ public void run(SyncTaskChain chainTask) { final IpRangeInventory inv = IpRangeInventory.valueOf(iprvo); for (IpRangeDeletionExtensionPoint ext : exts) { - ext.preDeleteIpRange(inv); + ext.preDeleteIpRange(inv, msg.getNetworkDeletionContext()); } CollectionUtils.safeForEach(exts, new ForEachFunction() { @Override public void run(IpRangeDeletionExtensionPoint arg) { - arg.beforeDeleteIpRange(inv); + arg.beforeDeleteIpRange(inv, msg.getNetworkDeletionContext()); } }); @@ -319,13 +766,14 @@ public void run(FlowTrigger trigger, Map data) { return; } - SdnControllerDisableDHCPMsg msg = new SdnControllerDisableDHCPMsg(); - msg.setL3NetworkUuid(self.getUuid()); - msg.setIpVersion(iprvo.getIpVersion()); - msg.setSdnControllerUuid(sdnControllerUuid); - msg.setCheckIpRange(true); - bus.makeTargetServiceIdByResourceUuid(msg, SdnControllerConstant.SERVICE_ID, sdnControllerUuid); - bus.send(msg, new CloudBusCallBack(trigger) { + SdnControllerDisableDHCPMsg dhcpMsg = new SdnControllerDisableDHCPMsg(); + dhcpMsg.setL3NetworkUuid(self.getUuid()); + dhcpMsg.setIpVersion(iprvo.getIpVersion()); + dhcpMsg.setSdnControllerUuid(sdnControllerUuid); + dhcpMsg.setCheckIpRange(true); + dhcpMsg.setNetworkDeletionContext(msg.getNetworkDeletionContext()); + bus.makeTargetServiceIdByResourceUuid(dhcpMsg, SdnControllerConstant.SERVICE_ID, sdnControllerUuid); + bus.send(dhcpMsg, new CloudBusCallBack(trigger) { @Override public void run(MessageReply reply) { if (!reply.isSuccess()) { @@ -348,23 +796,18 @@ public void run(FlowTrigger trigger, Map data) { } boolean isLastIpRange = isLastNormalIpRangeOfVersion(msg.getIpRangeUuid(), inv.getIpVersion()); - if (isLastIpRange) { - sdnL3.deleteIpRange(inv, new Completion(trigger) { - @Override - public void success() { - trigger.next(); - } + sdnL3.deleteIpRange(inv, isLastIpRange, msg.getNetworkDeletionContext(), + new Completion(trigger) { + @Override + public void success() { + trigger.next(); + } - @Override - public void fail(ErrorCode errorCode) { - logger.debug(String.format("delete ip range [uuid:%s] failed because %s", - inv.getUuid(), errorCode.getDetails())); - trigger.next(); - } - }); - } else { - trigger.next(); - } + @Override + public void fail(ErrorCode errorCode) { + trigger.fail(errorCode); + } + }); } }).then(new NoRollbackFlow() { String __name__ = "remove-db"; @@ -378,7 +821,7 @@ public void run(FlowTrigger trigger, Map data) { CollectionUtils.safeForEach(exts, new ForEachFunction() { @Override public void run(IpRangeDeletionExtensionPoint arg) { - arg.afterDeleteIpRange(inv); + arg.afterDeleteIpRange(inv, msg.getNetworkDeletionContext()); } }); trigger.next(); @@ -390,7 +833,7 @@ public void run(IpRangeDeletionExtensionPoint arg) { public void run (FlowTrigger trigger, Map data){ CollectionUtils.safeForEach( pluginRgty.getExtensionList(AfterDeleteIpRangeExtensionPoint.class), - ext -> ext.afterDeleteIpRange(inv) + ext -> ext.afterDeleteIpRange(inv, msg.getNetworkDeletionContext()) ); trigger.next(); } @@ -445,7 +888,8 @@ public void run(FlowTrigger trigger, Map data) { return; } - controllerL3.deleteL3Network(L3NetworkInventory.valueOf(l3NetworkVO), new Completion(trigger) { + controllerL3.deleteL3Network(L3NetworkInventory.valueOf(l3NetworkVO), + msg.getNetworkDeletionContext(), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -453,7 +897,12 @@ public void success() { @Override public void fail(ErrorCode errorCode) { - trigger.next();//ignore error + if (msg.getNetworkDeletionContext() != null && + msg.getNetworkDeletionContext().isWholeL2SegmentDelete()) { + trigger.fail(errorCode); + } else { + trigger.next(); + } } }); } @@ -487,9 +936,9 @@ public void run(FlowTrigger trigger, Map data) { } L3NetworkInventory inv = L3NetworkInventory.valueOf(self); - extpEmitter.beforeDelete(inv); + extpEmitter.beforeDelete(inv, msg.getNetworkDeletionContext()); deleteHook(); - extpEmitter.afterDelete(inv); + extpEmitter.afterDelete(inv, msg.getNetworkDeletionContext()); trigger.next(); } @@ -1077,22 +1526,61 @@ private void handle(APIDetachNetworkServiceFromL3NetworkMsg msg) { return; } - detachNetworkServiceFromL3NetworkMsg(l3VO, refs, new Completion(msg) { + ReturnValueCompletion completion = new ReturnValueCompletion(msg) { + @Override + public void success(L3NetworkInventory inventory) { + evt.setInventory(inventory); + bus.publish(evt); + } + + @Override + public void fail(ErrorCode errorCode) { + evt.setError(errorCode); + bus.publish(evt); + } + }; + if (refs.stream().anyMatch(ref -> NetworkServiceType.DHCP.toString() + .equals(ref.getNetworkServiceType()))) { + NetworkConfigMutation mutation = NetworkConfigMutation.dhcp( + self.getL2NetworkUuid(), NetworkOperationOrigin.API, msg.getId(), + msg.getSession() == null ? null : msg.getSession().getAccountUuid(), + msg.getL3NetworkUuid(), false, msg.getSystemTags()); + java.util.concurrent.atomic.AtomicReference result = + new java.util.concurrent.atomic.AtomicReference<>(); + if (mutateNetworkConfig(mutation, + localCompletion -> detachNetworkServicesLocally(l3VO, refs, + new ReturnValueCompletion(localCompletion) { + @Override + public void success(L3NetworkInventory inventory) { + result.set(inventory); + localCompletion.success(); + } + + @Override + public void fail(ErrorCode errorCode) { + localCompletion.fail(errorCode); + } + }), result, completion)) { + return; + } + } + detachNetworkServicesLocally(l3VO, refs, completion); + } + + private void detachNetworkServicesLocally(L3NetworkVO l3VO, + List refs, + ReturnValueCompletion completion) { + detachNetworkServiceFromL3NetworkMsg(l3VO, refs, new Completion(completion) { @Override public void success() { dbf.removeCollection(refs, NetworkServiceL3NetworkRefVO.class); - logger.debug(String.format("successfully detached network service provider[uuid:%s]", JSONObjectUtil.dumpPretty(refs))); self = dbf.reload(self); - evt.setInventory(L3NetworkInventory.valueOf(self)); - bus.publish(evt); + completion.success(L3NetworkInventory.valueOf(self)); } @Override public void fail(ErrorCode errorCode) { - logger.debug(String.format("detached network service provider[uuid:%s] failed:%s", JSONObjectUtil.dumpPretty(refs), errorCode.getDetails())); - self = dbf.reload(self); - evt.setInventory(L3NetworkInventory.valueOf(self)); - bus.publish(evt); + completion.fail(errorCode); } }); } @@ -1224,15 +1712,13 @@ private void handle(APIUpdateL3NetworkMsg msg) { private void handle(APIAddIpRangeByNetworkCidrMsg msg) { List iprs = IpRangeInventory.fromMessage(msg); + assignStableIpRangeUuids(iprs, msg.getResourceUuid()); APIAddIpRangeByNetworkCidrEvent evt = new APIAddIpRangeByNetworkCidrEvent(msg.getId()); - IpRangeFactory factory = l3NwMgr.getIpRangeFactory(iprs.get(0).getIpRangeType()); - factory.createIpRange(iprs, msg, new ReturnValueCompletion>(msg) { + addIpRanges(iprs, msg, NetworkCreateContext.api(), new ReturnValueCompletion>(msg) { @Override public void success(List invs) { IpRangeInventory inv = invs.get(0); - tagMgr.createTagsFromAPICreateMessage(msg, inv.getL3NetworkUuid(), L3NetworkVO.class.getSimpleName()); - setIpRangeSharedResource(msg.getL3NetworkUuid(), inv.getUuid()); evt.setInventory(inv); evt.setInventories(invs); bus.publish(evt); @@ -1248,15 +1734,15 @@ public void fail(ErrorCode errorCode) { private void handle(APIAddIpv6RangeByNetworkCidrMsg msg) { IpRangeInventory ipr = IpRangeInventory.fromMessage(msg); + if (ipr.getUuid() == null) { + ipr.setUuid(Platform.getUuid()); + } APIAddIpRangeByNetworkCidrEvent evt = new APIAddIpRangeByNetworkCidrEvent(msg.getId()); - IpRangeFactory factory = l3NwMgr.getIpRangeFactory(ipr.getIpRangeType()); - factory.createIpRange(Collections.singletonList(ipr), msg, new ReturnValueCompletion>(msg) { + addIpRanges(Collections.singletonList(ipr), msg, NetworkCreateContext.api(), new ReturnValueCompletion>(msg) { @Override public void success(List invs) { IpRangeInventory inv = invs.get(0); - tagMgr.createTagsFromAPICreateMessage(msg, inv.getL3NetworkUuid(), L3NetworkVO.class.getSimpleName()); - setIpRangeSharedResource(msg.getL3NetworkUuid(), inv.getUuid()); evt.setInventory(inv); bus.publish(evt); } @@ -1271,15 +1757,15 @@ public void fail(ErrorCode errorCode) { private void handle(APIAddIpv6RangeMsg msg) { IpRangeInventory ipr = IpRangeInventory.fromMessage(msg); + if (ipr.getUuid() == null) { + ipr.setUuid(Platform.getUuid()); + } APIAddIpRangeEvent evt = new APIAddIpRangeEvent(msg.getId()); - IpRangeFactory factory = l3NwMgr.getIpRangeFactory(ipr.getIpRangeType()); - factory.createIpRange(Collections.singletonList(ipr), msg, new ReturnValueCompletion>(msg) { + addIpRanges(Collections.singletonList(ipr), msg, NetworkCreateContext.api(), new ReturnValueCompletion>(msg) { @Override public void success(List invs) { IpRangeInventory inv = invs.get(0); - tagMgr.createTagsFromAPICreateMessage(msg, inv.getL3NetworkUuid(), L3NetworkVO.class.getSimpleName()); - setIpRangeSharedResource(msg.getL3NetworkUuid(), inv.getUuid()); evt.setInventory(inv); bus.publish(evt); } @@ -1292,6 +1778,16 @@ public void fail(ErrorCode errorCode) { }); } + private static void assignStableIpRangeUuids(List iprs, + String requestedUuid) { + for (int i = 0; i < iprs.size(); i++) { + if (iprs.get(i).getUuid() == null) { + iprs.get(i).setUuid(i == 0 && requestedUuid != null + ? requestedUuid : Platform.getUuid()); + } + } + } + private void handle(APIChangeL3NetworkStateMsg msg) { if (L3NetworkStateEvent.enable.toString().equals(msg.getStateEvent())) { @@ -1311,6 +1807,52 @@ private void handle(APIChangeL3NetworkStateMsg msg) { private void handle(final APIRemoveDnsFromL3NetworkMsg msg) { final APIRemoveDnsFromL3NetworkEvent evt = new APIRemoveDnsFromL3NetworkEvent(msg.getId()); + ReturnValueCompletion completion = new ReturnValueCompletion(msg) { + @Override + public void success(L3NetworkInventory inventory) { + evt.setInventory(inventory); + bus.publish(evt); + } + + @Override + public void fail(ErrorCode errorCode) { + evt.setError(errorCode); + bus.publish(evt); + } + }; + List desiredDns = Q.New(L3NetworkDnsVO.class) + .select(L3NetworkDnsVO_.dns) + .eq(L3NetworkDnsVO_.l3NetworkUuid, msg.getL3NetworkUuid()) + .notEq(L3NetworkDnsVO_.dns, msg.getDns()).listValues(); + NetworkConfigMutation mutation = NetworkConfigMutation.dhcpDns( + self.getL2NetworkUuid(), NetworkOperationOrigin.API, msg.getId(), + msg.getSession() == null ? null : msg.getSession().getAccountUuid(), + msg.getL3NetworkUuid(), NetworkUtils.isIpv4Address(msg.getDns()) + ? IPv6Constants.IPv4 : IPv6Constants.IPv6, desiredDns); + java.util.concurrent.atomic.AtomicReference result = + new java.util.concurrent.atomic.AtomicReference<>(); + if (mutateNetworkConfig(mutation, + localCompletion -> removeDns(msg, true, + new ReturnValueCompletion(localCompletion) { + @Override + public void success(L3NetworkInventory inventory) { + result.set(inventory); + localCompletion.success(); + } + + @Override + public void fail(ErrorCode errorCode) { + localCompletion.fail(errorCode); + } + }), result, completion)) { + return; + } + removeDns(msg, false, completion); + } + + private void removeDns(final APIRemoveDnsFromL3NetworkMsg msg, + boolean remoteCommitted, + ReturnValueCompletion completion) { SdnControllerDhcp sdnDhcp = l3NwMgr.getSdnControllerDhcp(msg.getL3NetworkUuid()); @@ -1339,6 +1881,11 @@ public void run(FlowTrigger trigger, Map data) { flow(new NoRollbackFlow() { String __name__ = "remove-dns-from-sdn"; + @Override + public boolean skip(Map data) { + return remoteCommitted; + } + @Override public void run(FlowTrigger trigger, Map data) { String sdnControllerUuid = L3NetworkHelper.getSdnControllerUuidFromL3Uuid(self.getUuid()); @@ -1401,16 +1948,14 @@ public void run(MessageReply reply) { done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { - evt.setInventory(L3NetworkInventory.valueOf(dbf.reload(self))); - bus.publish(evt); + completion.success(L3NetworkInventory.valueOf(dbf.reload(self))); } }); error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { - evt.setError(errCode); - bus.publish(evt); + completion.fail(errCode); } }); } @@ -1419,6 +1964,54 @@ public void handle(ErrorCode errCode, Map data) { private void handle(final APIAddDnsToL3NetworkMsg msg) { final APIAddDnsToL3NetworkEvent evt = new APIAddDnsToL3NetworkEvent(msg.getId()); + ReturnValueCompletion completion = new ReturnValueCompletion(msg) { + @Override + public void success(L3NetworkInventory inventory) { + evt.setInventory(inventory); + bus.publish(evt); + } + + @Override + public void fail(ErrorCode errorCode) { + evt.setError(errorCode); + bus.publish(evt); + } + }; + List desiredDns = Q.New(L3NetworkDnsVO.class) + .select(L3NetworkDnsVO_.dns) + .eq(L3NetworkDnsVO_.l3NetworkUuid, msg.getL3NetworkUuid()).listValues(); + if (!desiredDns.contains(msg.getDns())) { + desiredDns.add(msg.getDns()); + } + NetworkConfigMutation mutation = NetworkConfigMutation.dhcpDns( + self.getL2NetworkUuid(), NetworkOperationOrigin.API, msg.getId(), + msg.getSession() == null ? null : msg.getSession().getAccountUuid(), + msg.getL3NetworkUuid(), NetworkUtils.isIpv4Address(msg.getDns()) + ? IPv6Constants.IPv4 : IPv6Constants.IPv6, desiredDns); + java.util.concurrent.atomic.AtomicReference result = + new java.util.concurrent.atomic.AtomicReference<>(); + if (mutateNetworkConfig(mutation, + localCompletion -> addDns(msg, true, + new ReturnValueCompletion(localCompletion) { + @Override + public void success(L3NetworkInventory inventory) { + result.set(inventory); + localCompletion.success(); + } + + @Override + public void fail(ErrorCode errorCode) { + localCompletion.fail(errorCode); + } + }), result, completion)) { + return; + } + addDns(msg, false, completion); + } + + private void addDns(final APIAddDnsToL3NetworkMsg msg, + boolean remoteCommitted, + ReturnValueCompletion completion) { SdnControllerDhcp sdnDhcp = l3NwMgr.getSdnControllerDhcp(msg.getL3NetworkUuid()); @@ -1455,6 +2048,11 @@ public void rollback(FlowRollback trigger, Map data) { flow(new NoRollbackFlow() { String __name__ = "apply-to-sdn"; + @Override + public boolean skip(Map data) { + return remoteCommitted; + } + @Override public void run(FlowTrigger trigger, Map data) { String sdnControllerUuid = L3NetworkHelper.getSdnControllerUuidFromL3Uuid(self.getUuid()); @@ -1518,24 +2116,22 @@ public void run(MessageReply reply) { @Override public void handle(Map data) { self = dbf.reload(self); - evt.setInventory(L3NetworkInventory.valueOf(self)); logger.debug(String.format("successfully added dns[%s] to L3Network[uuid:%s, name:%s]", msg.getDns(), self.getUuid(), self.getName())); - bus.publish(evt); + completion.success(L3NetworkInventory.valueOf(self)); } }); error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { - evt.setError(errCode); - bus.publish(evt); + completion.fail(errCode); } }); } }).start(); } - private void handle(final AttachNetworkServiceToL3Msg msg) { + private void handle(final AttachNetworkServiceToL3Msg msg) { MessageReply reply = new MessageReply(); L3NetworkVO l3VO = dbf.findByUuid(msg.getL3NetworkUuid(), L3NetworkVO.class); List networkServiceProviderVOS = Q.New(NetworkServiceProviderVO.class).list(); @@ -1556,7 +2152,20 @@ private void handle(final AttachNetworkServiceToL3Msg msg) { logger.debug(String.format("successfully attached network service provider[uuid:%s] to l3network[uuid:%s, name:%s] with services%s", e.getKey(), self.getUuid(), self.getName(), e.getValue())); } - dbf.persistCollection(refVOS); + try { + new SQLBatch() { + @Override + protected void scripts() { + for (NetworkServiceL3NetworkRefVO ref : refVOS) { + persist(ref); + } + } + }.execute(); + } catch (OperationFailureException e) { + reply.setError(e.getErrorCode()); + bus.reply(msg, reply); + return; + } new While<>(refVOS).each((ref, wcomp) -> { String provideType = providerUuidTypeMap.get(ref.getNetworkServiceProviderUuid()); @@ -1572,6 +2181,12 @@ private void handle(final AttachNetworkServiceToL3Msg msg) { return; } + if (msg.getContext() != null && msg.getContext().isRemoteWriteSuppressed() + && nsType == NetworkServiceType.DHCP) { + wcomp.done(); + return; + } + nsMgr.enableNetworkService(l3VO, ptype, nsType, msg.getApiSystemTags(), new Completion(wcomp) { @Override public void success() { @@ -1610,6 +2225,68 @@ public void fail(ErrorCode errorCode) { }); } + private void handle(UpdateProjectedDnsMsg msg) { + thdf.chainSubmit(new ChainTask(msg) { + @Override + public void run(SyncTaskChain chain) { + if (msg.getContext() == null || !msg.getContext().isProjection()) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L3_10085, + "DNS projection requires a ZNS projection context")); + chain.next(); + return; + } + + LinkedHashSet desired = new LinkedHashSet<>( + msg.getDns() == null ? Collections.emptyList() : msg.getDns()); + String invalid = desired.stream().filter(dns -> !NetworkUtils.isIpAddress(dns)).findFirst().orElse(null); + if (invalid != null) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L3_10086, + "DNS[%s] is not an IP address", invalid)); + chain.next(); + return; + } + + new SQLBatch() { + @Override + protected void scripts() { + List existing = Q.New(L3NetworkDnsVO.class) + .eq(L3NetworkDnsVO_.l3NetworkUuid, msg.getL3NetworkUuid()).list(); + Set current = existing.stream().map(L3NetworkDnsVO::getDns) + .collect(Collectors.toSet()); + List obsolete = existing.stream() + .filter(vo -> !desired.contains(vo.getDns())).collect(Collectors.toList()); + if (!obsolete.isEmpty()) { + dbf.removeCollection(obsolete, L3NetworkDnsVO.class); + } + List additions = desired.stream() + .filter(dns -> !current.contains(dns)) + .map(dns -> { + L3NetworkDnsVO vo = new L3NetworkDnsVO(); + vo.setL3NetworkUuid(msg.getL3NetworkUuid()); + vo.setDns(dns); + return vo; + }).collect(Collectors.toList()); + if (!additions.isEmpty()) { + dbf.persistCollection(additions); + } + } + }.execute(); + bus.reply(msg, new MessageReply()); + chain.next(); + } + + @Override + public String getSyncSignature() { + return syncThreadName; + } + + @Override + public String getName() { + return "update-projected-dns"; + } + }); + } + private void handle(APIAttachNetworkServiceToL3NetworkMsg msg) { APIAttachNetworkServiceToL3NetworkEvent evt = new APIAttachNetworkServiceToL3NetworkEvent(msg.getId()); if (msg.isSkipAttach()) { @@ -1619,28 +2296,82 @@ private void handle(APIAttachNetworkServiceToL3NetworkMsg msg) { return; } - AttachNetworkServiceToL3Msg amsg = new AttachNetworkServiceToL3Msg(); - amsg.setL3NetworkUuid(msg.getL3NetworkUuid()); - amsg.setNetworkServices(msg.getNetworkServices()); - if (msg.getSystemTags() != null) { - amsg.setApiSystemTags(msg.getSystemTags()); + ReturnValueCompletion completion = new ReturnValueCompletion(msg) { + @Override + public void success(L3NetworkInventory inventory) { + evt.setInventory(inventory); + bus.publish(evt); + } + + @Override + public void fail(ErrorCode errorCode) { + evt.setError(errorCode); + bus.publish(evt); + } + }; + if (containsDhcp(msg.getNetworkServices())) { + NetworkConfigMutation mutation = NetworkConfigMutation.dhcp( + self.getL2NetworkUuid(), NetworkOperationOrigin.API, msg.getId(), + msg.getSession() == null ? null : msg.getSession().getAccountUuid(), + msg.getL3NetworkUuid(), true, msg.getSystemTags()); + java.util.concurrent.atomic.AtomicReference result = + new java.util.concurrent.atomic.AtomicReference<>(); + if (mutateNetworkConfig(mutation, + localCompletion -> attachNetworkServices(msg, + NetworkCreateContext.cloudCommit(msg.getId()), + new ReturnValueCompletion(localCompletion) { + @Override + public void success(L3NetworkInventory inventory) { + result.set(inventory); + localCompletion.success(); + } + + @Override + public void fail(ErrorCode errorCode) { + localCompletion.fail(errorCode); + } + }), result, completion)) { + return; + } + } + attachNetworkServices(msg, NetworkCreateContext.api(), completion); + } + + private boolean containsDhcp(Map> networkServices) { + if (networkServices == null || networkServices.isEmpty()) { + return false; } + List services = networkServices.values().stream() + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + return services.stream().anyMatch(NetworkServiceType.DHCP.toString()::equals); + } - bus.makeTargetServiceIdByResourceUuid(amsg, L3NetworkConstant.SERVICE_ID, msg.getL3NetworkUuid()); - bus.send(amsg, new CloudBusCallBack(msg) { + private void attachNetworkServices(APIAttachNetworkServiceToL3NetworkMsg msg, + NetworkCreateContext context, + ReturnValueCompletion completion) { + AttachNetworkServiceToL3Msg attach = new AttachNetworkServiceToL3Msg(); + attach.setL3NetworkUuid(msg.getL3NetworkUuid()); + attach.setNetworkServices(msg.getNetworkServices()); + attach.setContext(context); + if (msg.getSystemTags() != null) { + attach.setApiSystemTags(msg.getSystemTags()); + } + bus.makeTargetServiceIdByResourceUuid(attach, L3NetworkConstant.SERVICE_ID, + msg.getL3NetworkUuid()); + bus.send(attach, new CloudBusCallBack(completion) { @Override public void run(MessageReply reply) { - if (reply.isSuccess()) { - self = dbf.reload(self); - evt.setInventory(L3NetworkInventory.valueOf(self)); - bus.publish(evt); - } else { - evt.setError(reply.getError()); - bus.publish(evt); + if (!reply.isSuccess()) { + completion.fail(reply.getError()); + return; } + self = dbf.reload(self); + completion.success(L3NetworkInventory.valueOf(self)); } }); - } + } private void doDeleteIpRange(APIDeleteIpRangeMsg msg, Completion completion) { @@ -1714,6 +2445,68 @@ public void handle(ErrorCode errCode, Map data) { }).start(); } + private void deleteIpRangeWithMutation(APIDeleteIpRangeMsg msg, Completion completion) { + IpRangeVO deleting = dbf.findByUuid(msg.getUuid(), IpRangeVO.class); + if (!(deleting instanceof NormalIpRangeVO)) { + doDeleteIpRange(msg, completion); + return; + } + List remaining = Q.New(NormalIpRangeVO.class) + .eq(IpRangeVO_.l3NetworkUuid, deleting.getL3NetworkUuid()) + .eq(IpRangeVO_.ipVersion, deleting.getIpVersion()) + .notEq(IpRangeVO_.uuid, deleting.getUuid()) + .list(); + NetworkConfigMutation mutation; + if (remaining.isEmpty()) { + mutation = NetworkConfigMutation.deleteIpam( + self.getL2NetworkUuid(), NetworkOperationOrigin.API, msg.getId(), + msg.getSession() == null ? null : msg.getSession().getAccountUuid(), + deleting.getL3NetworkUuid(), deleting.getIpVersion()); + } else { + List targets = remaining.stream() + .map(range -> new NetworkConfigMutation.IpRangeTarget( + range.getUuid(), range.getStartIp(), range.getEndIp())) + .collect(Collectors.toList()); + NormalIpRangeVO first = remaining.get(0); + int prefix = first.getIpVersion() == IPv6Constants.IPv6 + ? first.getPrefixLen() + : NetworkUtils.getPrefixLengthFromNetmask(first.getNetmask()); + mutation = NetworkConfigMutation.ipam( + self.getL2NetworkUuid(), NetworkOperationOrigin.API, msg.getId(), + msg.getSession() == null ? null : msg.getSession().getAccountUuid(), + deleting.getL3NetworkUuid(), deleting.getIpVersion(), + first.getGateway() + "/" + prefix, targets); + } + java.util.concurrent.atomic.AtomicReference deleted = + new java.util.concurrent.atomic.AtomicReference<>(false); + if (mutateNetworkConfig(mutation, + localCompletion -> doDeleteIpRange(msg, new Completion(localCompletion) { + @Override + public void success() { + deleted.set(true); + localCompletion.success(); + } + + @Override + public void fail(ErrorCode errorCode) { + localCompletion.fail(errorCode); + } + }), deleted, new ReturnValueCompletion(completion) { + @Override + public void success(Boolean ignored) { + completion.success(); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + })) { + return; + } + doDeleteIpRange(msg, completion); + } + private void syncManagementServiceTypeWhileDelete(ServiceTypeExtensionPoint ext, L2NetworkVO l2NetworkVO, List hostUuids) { String l2NetworkType = l2NetworkVO.getType(); switch (l2NetworkType) { @@ -1744,7 +2537,7 @@ public String getName() { @Override public void run(SyncTaskChain chain) { - doDeleteIpRange(msg, new Completion(chain) { + deleteIpRangeWithMutation(msg, new Completion(chain) { @Override public void success() { bus.publish(evt); diff --git a/network/src/main/java/org/zstack/network/l3/L3NetworkCascadeExtension.java b/network/src/main/java/org/zstack/network/l3/L3NetworkCascadeExtension.java index 1d8a5359327..bde03163291 100755 --- a/network/src/main/java/org/zstack/network/l3/L3NetworkCascadeExtension.java +++ b/network/src/main/java/org/zstack/network/l3/L3NetworkCascadeExtension.java @@ -16,11 +16,13 @@ import org.zstack.header.message.MessageReply; import org.zstack.header.network.l2.L2NetworkInventory; import org.zstack.header.network.l2.L2NetworkVO; +import org.zstack.header.network.l2.NetworkDeletionContext; import org.zstack.header.network.l3.*; import org.zstack.utils.CollectionUtils; import org.zstack.utils.Utils; import org.zstack.utils.function.Function; import org.zstack.utils.logging.CLogger; +import org.zstack.network.l2.NetworkDeletionContexts; import javax.persistence.TypedQuery; import java.util.ArrayList; @@ -87,6 +89,7 @@ private void handleDeletion(final CascadeAction action, final Completion complet L3NetworkDeletionMsg msg = new L3NetworkDeletionMsg(); msg.setL3NetworkUuid(l3inv.getUuid()); msg.setForceDelete(action.isActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE)); + msg.setNetworkDeletionContext(NetworkDeletionContexts.get(action, l3inv.getL2NetworkUuid())); bus.makeTargetServiceIdByResourceUuid(msg, L3NetworkConstant.SERVICE_ID, l3inv.getUuid()); msgs.add(msg); } @@ -125,7 +128,8 @@ private void handleDeletionCheck(CascadeAction action, Completion completion) { try { for (L3NetworkInventory prinv : l3invs) { - extpEmitter.preDelete(prinv); + extpEmitter.preDelete(prinv, + NetworkDeletionContexts.get(action, prinv.getL2NetworkUuid())); } completion.success(); diff --git a/network/src/main/java/org/zstack/network/l3/L3NetworkExtensionPointEmitter.java b/network/src/main/java/org/zstack/network/l3/L3NetworkExtensionPointEmitter.java index 34dc9f35674..9ebb53cb13c 100755 --- a/network/src/main/java/org/zstack/network/l3/L3NetworkExtensionPointEmitter.java +++ b/network/src/main/java/org/zstack/network/l3/L3NetworkExtensionPointEmitter.java @@ -7,6 +7,7 @@ import org.zstack.header.network.l3.L3NetworkDeleteExtensionPoint; import org.zstack.header.network.l3.L3NetworkException; import org.zstack.header.network.l3.L3NetworkInventory; +import org.zstack.header.network.l2.NetworkDeletionContext; import org.zstack.utils.CollectionUtils; import org.zstack.utils.Utils; import org.zstack.utils.function.ForEachFunction; @@ -32,9 +33,14 @@ public void run(L3NetworkCreateExtensionPoint arg) { } public void preDelete(L3NetworkInventory inv) throws L3NetworkException { + preDelete(inv, null); + } + + public void preDelete(L3NetworkInventory inv, NetworkDeletionContext context) + throws L3NetworkException { for (L3NetworkDeleteExtensionPoint ext : deleteExtensions) { try { - ext.preDeleteL3Network(inv); + ext.preDeleteL3Network(inv, context); } catch (L3NetworkException l3e) { throw l3e; } catch (Exception e) { @@ -45,19 +51,27 @@ public void preDelete(L3NetworkInventory inv) throws L3NetworkException { } public void beforeDelete(final L3NetworkInventory inv) { + beforeDelete(inv, null); + } + + public void beforeDelete(final L3NetworkInventory inv, NetworkDeletionContext context) { CollectionUtils.safeForEach(deleteExtensions, new ForEachFunction() { @Override public void run(L3NetworkDeleteExtensionPoint arg) { - arg.beforeDeleteL3Network(inv); + arg.beforeDeleteL3Network(inv, context); } }); } public void afterDelete(final L3NetworkInventory inv) { + afterDelete(inv, null); + } + + public void afterDelete(final L3NetworkInventory inv, NetworkDeletionContext context) { CollectionUtils.safeForEach(deleteExtensions, new ForEachFunction() { @Override public void run(L3NetworkDeleteExtensionPoint arg) { - arg.afterDeleteL3Network(inv); + arg.afterDeleteL3Network(inv, context); } }); } diff --git a/network/src/main/java/org/zstack/network/l3/L3NetworkManager.java b/network/src/main/java/org/zstack/network/l3/L3NetworkManager.java index eebbb11b747..9177bbadacb 100755 --- a/network/src/main/java/org/zstack/network/l3/L3NetworkManager.java +++ b/network/src/main/java/org/zstack/network/l3/L3NetworkManager.java @@ -14,6 +14,8 @@ public interface L3NetworkManager { UsedIpInventory reserveIp(IpRangeVO ipRange, String ip); UsedIpInventory reserveIp(IpRangeVO ipRange, String ip, boolean allowDuplicatedAddress); + UsedIpInventory reserveIp(IpRangeVO ipRange, String ip, boolean allowDuplicatedAddress, + String operationUuid, String operationStep); boolean isIpRangeFull(IpRangeVO vo); diff --git a/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java b/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java index 45fecdc0aa1..757a8ab5932 100755 --- a/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java +++ b/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java @@ -32,6 +32,10 @@ import org.zstack.header.message.Message; import org.zstack.header.message.MessageReply; import org.zstack.header.network.l2.*; +import org.zstack.header.network.NetworkDeleteGuardExtensionPoint; +import org.zstack.header.network.NetworkConfigLocalContinuation; +import org.zstack.header.network.NetworkConfigMutation; +import org.zstack.header.network.NetworkConfigMutationExtensionPoint; import org.zstack.header.network.l3.*; import org.zstack.header.network.l3.datatypes.IpCapacityData; import org.zstack.header.network.service.GetSdnControllerExtensionPoint; @@ -116,13 +120,45 @@ public void handleMessage(Message msg) { } private void handleLocalMessage(Message msg) { - if (msg instanceof L3NetworkMessage) { + if (msg instanceof CreateL3NetworkMsg) { + handle((CreateL3NetworkMsg) msg); + } else if (msg instanceof L3NetworkMessage) { passThrough((L3NetworkMessage) msg); } else { bus.dealWithUnknownMessage(msg); } } + private void handle(CreateL3NetworkMsg msg) { + APICreateL3NetworkMsg api = new APICreateL3NetworkMsg(); + api.setName(msg.getName()); + api.setDescription(msg.getDescription()); + api.setType(msg.getType()); + api.setL2NetworkUuid(msg.getL2NetworkUuid()); + api.setCategory(msg.getCategory()); + api.setIpVersion(msg.getIpVersion()); + api.setSystem(msg.isSystem()); + api.setDnsDomain(msg.getDnsDomain()); + api.setEnableIPAM(msg.getEnableIPAM()); + api.setResourceUuid(msg.getResourceUuid()); + api.setSession(msg.getSession()); + api.setSystemTags(msg.getSystemTags()); + handle(api, msg.getContext() == null ? NetworkCreateContext.api() : msg.getContext(), + new ReturnValueCompletion(msg) { + @Override + public void success(L3NetworkInventory inventory) { + CreateL3NetworkReply reply = new CreateL3NetworkReply(); + reply.setInventory(inventory); + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + bus.replyErrorByMessageType(msg, errorCode); + } + }); + } + private void handleApiMessage(APIMessage msg) { if (msg instanceof APICreateL3NetworkMsg) { @@ -526,7 +562,68 @@ private void syncManagementServiceTypeWhileCreate(ServiceTypeExtensionPoint ext, } private void handle(APICreateL3NetworkMsg msg) { - APICreateL3NetworkEvent evt = new APICreateL3NetworkEvent(msg.getId()); + handle(msg, NetworkCreateContext.api(), new ReturnValueCompletion(msg) { + @Override + public void success(L3NetworkInventory inventory) { + APICreateL3NetworkEvent evt = new APICreateL3NetworkEvent(msg.getId()); + evt.setInventory(inventory); + bus.publish(evt); + } + + @Override + public void fail(ErrorCode errorCode) { + APICreateL3NetworkEvent evt = new APICreateL3NetworkEvent(msg.getId()); + evt.setError(errorCode); + bus.publish(evt); + } + }); + } + + private void handle(APICreateL3NetworkMsg msg, NetworkCreateContext context) { + handle(msg, context, new ReturnValueCompletion(msg) { + @Override + public void success(L3NetworkInventory inventory) { + logger.debug(String.format("Successfully created L3Network[name:%s, uuid:%s]", msg.getName(), inventory.getUuid())); + } + + @Override + public void fail(ErrorCode errorCode) { + logger.warn(String.format("Failed to create L3Network[name:%s]: %s", msg.getName(), errorCode)); + } + }); + } + + private void handle(APICreateL3NetworkMsg msg, NetworkCreateContext context, + ReturnValueCompletion completion) { + + if (context.getOrigin() == NetworkOperationOrigin.API) { + if (msg.getResourceUuid() == null) { + msg.setResourceUuid(Platform.getUuid()); + } + NetworkConfigMutation mutation = NetworkConfigMutation.l3Create( + msg.getL2NetworkUuid(), context.getOrigin(), msg.getId(), + msg.getSession() == null ? null : msg.getSession().getAccountUuid(), + msg.getResourceUuid(), msg.getType(), msg.getSystemTags()); + java.util.concurrent.atomic.AtomicReference created = + new java.util.concurrent.atomic.AtomicReference<>(); + if (mutateNetworkConfig(mutation, + localCompletion -> handle(msg, + NetworkCreateContext.cloudCommit(msg.getId()), + new ReturnValueCompletion(localCompletion) { + @Override + public void success(L3NetworkInventory inventory) { + created.set(inventory); + localCompletion.success(); + } + + @Override + public void fail(ErrorCode errorCode) { + localCompletion.fail(errorCode); + } + }), created, completion)) { + return; + } + } L2NetworkVO l2Vo = Q.New(L2NetworkVO.class).eq(L2NetworkVO_.uuid, msg.getL2NetworkUuid()).find(); assert l2Vo.getZoneUuid() != null; @@ -569,7 +666,7 @@ public void run(FlowTrigger trigger, Map data) { return; } - controllerL3.createL3Network(L3NetworkInventory.valueOf(vo), msg.getSystemTags(), + controllerL3.createL3Network(L3NetworkInventory.valueOf(vo), msg.getSystemTags(), context, new Completion(trigger) { @Override public void success() { @@ -591,8 +688,14 @@ public void run(FlowTrigger trigger, Map data) { L3NetworkInventory inv = new SQLBatchWithReturn() { @Override protected L3NetworkInventory scripts() { - vo.setAccountUuid(msg.getSession().getAccountUuid()); - L3NetworkInventory inv = factory.createL3Network(vo, msg); + String accountUuid = msg.getSession() == null && context.getExternalRef() != null + ? context.getExternalRef().getAccountUuid() + : msg.getSession() == null ? null : msg.getSession().getAccountUuid(); + if (accountUuid == null) { + throw new CloudRuntimeException("account uuid is required for l3 create"); + } + vo.setAccountUuid(accountUuid); + L3NetworkInventory inv = factory.createL3Network(vo, msg, context); tagMgr.createTagsFromAPICreateMessage(msg, vo.getUuid(), L3NetworkVO.class.getSimpleName()); return inv; } @@ -612,26 +715,55 @@ protected L3NetworkInventory scripts() { } extpEmitter.afterCreate(inv); - evt.setInventory(inv); + data.put("L3NetworkInventory", inv); trigger.next(); } }).error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { - evt.setError(errCode); - bus.publish(evt); + completion.fail(errCode); } }).done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { - logger.debug(String.format("Successfully created L3Network[name:%s, uuid:%s]", vo.getName(), vo.getUuid())); - bus.publish(evt); + completion.success((L3NetworkInventory) data.get("L3NetworkInventory")); } }).start(); } + private boolean mutateNetworkConfig(NetworkConfigMutation mutation, + NetworkConfigLocalContinuation continuation, + java.util.concurrent.atomic.AtomicReference result, + ReturnValueCompletion completion) { + List providers = pluginRgty + .getExtensionList(NetworkConfigMutationExtensionPoint.class).stream() + .filter(provider -> provider.supports(mutation)) + .collect(java.util.stream.Collectors.toList()); + if (providers.isEmpty()) { + return false; + } + if (providers.size() != 1) { + completion.fail(errf.stringToInternalError(String.format( + "network mutation[%s] matched[%s] providers", + mutation.getKind(), providers.size()))); + return true; + } + providers.get(0).mutate(mutation, continuation, new Completion(completion) { + @Override + public void success() { + completion.success(result.get()); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + }); + return true; + } + @Override public String getId() { return bus.makeLocalServiceId(L3NetworkConstant.SERVICE_ID); @@ -817,13 +949,30 @@ public UsedIpInventory reserveIp(IpRangeVO ipRange, String ip) { @Override public UsedIpInventory reserveIp(IpRangeVO ipRange, String ip, boolean allowDuplicatedAddress) { - if (NetworkUtils.isIpv4Address(ip)) { - return reserveIpv4(ipRange, ip, allowDuplicatedAddress); - } else if (IPv6NetworkUtils.isIpv6Address(ip)) { - return reserveIpv6(ipRange, ip, allowDuplicatedAddress); - } else { - return null; - } + return reserveIp(ipRange, ip, allowDuplicatedAddress, null, null); + } + + @Override + public UsedIpInventory reserveIp(IpRangeVO ipRange, String ip, boolean allowDuplicatedAddress, + String operationUuid, String operationStep) { + return new SQLBatchWithReturn() { + @Override + protected UsedIpInventory scripts() { + for (NetworkDeleteGuardExtensionPoint extension : + pluginRgty.getExtensionList(NetworkDeleteGuardExtensionPoint.class)) { + ErrorCode errorCode = extension.checkL3NetworkWithLock(ipRange.getL3NetworkUuid()); + if (errorCode != null) { + throw new CloudRuntimeException(errorCode.getDetails()); + } + } + if (NetworkUtils.isIpv4Address(ip)) { + return reserveIpv4(ipRange, ip, allowDuplicatedAddress); + } else if (IPv6NetworkUtils.isIpv6Address(ip)) { + return reserveIpv6(ipRange, ip, allowDuplicatedAddress); + } + return null; + } + }.execute(); } @Override diff --git a/network/src/main/java/org/zstack/network/l3/NormalIpRangeFactory.java b/network/src/main/java/org/zstack/network/l3/NormalIpRangeFactory.java index c052d0a1d69..35c20060aa8 100644 --- a/network/src/main/java/org/zstack/network/l3/NormalIpRangeFactory.java +++ b/network/src/main/java/org/zstack/network/l3/NormalIpRangeFactory.java @@ -16,6 +16,7 @@ import org.zstack.header.message.APICreateMessage; import org.zstack.header.message.MessageReply; import org.zstack.header.network.l3.*; +import org.zstack.header.network.l2.NetworkCreateContext; import org.zstack.header.network.sdncontroller.SdnControllerConstant; import org.zstack.header.network.service.SdnControllerDhcp; import org.zstack.utils.CollectionUtils; @@ -29,6 +30,9 @@ import java.util.List; import java.util.Map; +import static org.zstack.core.Platform.argerr; +import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_NETWORK_L3_10092; + public class NormalIpRangeFactory implements IpRangeFactory { @Autowired DatabaseFacade dbf; @@ -47,6 +51,22 @@ public IpRangeType getType() { @Override public void createIpRange(List iprs, APICreateMessage msg, ReturnValueCompletion> completion) { + createIpRange(iprs, msg, NetworkCreateContext.api(), completion); + } + + @Override + public void createIpRange(List iprs, APICreateMessage msg, NetworkCreateContext context, + ReturnValueCompletion> completion) { + String resolvedAccountUuid = msg.getSession() == null ? null : msg.getSession().getAccountUuid(); + if (resolvedAccountUuid == null && context != null && context.getExternalRef() != null) { + resolvedAccountUuid = context.getExternalRef().getAccountUuid(); + } + if (resolvedAccountUuid == null) { + completion.fail(argerr(ORG_ZSTACK_NETWORK_L3_10092, + "account uuid is required for normal IP range creation")); + return; + } + String accountUuid = resolvedAccountUuid; FlowChain chain = new SimpleFlowChain(); chain.setName(String.format("add-iprange-to-l3-%s", iprs.get(0).getL3NetworkUuid())); chain.then(new Flow() { @@ -59,7 +79,7 @@ public void run(FlowTrigger trigger, Map data) { @Override protected NormalIpRangeVO scripts() { NormalIpRangeVO vo = (NormalIpRangeVO) IpRangeHelper - .fromIpRangeInventory(ipr, msg.getSession().getAccountUuid()); + .fromIpRangeInventory(ipr, accountUuid); dbf.getEntityManager().persist(vo); dbf.getEntityManager().flush(); dbf.getEntityManager().refresh(vo); @@ -97,7 +117,7 @@ protected NormalIpRangeVO scripts() { CollectionUtils.safeForEach(pluginRgty.getExtensionList(AfterAddIpRangeExtensionPoint.class), new ForEachFunction() { @Override public void run(AfterAddIpRangeExtensionPoint ext) { - ext.afterAddIpRange(IpRangeInventory.valueOf(vo), msg.getSystemTags()); + ext.afterAddIpRange(IpRangeInventory.valueOf(vo), msg.getSystemTags(), context); } }); @@ -119,6 +139,10 @@ public void rollback(FlowRollback trigger, Map data) { @Override public void run(FlowTrigger trigger, Map data) { + if (context.isRemoteWriteSuppressed()) { + trigger.next(); + return; + } String sdnControllerUuid = L3NetworkHelper.getSdnControllerUuidFromL3Uuid(iprs.get(0).getL3NetworkUuid()); if (sdnControllerUuid == null) { trigger.next(); @@ -146,6 +170,10 @@ public void run(MessageReply reply) { @Override public void run(FlowTrigger trigger, Map data) { + if (context.isRemoteWriteSuppressed()) { + trigger.next(); + return; + } L3NetworkVO l3vo = dbf.findByUuid(iprs.get(0).getL3NetworkUuid(), L3NetworkVO.class); SdnControllerL3 sdnL3 = l3Mgr.getSdnControllerL3(l3vo.getL2NetworkUuid()); if (sdnL3 == null) { diff --git a/network/src/main/java/org/zstack/network/l3/RandomIpAllocatorStrategy.java b/network/src/main/java/org/zstack/network/l3/RandomIpAllocatorStrategy.java index e4b8d1562f6..d402a22a6d9 100755 --- a/network/src/main/java/org/zstack/network/l3/RandomIpAllocatorStrategy.java +++ b/network/src/main/java/org/zstack/network/l3/RandomIpAllocatorStrategy.java @@ -51,7 +51,8 @@ public UsedIpInventory allocateIp(IpAllocateMessage msg) { return null; } - UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed()); + UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed(), + msg.getOperationUuid(), msg.getOperationStep()); if (inv != null) { return inv; } diff --git a/network/src/main/java/org/zstack/network/l3/RandomIpv6AllocatorStrategy.java b/network/src/main/java/org/zstack/network/l3/RandomIpv6AllocatorStrategy.java index b9ea4737ec7..2b3ead694d8 100755 --- a/network/src/main/java/org/zstack/network/l3/RandomIpv6AllocatorStrategy.java +++ b/network/src/main/java/org/zstack/network/l3/RandomIpv6AllocatorStrategy.java @@ -50,7 +50,8 @@ public UsedIpInventory allocateIp(IpAllocateMessage msg) { return null; } - UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed()); + UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed(), + msg.getOperationUuid(), msg.getOperationStep()); if (inv != null) { return inv; } diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnController.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnController.java index 57072cbaa57..e480fed38d9 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnController.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnController.java @@ -3,6 +3,7 @@ import org.zstack.header.core.Completion; import org.zstack.header.network.sdncontroller.*; import org.zstack.sdnController.header.*; +import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_SDNCONTROLLER_10035; public interface SdnController { @@ -27,4 +28,10 @@ public interface SdnController { default void removeHost(SdnControllerRemoveHostMsg msg, Completion completion) {completion.success();}; default void changeHost(SdnControllerHostRefVO oldRef, SdnControllerHostRefVO newRef, Completion completion) {completion.success();}; + + default void pullResources(PullSdnControllerMsg msg, Completion completion) { + completion.fail(org.zstack.core.Platform.operr( + ORG_ZSTACK_SDNCONTROLLER_10035, + "Resource pull is not supported by sdn controller[%s]", msg.getSdnControllerUuid())); + } } diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerApiInterceptor.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerApiInterceptor.java index 97840150a1e..a44a2b9d163 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerApiInterceptor.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerApiInterceptor.java @@ -22,6 +22,7 @@ import org.zstack.network.securitygroup.*; import org.zstack.sdnController.header.*; import org.zstack.utils.Utils; +import org.zstack.utils.StringDSL; import org.zstack.utils.logging.CLogger; import org.zstack.utils.network.NetworkUtils; @@ -29,6 +30,7 @@ import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedHashSet; import java.util.Map; import static org.zstack.core.Platform.argerr; @@ -60,6 +62,7 @@ public List getMessageClassToIntercept() { ret.add(APISetVmNicSecurityGroupMsg.class); ret.add(APIAddSecurityGroupRuleMsg.class); ret.add(APIPullSdnControllerTenantMsg.class); + ret.add(APIPullSdnControllerMsg.class); return ret; } @@ -85,6 +88,8 @@ public APIMessage intercept(APIMessage msg) throws ApiMessageInterceptionExcepti validate((APIAddSecurityGroupRuleMsg) msg); } else if (msg instanceof APIPullSdnControllerTenantMsg) { validate((APIPullSdnControllerTenantMsg) msg); + } else if (msg instanceof APIPullSdnControllerMsg) { + validate((APIPullSdnControllerMsg) msg); } else if (msg instanceof APIChangeSdnControllerMsg) { validate((APIChangeSdnControllerMsg) msg); } @@ -267,6 +272,42 @@ private void validate(APIPullSdnControllerTenantMsg msg) { } } + private void validate(APIPullSdnControllerMsg msg) { + if (!Arrays.asList("Segment", "TenantRouter").contains(msg.getResourceType())) { + throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_SDNCONTROLLER_10036, + "Unsupported resource type[%s]", msg.getResourceType())); + } + try { + msg.setResourceUuids(normalizeResourceUuids(msg.getResourceUuids())); + } catch (IllegalArgumentException e) { + throw new ApiMessageInterceptionException(argerr( + ORG_ZSTACK_SDNCONTROLLER_10037, e.getMessage())); + } + if (msg.getResourceUuids() != null && msg.getResourceUuids().size() > 100) { + throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_SDNCONTROLLER_10038, + "At most 100 resource uuids can be pulled in one request")); + } + if (dbf.findByUuid(msg.getSdnControllerUuid(), SdnControllerVO.class) == null) { + throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_SDNCONTROLLER_10039, + "SDN controller[uuid:%s] not found", msg.getSdnControllerUuid())); + } + } + + static List normalizeResourceUuids(List resourceUuids) { + if (resourceUuids == null) { + return null; + } + LinkedHashSet normalized = new LinkedHashSet<>(); + for (String resourceUuid : resourceUuids) { + if (!StringDSL.isUuid(resourceUuid) && !StringDSL.isZStackUuid(resourceUuid)) { + throw new IllegalArgumentException(String.format( + "Invalid resource uuid[%s]", resourceUuid)); + } + normalized.add(resourceUuid); + } + return new ArrayList<>(normalized); + } + private void validateVlanRanges(List ranges) { List sdnVlanRanges = new ArrayList<>(); for (String range : ranges) { diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerBase.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerBase.java index 848c962b2e3..c256f2c6947 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerBase.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerBase.java @@ -8,6 +8,7 @@ import org.zstack.core.Platform; import org.zstack.core.asyncbatch.While; import org.zstack.core.cascade.CascadeConstant; +import org.zstack.core.cascade.CascadeAction; import org.zstack.core.cascade.CascadeFacade; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.CloudBusCallBack; @@ -120,12 +121,16 @@ public void handleMessage(SdnControllerMessage msg) { handle((APISdnControllerChangeHostMsg) msg); } else if (msg instanceof APIPullSdnControllerTenantMsg) { handle((APIPullSdnControllerTenantMsg) msg); + } else if (msg instanceof APIPullSdnControllerMsg) { + handle((APIPullSdnControllerMsg) msg); } else if (msg instanceof APIChangeSdnControllerMsg) { handle((APIChangeSdnControllerMsg) msg); } else if (msg instanceof SdnControllerRemoveHostMsg) { handle((SdnControllerRemoveHostMsg) msg); } else if (msg instanceof PullSdnControllerTenantMsg) { handle((PullSdnControllerTenantMsg) msg); + } else if (msg instanceof PullSdnControllerMsg) { + handle((PullSdnControllerMsg) msg); } else if (msg instanceof ReconnectSdnControllerMsg) { handle((ReconnectSdnControllerMsg) msg); } else if (msg instanceof SyncSdnControllerDataMsg) { @@ -948,13 +953,15 @@ private void handle(APIRemoveSdnControllerMsg msg) { final String issuer = SdnControllerVO.class.getSimpleName(); SdnControllerVO vo = dbf.findByUuid(msg.getUuid(), SdnControllerVO.class); final List ctx = asList(SdnControllerInventory.valueOf(vo)); + final CascadeAction cascadeAction = new CascadeAction().setRootIssuer(issuer) + .setRootIssuerContext(ctx).setParentIssuer(issuer).setParentIssuerContext(ctx); FlowChain chain = FlowChainBuilder.newSimpleFlowChain(); chain.setName(String.format("delete-sdn-controller-%s-name-%s", msg.getUuid(), vo.getName())); if (msg.getDeletionMode() == APIDeleteMessage.DeletionMode.Permissive) { chain.then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_CHECK_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_CHECK_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -969,7 +976,7 @@ public void fail(ErrorCode errorCode) { }).then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_DELETE_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_DELETE_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -986,7 +993,7 @@ public void fail(ErrorCode errorCode) { chain.then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_FORCE_DELETE_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -1063,6 +1070,15 @@ public void fail(ErrorCode errorCode) { }); } + private void handle(APIPullSdnControllerMsg amsg) { + APIPullSdnControllerEvent event = new APIPullSdnControllerEvent(amsg.getId()); + PullSdnControllerMsg msg = PullSdnControllerMsg.fromApi(amsg); + pullResources(msg, new Completion(msg) { + @Override public void success() { bus.publish(event); } + @Override public void fail(ErrorCode errorCode) { event.setError(errorCode); bus.publish(event); } + }); + } + private void handle(PullSdnControllerTenantMsg msg) { PullSdnControllerTenantReply reply = new PullSdnControllerTenantReply(); @@ -1086,6 +1102,18 @@ public void fail(ErrorCode errorCode) { }); } + private void handle(PullSdnControllerMsg msg) { + PullSdnControllerReply reply = new PullSdnControllerReply(); + pullResources(msg, new Completion(msg) { + @Override public void success() { bus.reply(msg, reply); } + @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } + }); + } + + private void pullResources(PullSdnControllerMsg msg, Completion completion) { + getSdnController().pullResources(msg, completion); + } + private void pullSdnControllerTenant(PullSdnControllerTenantMsg msg, Completion completion) { // Only H3C VCFC V2 controllers support this operation, already validated in interceptor // But confirm again here to ensure type safety diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerL2.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerL2.java index 4a63f3c5c2b..fb348ef4b23 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerL2.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerL2.java @@ -1,9 +1,12 @@ package org.zstack.sdnController; import org.zstack.header.core.Completion; +import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.host.HostInventory; import org.zstack.header.network.l2.APICreateL2NetworkMsg; import org.zstack.header.network.l2.L2NetworkInventory; +import org.zstack.header.network.l2.NetworkCreateContext; +import org.zstack.header.network.l2.NetworkDeletionContext; import org.zstack.header.network.l3.IpRangeInventory; import org.zstack.header.network.l3.L3NetworkInventory; import org.zstack.header.vm.VmNicInventory; @@ -20,6 +23,9 @@ public interface SdnControllerL2 { void preCreateVxlanNetwork(L2VxlanNetworkInventory vxlan, List systemTags, Completion completion); void createL2Network(L2NetworkInventory inv, APICreateL2NetworkMsg msg, Completion completion); + default void createL2Network(L2NetworkInventory inv, APICreateL2NetworkMsg msg, NetworkCreateContext context, Completion completion) { + createL2Network(inv, msg, completion); + } void postCreateVxlanNetwork(L2VxlanNetworkInventory vxlan, List systemTags, Completion completion); void preAttachL2NetworkToCluster(L2VxlanNetworkInventory vxlan, List systemTags, Completion completion); @@ -30,6 +36,34 @@ public interface SdnControllerL2 { void deleteSdnController(SdnControllerDeletionMsg msg, SdnControllerInventory sdn, Completion completion); void detachL2NetworkFromCluster(L2VxlanNetworkInventory vxlan, List clusterUuids, Completion completion); void deleteL2Network(L2NetworkInventory inv, Completion completion); + default boolean requiresConfirmedDelete() { return false; } + default void deleteL2Network(L2NetworkInventory inv, String operationUuid, Completion completion) { + deleteL2Network(inv, completion); + } + default void deleteL2Network(L2NetworkInventory inv, NetworkDeletionContext context, + Completion completion) { + deleteL2Network(inv, context == null ? null : context.getOperationUuid(), completion); + } + default ErrorCode beginConfirmedDelete(L2NetworkInventory inv) { return null; } + default ErrorCode beginConfirmedDelete(L2NetworkInventory inv, NetworkDeletionContext context) { + return beginConfirmedDelete(inv); + } + default ErrorCode checkConfirmedDelete(L2NetworkInventory inv) { return null; } + default ErrorCode checkConfirmedDelete(L2NetworkInventory inv, NetworkDeletionContext context) { + return checkConfirmedDelete(inv); + } + default ErrorCode completeConfirmedDelete(L2NetworkInventory inv) { return null; } + default ErrorCode completeConfirmedDelete(L2NetworkInventory inv, NetworkDeletionContext context) { + return completeConfirmedDelete(inv); + } + default ErrorCode cancelConfirmedDelete(L2NetworkInventory inv) { return null; } + default ErrorCode cancelConfirmedDelete(L2NetworkInventory inv, NetworkDeletionContext context) { + return cancelConfirmedDelete(inv); + } + default void deleteConfirmedLocalMetadata(L2NetworkInventory inv) { } + default void deleteConfirmedLocalMetadata(L2NetworkInventory inv, NetworkDeletionContext context) { + deleteConfirmedLocalMetadata(inv); + } List getVniRange(SdnControllerInventory controller); List getVlanRange(SdnControllerInventory controller); diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java index e832f146738..d2c116681b9 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java @@ -47,7 +47,7 @@ import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.*; public class SdnControllerManagerImpl extends AbstractService implements SdnControllerManager, - L2NetworkCreateExtensionPoint, L2NetworkDeleteExtensionPoint, + L2NetworkCreateExtensionPoint, L2NetworkDeleteExtensionPoint, L2DeleteConfirmExtensionPoint, SecurityGroupGetSdnBackendExtensionPoint, AfterAddIpRangeExtensionPoint, IpRangeDeletionExtensionPoint, GetSdnControllerExtensionPoint, AfterAllocateSdnNicExtensionPoint { @@ -292,6 +292,12 @@ public void beforeCreateL2Network(APICreateL2NetworkMsg msg) throws NetworkExcep @Override public void postCreateL2Network(L2NetworkInventory l2Network, APICreateL2NetworkMsg msg, Completion completion) { + postCreateL2Network(l2Network, msg, NetworkCreateContext.api(), completion); + } + + @Override + public void postCreateL2Network(L2NetworkInventory l2Network, APICreateL2NetworkMsg msg, + NetworkCreateContext context, Completion completion) { VSwitchType vSwitchType = VSwitchType.valueOf(l2Network.getvSwitchType()); if (vSwitchType.getSdnControllerType() == null) { completion.success(); @@ -326,7 +332,7 @@ public void postCreateL2Network(L2NetworkInventory l2Network, APICreateL2Network SdnControllerFactory factory = getSdnControllerFactory(sdnControllerVO.getVendorType()); SdnControllerL2 controller = factory.getSdnControllerL2(sdnControllerVO); - controller.createL2Network(l2Network, msg, completion); + controller.createL2Network(l2Network, msg, context, completion); } @Override @@ -346,16 +352,7 @@ public void beforeDeleteL2Network(L2NetworkInventory inventory) { @Override public void deleteL2Network(L2NetworkInventory inv, NoErrorCompletion completion) { - VSwitchType vSwitchType = VSwitchType.valueOf(inv.getvSwitchType()); - if (vSwitchType.getSdnControllerType() == null) { - //hardware vxlan will go this path - completion.done(); - return; - } - - /* vswitch type: OvnDpdk will go here */ - SdnControllerFactory factory = getSdnControllerFactory(vSwitchType.getSdnControllerType()); - SdnControllerL2 controllerL2 = factory.getSdnControllerL2(inv.getUuid()); + SdnControllerL2 controllerL2 = findSdnControllerL2(inv); if (controllerL2 == null) { logger.warn(String.format("can not found sdn controller for l2 network[uuid:%s, vswitchType:%s]", inv.getUuid(), inv.getvSwitchType())); @@ -378,6 +375,129 @@ public void fail(ErrorCode errorCode) { }); } + @Override + public boolean requiresConfirmedDelete(L2NetworkInventory inv) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inv); + return controllerL2 != null && controllerL2.requiresConfirmedDelete(); + } + + @Override + public void deleteL2Network(L2NetworkInventory inv, String operationUuid, Completion completion) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inv); + if (controllerL2 == null) { + completion.fail(confirmedDeleteControllerMissing(inv)); + return; + } + controllerL2.deleteL2Network(inv, operationUuid, completion); + } + + @Override + public void deleteL2Network(L2NetworkInventory inv, NetworkDeletionContext context, + Completion completion) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inv); + if (controllerL2 == null) { + completion.fail(confirmedDeleteControllerMissing(inv)); + return; + } + controllerL2.deleteL2Network(inv, context, completion); + } + + @Override + public boolean supports(L2NetworkInventory inventory) { + return requiresConfirmedDelete(inventory); + } + + @Override + public ErrorCode begin(L2NetworkInventory inventory) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + return controllerL2 == null ? confirmedDeleteControllerMissing(inventory) + : controllerL2.beginConfirmedDelete(inventory); + } + + @Override + public ErrorCode begin(L2NetworkInventory inventory, NetworkDeletionContext context) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + return controllerL2 == null ? confirmedDeleteControllerMissing(inventory) + : controllerL2.beginConfirmedDelete(inventory, context); + } + + @Override + public ErrorCode check(L2NetworkInventory inventory) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + return controllerL2 == null ? confirmedDeleteControllerMissing(inventory) + : controllerL2.checkConfirmedDelete(inventory); + } + + @Override + public ErrorCode check(L2NetworkInventory inventory, NetworkDeletionContext context) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + return controllerL2 == null ? confirmedDeleteControllerMissing(inventory) + : controllerL2.checkConfirmedDelete(inventory, context); + } + + @Override + public ErrorCode delete(L2NetworkInventory inventory) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + return controllerL2 == null ? confirmedDeleteControllerMissing(inventory) + : controllerL2.completeConfirmedDelete(inventory); + } + + @Override + public ErrorCode delete(L2NetworkInventory inventory, NetworkDeletionContext context) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + return controllerL2 == null ? confirmedDeleteControllerMissing(inventory) + : controllerL2.completeConfirmedDelete(inventory, context); + } + + @Override + public ErrorCode cancel(L2NetworkInventory inventory) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + return controllerL2 == null ? confirmedDeleteControllerMissing(inventory) + : controllerL2.cancelConfirmedDelete(inventory); + } + + @Override + public ErrorCode cancel(L2NetworkInventory inventory, NetworkDeletionContext context) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + return controllerL2 == null ? confirmedDeleteControllerMissing(inventory) + : controllerL2.cancelConfirmedDelete(inventory, context); + } + + private ErrorCode confirmedDeleteControllerMissing(L2NetworkInventory inventory) { + return operr(ORG_ZSTACK_SDNCONTROLLER_10034, + "cannot find sdn controller for confirmed L2Network deletion[uuid:%s, vswitchType:%s]", + inventory.getUuid(), inventory.getvSwitchType()); + } + + @Override + public void deleteLocalMetadata(L2NetworkInventory inventory) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + if (controllerL2 == null) { + throw new CloudRuntimeException(String.format( + "cannot find sdn controller for confirmed l2 network deletion[uuid:%s]", inventory.getUuid())); + } + controllerL2.deleteConfirmedLocalMetadata(inventory); + } + + @Override + public void deleteLocalMetadata(L2NetworkInventory inventory, NetworkDeletionContext context) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + if (controllerL2 == null) { + throw new CloudRuntimeException(String.format( + "cannot find sdn controller for confirmed l2 network deletion[uuid:%s]", inventory.getUuid())); + } + controllerL2.deleteConfirmedLocalMetadata(inventory, context); + } + + private SdnControllerL2 findSdnControllerL2(L2NetworkInventory inv) { + VSwitchType vSwitchType = VSwitchType.valueOf(inv.getvSwitchType()); + if (vSwitchType.getSdnControllerType() == null) { + return null; + } + SdnControllerFactory factory = getSdnControllerFactory(vSwitchType.getSdnControllerType()); + return factory == null ? null : factory.getSdnControllerL2(inv.getUuid()); + } + @Override public void afterDeleteL2Network(L2NetworkInventory inventory) { diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerEvent.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerEvent.java new file mode 100644 index 00000000000..f905b260bff --- /dev/null +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerEvent.java @@ -0,0 +1,10 @@ +package org.zstack.sdnController.header; + +import org.zstack.header.message.APIEvent; +import org.zstack.header.rest.RestResponse; + +@RestResponse +public class APIPullSdnControllerEvent extends APIEvent { + public APIPullSdnControllerEvent() { } + public APIPullSdnControllerEvent(String apiId) { super(apiId); } +} diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerEventDoc_zh_cn.groovy b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerEventDoc_zh_cn.groovy new file mode 100644 index 00000000000..2b4da2c1827 --- /dev/null +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerEventDoc_zh_cn.groovy @@ -0,0 +1,23 @@ +package org.zstack.sdnController.header + +import org.zstack.header.errorcode.ErrorCode + +doc { + + title "PullSdnControllerEvent" + + field { + name "success" + desc "操作是否成功" + type "boolean" + since "5.5.38" + } + ref { + name "error" + path "org.zstack.sdnController.header.APIPullSdnControllerEvent.error" + desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null",false + type "ErrorCode" + since "5.5.38" + clz ErrorCode.class + } +} diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerMsg.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerMsg.java new file mode 100644 index 00000000000..dd65117f990 --- /dev/null +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerMsg.java @@ -0,0 +1,32 @@ +package org.zstack.sdnController.header; + +import org.springframework.http.HttpMethod; +import org.zstack.header.identity.Action; +import org.zstack.header.message.APIMessage; +import org.zstack.header.message.APIParam; +import org.zstack.header.network.sdncontroller.SdnControllerConstant; +import org.zstack.header.network.sdncontroller.SdnControllerMessage; +import org.zstack.header.network.sdncontroller.SdnControllerVO; +import org.zstack.header.rest.RestRequest; + +import java.util.List; + +@RestRequest(path = "/sdn-controllers/{uuid}/resources/actions", method = HttpMethod.PUT, + responseClass = APIPullSdnControllerEvent.class, isAction = true) +@Action(category = SdnControllerConstant.ACTION_CATEGORY) +public class APIPullSdnControllerMsg extends APIMessage implements SdnControllerMessage { + @APIParam(resourceType = SdnControllerVO.class, checkAccount = true, operationTarget = true) + private String uuid; + @APIParam(validValues = {"Segment", "TenantRouter"}) + private String resourceType; + @APIParam(required = false) + private List resourceUuids; + + @Override public String getSdnControllerUuid() { return uuid; } + public String getUuid() { return uuid; } + public void setUuid(String value) { uuid = value; } + public String getResourceType() { return resourceType; } + public void setResourceType(String value) { resourceType = value; } + public List getResourceUuids() { return resourceUuids; } + public void setResourceUuids(List value) { resourceUuids = value; } +} diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerMsgDoc_zh_cn.groovy b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerMsgDoc_zh_cn.groovy new file mode 100644 index 00000000000..41355167313 --- /dev/null +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerMsgDoc_zh_cn.groovy @@ -0,0 +1,77 @@ +package org.zstack.sdnController.header + +import org.zstack.sdnController.header.APIPullSdnControllerEvent + +doc { + title "PullSdnController" + + category "SdnController" + + desc """从SDN控制器拉取指定类型的最新资源""" + + rest { + request { + url "PUT /v1/sdn-controllers/{uuid}/resources/actions" + + header (Authorization: 'OAuth the-session-uuid') + + clz APIPullSdnControllerMsg.class + + desc """触发Cloud从指定SDN控制器读取Segment或TenantRouter;可指定资源UUID列表,未指定时拉取该类型的全部资源""" + + params { + + column { + name "uuid" + enclosedIn "pullSdnController" + desc "资源的UUID,唯一标示该资源" + location "url" + type "String" + optional false + since "5.5.38" + } + column { + name "resourceType" + enclosedIn "pullSdnController" + desc "待拉取的资源类型,支持Segment或TenantRouter" + location "body" + type "String" + optional false + since "5.5.38" + values ("Segment","TenantRouter") + } + column { + name "resourceUuids" + enclosedIn "pullSdnController" + desc "待拉取的资源UUID列表;为空时拉取所选类型的全部资源" + location "body" + type "List" + optional true + since "5.5.38" + } + column { + name "systemTags" + enclosedIn "" + desc "系统标签" + location "body" + type "List" + optional true + since "5.5.38" + } + column { + name "userTags" + enclosedIn "" + desc "用户标签" + location "body" + type "List" + optional true + since "5.5.38" + } + } + } + + response { + clz APIPullSdnControllerEvent.class + } + } +} diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/header/PullSdnControllerMsg.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/PullSdnControllerMsg.java new file mode 100644 index 00000000000..603ec9913e7 --- /dev/null +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/PullSdnControllerMsg.java @@ -0,0 +1,27 @@ +package org.zstack.sdnController.header; + +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.network.sdncontroller.SdnControllerMessage; + +import java.util.List; + +public class PullSdnControllerMsg extends NeedReplyMessage implements SdnControllerMessage { + private String sdnControllerUuid; + private String resourceType; + private List resourceUuids; + + public static PullSdnControllerMsg fromApi(APIPullSdnControllerMsg api) { + PullSdnControllerMsg msg = new PullSdnControllerMsg(); + msg.setSdnControllerUuid(api.getSdnControllerUuid()); + msg.setResourceType(api.getResourceType()); + msg.setResourceUuids(api.getResourceUuids()); + return msg; + } + + @Override public String getSdnControllerUuid() { return sdnControllerUuid; } + public void setSdnControllerUuid(String value) { sdnControllerUuid = value; } + public String getResourceType() { return resourceType; } + public void setResourceType(String value) { resourceType = value; } + public List getResourceUuids() { return resourceUuids; } + public void setResourceUuids(List value) { resourceUuids = value; } +} diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/header/PullSdnControllerReply.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/PullSdnControllerReply.java new file mode 100644 index 00000000000..6dc07d6e0c9 --- /dev/null +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/PullSdnControllerReply.java @@ -0,0 +1,6 @@ +package org.zstack.sdnController.header; + +import org.zstack.header.message.MessageReply; + +public class PullSdnControllerReply extends MessageReply { +} diff --git a/sdk/src/main/java/SourceClassMap.java b/sdk/src/main/java/SourceClassMap.java index 29c9197c37d..0d503dedd9a 100644 --- a/sdk/src/main/java/SourceClassMap.java +++ b/sdk/src/main/java/SourceClassMap.java @@ -636,6 +636,8 @@ public class SourceClassMap { put("org.zstack.network.service.virtualrouter.VirtualRouterVmInventory", "org.zstack.sdk.VirtualRouterVmInventory"); put("org.zstack.network.zns.L2GeneveNetworkInventory", "org.zstack.sdk.network.zns.L2GeneveNetworkInventory"); put("org.zstack.network.zns.ZnsControllerInventory", "org.zstack.sdk.network.zns.ZnsControllerInventory"); + put("org.zstack.network.zns.ZnsSegmentCloudProjectionInventory", "org.zstack.sdk.network.zns.ZnsSegmentCloudProjectionInventory"); + put("org.zstack.network.zns.ZnsSegmentSyncOperationInventory", "org.zstack.sdk.network.zns.ZnsSegmentSyncOperationInventory"); put("org.zstack.network.zns.ZnsTenantInventory", "org.zstack.sdk.network.zns.ZnsTenantInventory"); put("org.zstack.network.zns.ZnsTenantRouterInventory", "org.zstack.sdk.network.zns.ZnsTenantRouterInventory"); put("org.zstack.network.zns.ZnsTransportZoneInventory", "org.zstack.sdk.network.zns.ZnsTransportZoneInventory"); @@ -1746,6 +1748,8 @@ public class SourceClassMap { put("org.zstack.sdk.license.header.server.TotalLicenseAuthorizedCapacityView", "org.zstack.license.header.server.TotalLicenseAuthorizedCapacityView"); put("org.zstack.sdk.network.zns.L2GeneveNetworkInventory", "org.zstack.network.zns.L2GeneveNetworkInventory"); put("org.zstack.sdk.network.zns.ZnsControllerInventory", "org.zstack.network.zns.ZnsControllerInventory"); + put("org.zstack.sdk.network.zns.ZnsSegmentCloudProjectionInventory", "org.zstack.network.zns.ZnsSegmentCloudProjectionInventory"); + put("org.zstack.sdk.network.zns.ZnsSegmentSyncOperationInventory", "org.zstack.network.zns.ZnsSegmentSyncOperationInventory"); put("org.zstack.sdk.network.zns.ZnsTenantInventory", "org.zstack.network.zns.ZnsTenantInventory"); put("org.zstack.sdk.network.zns.ZnsTenantRouterInventory", "org.zstack.network.zns.ZnsTenantRouterInventory"); put("org.zstack.sdk.network.zns.ZnsTransportZoneInventory", "org.zstack.network.zns.ZnsTransportZoneInventory"); diff --git a/sdk/src/main/java/org/zstack/sdk/PullSdnControllerAction.java b/sdk/src/main/java/org/zstack/sdk/PullSdnControllerAction.java new file mode 100644 index 00000000000..509696c69a9 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/PullSdnControllerAction.java @@ -0,0 +1,107 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class PullSdnControllerAction extends AbstractAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.PullSdnControllerResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + return this; + } + } + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String uuid; + + @Param(required = true, validValues = {"Segment","TenantRouter"}, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String resourceType; + + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.util.List resourceUuids; + + @Param(required = false) + public java.util.List systemTags; + + @Param(required = false) + public java.util.List userTags; + + @Param(required = false) + public String sessionId; + + @Param(required = false) + public String accessKeyId; + + @Param(required = false) + public String accessKeySecret; + + @Param(required = false) + public String requestIp; + + @NonAPIParam + public long timeout = -1; + + @NonAPIParam + public long pollingInterval = -1; + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.PullSdnControllerResult value = res.getResult(org.zstack.sdk.PullSdnControllerResult.class); + ret.value = value == null ? new org.zstack.sdk.PullSdnControllerResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "PUT"; + info.path = "/sdn-controllers/{uuid}/resources/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "pullSdnController"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/PullSdnControllerResult.java b/sdk/src/main/java/org/zstack/sdk/PullSdnControllerResult.java new file mode 100644 index 00000000000..7048752ca90 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/PullSdnControllerResult.java @@ -0,0 +1,7 @@ +package org.zstack.sdk; + + + +public class PullSdnControllerResult { + +} diff --git a/sdk/src/main/java/org/zstack/sdk/network/zns/QueryZnsSegmentCloudProjectionAction.java b/sdk/src/main/java/org/zstack/sdk/network/zns/QueryZnsSegmentCloudProjectionAction.java new file mode 100644 index 00000000000..0e8d5e90cac --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/network/zns/QueryZnsSegmentCloudProjectionAction.java @@ -0,0 +1,75 @@ +package org.zstack.sdk.network.zns; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class QueryZnsSegmentCloudProjectionAction extends QueryAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.network.zns.QueryZnsSegmentCloudProjectionResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + return this; + } + } + + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.network.zns.QueryZnsSegmentCloudProjectionResult value = res.getResult(org.zstack.sdk.network.zns.QueryZnsSegmentCloudProjectionResult.class); + ret.value = value == null ? new org.zstack.sdk.network.zns.QueryZnsSegmentCloudProjectionResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "GET"; + info.path = "/zns-segment-cloud-projections"; + info.needSession = true; + info.needPoll = false; + info.parameterName = ""; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/network/zns/QueryZnsSegmentCloudProjectionResult.java b/sdk/src/main/java/org/zstack/sdk/network/zns/QueryZnsSegmentCloudProjectionResult.java new file mode 100644 index 00000000000..6b6331b2094 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/network/zns/QueryZnsSegmentCloudProjectionResult.java @@ -0,0 +1,22 @@ +package org.zstack.sdk.network.zns; + + + +public class QueryZnsSegmentCloudProjectionResult { + public java.util.List inventories; + public void setInventories(java.util.List inventories) { + this.inventories = inventories; + } + public java.util.List getInventories() { + return this.inventories; + } + + public java.lang.Long total; + public void setTotal(java.lang.Long total) { + this.total = total; + } + public java.lang.Long getTotal() { + return this.total; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/network/zns/ZnsSegmentCloudProjectionInventory.java b/sdk/src/main/java/org/zstack/sdk/network/zns/ZnsSegmentCloudProjectionInventory.java new file mode 100644 index 00000000000..f5ccc1e2158 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/network/zns/ZnsSegmentCloudProjectionInventory.java @@ -0,0 +1,153 @@ +package org.zstack.sdk.network.zns; + +import org.zstack.sdk.L2NetworkInventory; +import org.zstack.sdk.L3NetworkInventory; +import org.zstack.sdk.network.zns.ZnsSegmentSyncOperationInventory; + +public class ZnsSegmentCloudProjectionInventory { + + public java.lang.String uuid; + public void setUuid(java.lang.String uuid) { + this.uuid = uuid; + } + public java.lang.String getUuid() { + return this.uuid; + } + + public java.lang.String sdnControllerUuid; + public void setSdnControllerUuid(java.lang.String sdnControllerUuid) { + this.sdnControllerUuid = sdnControllerUuid; + } + public java.lang.String getSdnControllerUuid() { + return this.sdnControllerUuid; + } + + public java.lang.String znsSegmentUuid; + public void setZnsSegmentUuid(java.lang.String znsSegmentUuid) { + this.znsSegmentUuid = znsSegmentUuid; + } + public java.lang.String getZnsSegmentUuid() { + return this.znsSegmentUuid; + } + + public java.lang.String zoneUuid; + public void setZoneUuid(java.lang.String zoneUuid) { + this.zoneUuid = zoneUuid; + } + public java.lang.String getZoneUuid() { + return this.zoneUuid; + } + + public java.lang.String l2NetworkUuid; + public void setL2NetworkUuid(java.lang.String l2NetworkUuid) { + this.l2NetworkUuid = l2NetworkUuid; + } + public java.lang.String getL2NetworkUuid() { + return this.l2NetworkUuid; + } + + public java.lang.String l3NetworkUuid; + public void setL3NetworkUuid(java.lang.String l3NetworkUuid) { + this.l3NetworkUuid = l3NetworkUuid; + } + public java.lang.String getL3NetworkUuid() { + return this.l3NetworkUuid; + } + + public java.lang.String state; + public void setState(java.lang.String state) { + this.state = state; + } + public java.lang.String getState() { + return this.state; + } + + public java.lang.Long currentConfigVersion; + public void setCurrentConfigVersion(java.lang.Long currentConfigVersion) { + this.currentConfigVersion = currentConfigVersion; + } + public java.lang.Long getCurrentConfigVersion() { + return this.currentConfigVersion; + } + + public java.lang.Long appliedConfigVersion; + public void setAppliedConfigVersion(java.lang.Long appliedConfigVersion) { + this.appliedConfigVersion = appliedConfigVersion; + } + public java.lang.Long getAppliedConfigVersion() { + return this.appliedConfigVersion; + } + + public java.lang.String operationUuid; + public void setOperationUuid(java.lang.String operationUuid) { + this.operationUuid = operationUuid; + } + public java.lang.String getOperationUuid() { + return this.operationUuid; + } + + public java.lang.String operationStep; + public void setOperationStep(java.lang.String operationStep) { + this.operationStep = operationStep; + } + public java.lang.String getOperationStep() { + return this.operationStep; + } + + public java.lang.String lastErrorCode; + public void setLastErrorCode(java.lang.String lastErrorCode) { + this.lastErrorCode = lastErrorCode; + } + public java.lang.String getLastErrorCode() { + return this.lastErrorCode; + } + + public java.lang.String lastErrorDetails; + public void setLastErrorDetails(java.lang.String lastErrorDetails) { + this.lastErrorDetails = lastErrorDetails; + } + public java.lang.String getLastErrorDetails() { + return this.lastErrorDetails; + } + + public java.sql.Timestamp createDate; + public void setCreateDate(java.sql.Timestamp createDate) { + this.createDate = createDate; + } + public java.sql.Timestamp getCreateDate() { + return this.createDate; + } + + public java.sql.Timestamp lastOpDate; + public void setLastOpDate(java.sql.Timestamp lastOpDate) { + this.lastOpDate = lastOpDate; + } + public java.sql.Timestamp getLastOpDate() { + return this.lastOpDate; + } + + public L2NetworkInventory l2Network; + public void setL2Network(L2NetworkInventory l2Network) { + this.l2Network = l2Network; + } + public L2NetworkInventory getL2Network() { + return this.l2Network; + } + + public L3NetworkInventory l3Network; + public void setL3Network(L3NetworkInventory l3Network) { + this.l3Network = l3Network; + } + public L3NetworkInventory getL3Network() { + return this.l3Network; + } + + public ZnsSegmentSyncOperationInventory operation; + public void setOperation(ZnsSegmentSyncOperationInventory operation) { + this.operation = operation; + } + public ZnsSegmentSyncOperationInventory getOperation() { + return this.operation; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/network/zns/ZnsSegmentSyncOperationInventory.java b/sdk/src/main/java/org/zstack/sdk/network/zns/ZnsSegmentSyncOperationInventory.java new file mode 100644 index 00000000000..fa8f8027c9a --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/network/zns/ZnsSegmentSyncOperationInventory.java @@ -0,0 +1,23 @@ +package org.zstack.sdk.network.zns; + + + +public class ZnsSegmentSyncOperationInventory { + + public java.lang.String uuid; + public void setUuid(java.lang.String uuid) { + this.uuid = uuid; + } + public java.lang.String getUuid() { + return this.uuid; + } + + public java.lang.String step; + public void setStep(java.lang.String step) { + this.step = step; + } + public java.lang.String getStep() { + return this.step; + } + +} diff --git a/test/src/test/groovy/org/zstack/core/rest/Zcf5485RestStatusContractCase.groovy b/test/src/test/groovy/org/zstack/core/rest/Zcf5485RestStatusContractCase.groovy new file mode 100644 index 00000000000..749e30850e2 --- /dev/null +++ b/test/src/test/groovy/org/zstack/core/rest/Zcf5485RestStatusContractCase.groovy @@ -0,0 +1,99 @@ +package org.zstack.core.rest + +import org.junit.Test +import org.zstack.core.rest.webhook.WebhookCallbackClient +import org.zstack.core.rest.webhook.WebhookProtocol +import org.zstack.core.thread.ThreadFacade +import org.zstack.core.thread.ThreadFacadeImpl +import org.zstack.header.core.ReturnValueCompletion +import org.zstack.header.errorcode.ErrorCode +import org.zstack.header.rest.RESTFacade +import org.zstack.header.rest.SyncHttpResponse + +import javax.servlet.http.HttpServletResponse +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger + +import static org.mockito.ArgumentMatchers.any +import static org.mockito.ArgumentMatchers.anyLong +import static org.mockito.Mockito.mock +import static org.mockito.Mockito.verify +import static org.mockito.Mockito.when + +class Zcf5485RestStatusContractCase { + @Test + void statusAndBodyAreWrittenWithoutCollapsingNon2xxToTransportFailure() { + HttpServletResponse response = mock(HttpServletResponse.class) + StringWriter body = new StringWriter() + when(response.writer).thenReturn(new PrintWriter(body)) + + RESTFacadeImpl.writeSyncHttpResponse( + new SyncHttpResponse(503, '{"condition":"receiver-overloaded"}'), response) + + verify(response).setStatus(503) + verify(response).setHeader('Content-Type', 'application/json') + assert body.toString() == '{"condition":"receiver-overloaded"}' + } + + @Test + void nullResponseIsAcceptedAsEmptyHttp200() { + HttpServletResponse response = mock(HttpServletResponse.class) + RESTFacadeImpl.writeSyncHttpResponse(null, response) + verify(response).setStatus(200) + } + + @Test + void webhookTaskIsRegisteredBeforeTimeoutScheduling() { + ThreadFacade threadFacade = mock(ThreadFacade.class) + def receipt = mock(ThreadFacadeImpl.TimeoutTaskReceipt.class) + def scheduled = new AtomicInteger() + def registeredAtSchedule = new AtomicBoolean() + WebhookCallbackClient client + when(threadFacade.submitTimeoutTask(any(Runnable.class), any(TimeUnit.class), anyLong())) + .thenAnswer { invocation -> + scheduled.incrementAndGet() + def field = WebhookCallbackClient.getDeclaredField("pendingCalls") + field.accessible = true + registeredAtSchedule.set((field.get(client) as Map).containsKey("same-task")) + receipt + } + WebhookProtocol protocol = new WebhookProtocol() { + @Override + String getCallbackPath() { "/test-webhook" } + + @Override + Class getCallbackClass() { Map.class } + + @Override + String extractTaskId(Map callback) { callback.taskId } + + @Override + boolean isSuccess(Map callback) { true } + + @Override + String extractError(Map callback) { null } + + @Override + void decorateRequest(Map headers, String taskId, String callbackUrl) { } + } + client = new WebhookCallbackClient( + protocol, mock(RESTFacade.class), threadFacade) + def successCount = new AtomicInteger() + def failureCount = new AtomicInteger() + def original = new ReturnValueCompletion(null) { + @Override + void success(Map value) { successCount.incrementAndGet() } + + @Override + void fail(ErrorCode errorCode) { failureCount.incrementAndGet() } + } + client.submit("same-task", original, TimeUnit.MILLISECONDS, 0) + client.deliverCallback([taskId: "same-task"]) + + assert scheduled.get() == 1 + assert registeredAtSchedule.get() + assert successCount.get() == 1 + assert failureCount.get() == 0 + } +} diff --git a/test/src/test/groovy/org/zstack/sdnController/Zcf5485SdnPullContractCase.groovy b/test/src/test/groovy/org/zstack/sdnController/Zcf5485SdnPullContractCase.groovy new file mode 100644 index 00000000000..aa4efc22255 --- /dev/null +++ b/test/src/test/groovy/org/zstack/sdnController/Zcf5485SdnPullContractCase.groovy @@ -0,0 +1,29 @@ +package org.zstack.sdnController + +import org.junit.Test + +class Zcf5485SdnPullContractCase { + @Test + void resourceUuidsAreValidatedAndDeduplicatedBeforeRouting() { + String znsUuid = 'dc9905ec-e6fc-4122-8861-7f56decafcaa' + String cloudUuid = 'dc9905ece6fc412288617f56decafcaa' + + assert SdnControllerApiInterceptor.normalizeResourceUuids( + [znsUuid, znsUuid, cloudUuid]) == [znsUuid, cloudUuid] + assert SdnControllerApiInterceptor.normalizeResourceUuids([]).isEmpty() + assert SdnControllerApiInterceptor.normalizeResourceUuids(null) == null + + try { + SdnControllerApiInterceptor.normalizeResourceUuids(['not-a-uuid']) + assert false: 'invalid resource UUID must be rejected' + } catch (IllegalArgumentException ignored) { + } + } + + @Test + void duplicateUuidsDoNotConsumeTheOneHundredResourceLimit() { + String uuid = 'dc9905ec-e6fc-4122-8861-7f56decafcaa' + assert SdnControllerApiInterceptor.normalizeResourceUuids( + Collections.nCopies(101, uuid)) == [uuid] + } +} diff --git a/test/src/test/groovy/org/zstack/test/integration/core/rest/RestFacadeCase.groovy b/test/src/test/groovy/org/zstack/test/integration/core/rest/RestFacadeCase.groovy index 1e0f335f146..d1af759ee78 100644 --- a/test/src/test/groovy/org/zstack/test/integration/core/rest/RestFacadeCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/core/rest/RestFacadeCase.groovy @@ -1,11 +1,17 @@ package org.zstack.test.integration.core.rest import org.springframework.http.HttpEntity +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpMethod +import org.springframework.http.ResponseEntity +import org.springframework.web.client.HttpStatusCodeException import org.zstack.core.rest.RESTFacadeImpl import org.zstack.header.console.ConsoleConstants import org.zstack.header.errorcode.ErrorCode import org.zstack.header.errorcode.SysErrors import org.zstack.header.rest.AsyncRESTCallback +import org.zstack.header.rest.RESTConstant +import org.zstack.header.rest.SyncHttpResponse import org.zstack.test.core.rest.RESTBeanForTest import org.zstack.test.integration.ZStackTest import org.zstack.testlib.EnvSpec @@ -40,9 +46,48 @@ class RestFacadeCase extends SubCase { void test() { env.create { testRestFacadeFailureWillBeRecorded() + testSyncHandlerWritesResponseBody() + testSyncHandlerPreservesStatusAndBody() } } + void testSyncHandlerPreservesStatusAndBody() { + RESTFacadeImpl restf = bean(RESTFacadeImpl.class) + String commandPath = "/test-rest-facade-status-body" + String body = '{"condition":"receiver-overloaded"}' + restf.registerSyncHttpStatusBodyCallHandler(commandPath, Map.class) { + Map ignored -> new SyncHttpResponse(429, body) + } + + HttpHeaders headers = new HttpHeaders() + headers.set(RESTConstant.COMMAND_PATH, commandPath) + try { + restf.getRESTTemplate().exchange(restf.getSendCommandUrl(), HttpMethod.POST, + new HttpEntity("{}", headers), String.class) + assert false: "expected the HTTP client to observe status 429" + } catch (HttpStatusCodeException e) { + assert e.rawStatusCode == 429 + assert e.responseBodyAsString == body + assert e.responseHeaders.getFirst(HttpHeaders.CONTENT_TYPE).startsWith("application/json") + } + } + + void testSyncHandlerWritesResponseBody() { + RESTFacadeImpl restf = bean(RESTFacadeImpl.class) + String commandPath = "/test-rest-facade-response-body" + String body = '{"result":"ok"}' + restf.registerSyncHttpCallHandler(commandPath, Map.class) { Map ignored -> body } + + HttpHeaders headers = new HttpHeaders() + headers.set(RESTConstant.COMMAND_PATH, commandPath) + ResponseEntity response = restf.getRESTTemplate().exchange(restf.getSendCommandUrl(), + HttpMethod.POST, new HttpEntity("{}", headers), String.class) + + assert response.statusCodeValue == 200 + assert response.body == body + assert response.headers.getFirst(HttpHeaders.CONTENT_TYPE).startsWith("application/json") + } + void testRestFacadeFailureWillBeRecorded() { RESTFacadeImpl restf = bean(RESTFacadeImpl.class) diff --git a/test/src/test/groovy/org/zstack/test/integration/network/l2network/AttachL2NetworkCase.groovy b/test/src/test/groovy/org/zstack/test/integration/network/l2network/AttachL2NetworkCase.groovy index 7d5e8bbfa1d..4c6637d5592 100644 --- a/test/src/test/groovy/org/zstack/test/integration/network/l2network/AttachL2NetworkCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/network/l2network/AttachL2NetworkCase.groovy @@ -97,6 +97,7 @@ public class AttachL2NetworkCase extends SubCase{ testCreateL2NetworkWithoutPhysicalInterface() testAttachL2NoVlanNetwork() testAttachL2ValnNetwork() + testAttachL2NetworkReturnsFailureWhenAdmissionThrows() testAttachL2NoVlanNetworkSynchronously() testAttachL2VlanNetworkSynchronously() testAddHost() diff --git a/test/src/test/groovy/org/zstack/test/integration/network/l2network/L2NetworkCascadeCase.groovy b/test/src/test/groovy/org/zstack/test/integration/network/l2network/L2NetworkCascadeCase.groovy new file mode 100644 index 00000000000..a20883924f0 --- /dev/null +++ b/test/src/test/groovy/org/zstack/test/integration/network/l2network/L2NetworkCascadeCase.groovy @@ -0,0 +1,647 @@ +package org.zstack.test.integration.network.l2network + +import org.zstack.core.componentloader.PluginRegistry +import org.zstack.core.cloudbus.CloudBus +import org.zstack.core.cascade.CascadeAction +import org.zstack.core.cascade.CascadeConstant +import org.zstack.core.cascade.CascadeFacade +import org.zstack.core.db.DatabaseFacade +import org.zstack.header.core.Completion +import org.zstack.header.errorcode.ErrorCode +import org.zstack.header.message.AbstractBeforeDeliveryMessageInterceptor +import org.zstack.header.message.Message +import org.zstack.header.network.NetworkConfigLocalContinuation +import org.zstack.header.network.NetworkConfigMutation +import org.zstack.header.network.NetworkConfigMutationExtensionPoint +import org.zstack.header.network.l2.L2DeleteConfirmExtensionPoint +import org.zstack.header.network.l2.L2NetworkInventory +import org.zstack.header.network.l2.L2NetworkUpdateExtensionPoint +import org.zstack.header.network.l2.L2NetworkVO +import org.zstack.header.network.l2.NetworkDeletionContext +import org.zstack.header.network.l3.IpRangeDeletionMsg +import org.zstack.header.network.l3.L3NetworkDeleteExtensionPoint +import org.zstack.header.network.l3.L3NetworkDeletionMsg +import org.zstack.header.network.l3.L3NetworkException +import org.zstack.header.network.l3.L3NetworkInventory +import org.zstack.header.network.l3.L3NetworkVO +import org.zstack.header.zone.ZoneVO +import org.zstack.network.l2.L2NetworkCascadeFilterExtensionPoint +import org.zstack.network.l2.L2NetworkCascadeExtension +import org.zstack.network.l2.L2NetworkExtensionPointEmitter +import org.zstack.sdk.L2NetworkInventory as SdkL2NetworkInventory +import org.zstack.sdk.ZoneInventory +import org.zstack.test.integration.network.NetworkTest +import org.zstack.testlib.EnvSpec +import org.zstack.testlib.SubCase + +import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_CORE_10002 +import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_NETWORK_L2_10023 +import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_NETWORK_L2_10024 + +class L2NetworkCascadeCase extends SubCase { + EnvSpec env + DatabaseFacade dbf + + @Override + void setup() { + useSpring(NetworkTest.springSpec) + } + + @Override + void environment() { + env = env { + zone { + name = "check-zone" + + l2NoVlanNetwork { + name = "check-l2-1" + physicalInterface = "eth0" + + l3Network { + name = "check-l3" + } + } + + l2NoVlanNetwork { + name = "check-l2-2" + physicalInterface = "eth1" + } + } + + zone { + name = "cleanup-zone" + + l2NoVlanNetwork { + name = "cleanup-l2" + physicalInterface = "eth2" + + l3Network { + name = "cleanup-l3" + + ip { + startIp = "192.168.80.10" + endIp = "192.168.80.20" + netmask = "255.255.255.0" + gateway = "192.168.80.1" + } + } + } + + l2NoVlanNetwork { + name = "force-l2" + physicalInterface = "eth3" + } + + l2NoVlanNetwork { + name = "force-check-l2" + physicalInterface = "eth6" + + l3Network { + name = "force-check-l3" + } + } + + l2NoVlanNetwork { + name = "metadata-l2" + physicalInterface = "eth4" + } + + l2NoVlanNetwork { + name = "plain-l2" + physicalInterface = "eth5" + } + + l2NoVlanNetwork { + name = "metadata-update-l2" + physicalInterface = "eth7" + } + + l2NoVlanNetwork { + name = "delete-failure-l2" + physicalInterface = "eth8" + } + } + } + } + + @Override + void test() { + env.create { + dbf = bean(DatabaseFacade.class) + testFenceExistsBeforeChildCheck() + testDeleteZoneCancelsEveryConfirmedDeleteOnCheckFailure() + testPrepareRuntimeFailureReturnsError() + testConfirmedDeleteRuntimeFailureCancelsPreparedFences() + testDeletionCheckHonorsCascadeFilter() + testWholeL2ContextPropagatesToChildren() + testParentForceChecksBeforeDeletingChildren() + testParentForcePreservesProviderError() + testPermissiveDeleteFailureCancelsProviderFence() + testUnsupportedProviderKeepsExistingDeleteBehavior() + testAsyncUpdateRuntimeFailureCompletesWithError() + testL2MetadataMutationCommitsRemoteBeforeLocal() + testDeleteL2NetworkRemovesConfirmedMetadataOnce() + } + } + + void testFenceExistsBeforeChildCheck() { + ZoneInventory zone = env.inventoryByName("check-zone") + List events = [] + def confirmation = new RecordingDeleteConfirmation(events: events) + def childCheck = new RecordingL3DeleteCheck(events: events, failCheck: true) + List confirmations = bean(PluginRegistry.class) + .getExtensionList(L2DeleteConfirmExtensionPoint.class) + List childChecks = bean(PluginRegistry.class) + .getExtensionList(L3NetworkDeleteExtensionPoint.class) + confirmations.add(confirmation) + childChecks.add(childCheck) + + try { + expectError { + deleteZone { + uuid = zone.uuid + } + } + } finally { + childChecks.remove(childCheck) + confirmations.remove(confirmation) + } + + assert events.findIndexOf { it.startsWith("child-check:") } > + events.findLastIndexOf { it.startsWith("begin:") } + } + + void testDeleteZoneCancelsEveryConfirmedDeleteOnCheckFailure() { + ZoneInventory zone = env.inventoryByName("check-zone") + def extension = new RecordingDeleteConfirmation(failOnSecondCheck: true) + List extensions = bean(PluginRegistry.class) + .getExtensionList(L2DeleteConfirmExtensionPoint.class) + extensions.add(extension) + + try { + expectError { + deleteZone { + uuid = zone.uuid + } + } + } finally { + extensions.remove(extension) + } + + assert extension.begun.size() == 2 + assert extension.cancelled == extension.begun.toList().reverse() + assert dbf.isExist(zone.uuid, org.zstack.header.zone.ZoneVO.class) + assert extension.begun.every { dbf.isExist(it, L2NetworkVO.class) } + } + + void testPrepareRuntimeFailureReturnsError() { + SdkL2NetworkInventory l2 = env.inventoryByName("metadata-update-l2") + L2NetworkInventory inventory = L2NetworkInventory.valueOf( + dbf.findByUuid(l2.uuid, L2NetworkVO.class)) + def filter = new ThrowingCascadeFilter() + List filters = bean(PluginRegistry.class) + .getExtensionList(L2NetworkCascadeFilterExtensionPoint.class) + filters.add(filter) + def action = new CascadeAction() + .setRootIssuer(L2NetworkVO.simpleName) + .setRootIssuerContext([inventory]) + .setParentIssuer(L2NetworkVO.simpleName) + .setParentIssuerContext([inventory]) + .setActionCode(CascadeConstant.DELETION_CHECK_CODE) + ErrorCode failure + + try { + bean(CascadeFacade.class).asyncCascade(action, new Completion(null) { + @Override + void success() { assert false } + + @Override + void fail(ErrorCode errorCode) { failure = errorCode } + }) + } finally { + filters.remove(filter) + } + + assert failure.globalErrorCode == ORG_ZSTACK_CORE_10002 + } + + void testConfirmedDeleteRuntimeFailureCancelsPreparedFences() { + ZoneInventory zone = env.inventoryByName("check-zone") + def inventory = org.zstack.header.zone.ZoneInventory.valueOf( + dbf.findByUuid(zone.uuid, ZoneVO.class)) + def extension = new RecordingDeleteConfirmation(throwOnSecondBegin: true) + List extensions = bean(PluginRegistry.class) + .getExtensionList(L2DeleteConfirmExtensionPoint.class) + extensions.add(extension) + def action = new CascadeAction() + .setRootIssuer(ZoneVO.simpleName) + .setRootIssuerContext([inventory]) + .setParentIssuer(ZoneVO.simpleName) + .setParentIssuerContext([inventory]) + .setActionCode(CascadeConstant.DELETION_CHECK_CODE) + ErrorCode failure + + try { + bean(CascadeFacade.class).asyncCascade(action, new Completion(null) { + @Override + void success() { assert false } + + @Override + void fail(ErrorCode errorCode) { failure = errorCode } + }) + } finally { + extensions.remove(extension) + } + + assert failure.globalErrorCode == ORG_ZSTACK_NETWORK_L2_10023 + assert extension.begun.size() == 2 + assert extension.cancelled == [extension.begun.first()] + } + + void testDeletionCheckHonorsCascadeFilter() { + SdkL2NetworkInventory kept = env.inventoryByName("check-l2-1") + SdkL2NetworkInventory filtered = env.inventoryByName("check-l2-2") + def filter = new KeepOnlyCascadeFilter(keptUuid: kept.uuid) + def confirmation = new RecordingDeleteConfirmation() + List filters = bean(PluginRegistry.class) + .getExtensionList(L2NetworkCascadeFilterExtensionPoint.class) + List confirmations = bean(PluginRegistry.class) + .getExtensionList(L2DeleteConfirmExtensionPoint.class) + filters.add(filter) + confirmations.add(confirmation) + def inventories = [kept, filtered].collect { + L2NetworkInventory.valueOf(dbf.findByUuid(it.uuid, L2NetworkVO.class)) + } + def action = new CascadeAction() + .setRootIssuer(L2NetworkVO.simpleName) + .setRootIssuerContext(inventories) + .setParentIssuer(L2NetworkVO.simpleName) + .setParentIssuerContext(inventories) + .setActionCode(CascadeConstant.DELETION_CHECK_CODE) + boolean success + + try { + bean(CascadeFacade.class).asyncCascade(action, new Completion(null) { + @Override + void success() { success = true } + + @Override + void fail(ErrorCode errorCode) { assert false: errorCode } + }) + bean(L2NetworkCascadeExtension.class).afterCascadeFailure(action) + } finally { + confirmations.remove(confirmation) + filters.remove(filter) + } + + assert success + assert confirmation.checked == [kept.uuid] + assert confirmation.checkContexts.every { it != null } + } + + void testDeleteL2NetworkRemovesConfirmedMetadataOnce() { + SdkL2NetworkInventory l2 = env.inventoryByName("metadata-l2") + def extension = new RecordingDeleteConfirmation() + List extensions = bean(PluginRegistry.class) + .getExtensionList(L2DeleteConfirmExtensionPoint.class) + extensions.add(extension) + + try { + deleteL2Network { + uuid = l2.uuid + } + } finally { + extensions.remove(extension) + } + + assert !dbf.isExist(l2.uuid, L2NetworkVO.class) + assert extension.checkCount == 1 + assert extension.localMetadataDeletes == [l2.uuid] + } + + void testWholeL2ContextPropagatesToChildren() { + SdkL2NetworkInventory l2 = env.inventoryByName("cleanup-l2") + def extension = new RecordingDeleteConfirmation() + List extensions = bean(PluginRegistry.class) + .getExtensionList(L2DeleteConfirmExtensionPoint.class) + extensions.add(extension) + List contexts = [] + bean(CloudBus.class).installBeforeDeliveryMessageInterceptor( + new AbstractBeforeDeliveryMessageInterceptor() { + @Override + void beforeDeliveryMessage(Message msg) { + contexts.add(msg.networkDeletionContext) + } + }, L3NetworkDeletionMsg.class, IpRangeDeletionMsg.class) + + try { + deleteL2Network { + uuid = l2.uuid + } + } finally { + extensions.remove(extension) + } + + assert contexts.size() == 2 + assert contexts.every { it?.origin == NetworkDeletionContext.Origin.WHOLE_L2_SEGMENT_DELETE } + assert contexts.every { it.rootIssuer == L2NetworkVO.simpleName } + assert contexts*.operationUuid.toSet() == extension.operationUuids.toSet() + } + + void testParentForcePreservesProviderError() { + SdkL2NetworkInventory l2 = env.inventoryByName("force-l2") + def extension = new RecordingDeleteConfirmation(failDelete: true) + List extensions = bean(PluginRegistry.class) + .getExtensionList(L2DeleteConfirmExtensionPoint.class) + extensions.add(extension) + + try { + expectError { + deleteL2Network { + uuid = l2.uuid + deleteMode = "Enforcing" + } + } + } finally { + extensions.remove(extension) + } + + assert dbf.isExist(l2.uuid, L2NetworkVO.class) + assert extension.forceDeleteFlags == [true] + assert extension.cancelled == [l2.uuid] + } + + void testPermissiveDeleteFailureCancelsProviderFence() { + SdkL2NetworkInventory l2 = env.inventoryByName("delete-failure-l2") + def extension = new RecordingDeleteConfirmation(failDelete: true) + List extensions = bean(PluginRegistry.class) + .getExtensionList(L2DeleteConfirmExtensionPoint.class) + extensions.add(extension) + + try { + expectError { + deleteL2Network { + uuid = l2.uuid + } + } + } finally { + extensions.remove(extension) + } + + assert dbf.isExist(l2.uuid, L2NetworkVO.class) + assert extension.cancelled == [l2.uuid] + } + + void testParentForceChecksBeforeDeletingChildren() { + SdkL2NetworkInventory l2 = env.inventoryByName("force-check-l2") + def l3 = env.inventoryByName("force-check-l3") + def extension = new RecordingDeleteConfirmation(failCheck: true) + List extensions = bean(PluginRegistry.class) + .getExtensionList(L2DeleteConfirmExtensionPoint.class) + extensions.add(extension) + + try { + expectError { + deleteL2Network { + uuid = l2.uuid + deleteMode = "Enforcing" + } + } + } finally { + extensions.remove(extension) + } + + assert extension.checkCount == 1 + assert dbf.isExist(l2.uuid, L2NetworkVO.class) + assert dbf.isExist(l3.uuid, L3NetworkVO.class) + } + + void testUnsupportedProviderKeepsExistingDeleteBehavior() { + SdkL2NetworkInventory l2 = env.inventoryByName("plain-l2") + def extension = new RecordingDeleteConfirmation(supported: false, failDelete: true) + List extensions = bean(PluginRegistry.class) + .getExtensionList(L2DeleteConfirmExtensionPoint.class) + extensions.add(extension) + + try { + deleteL2Network { + uuid = l2.uuid + } + } finally { + extensions.remove(extension) + } + + assert !dbf.isExist(l2.uuid, L2NetworkVO.class) + assert extension.begun.isEmpty() + } + + void testAsyncUpdateRuntimeFailureCompletesWithError() { + SdkL2NetworkInventory l2 = env.inventoryByName("metadata-update-l2") + L2NetworkInventory inventory = L2NetworkInventory.valueOf( + dbf.findByUuid(l2.uuid, L2NetworkVO.class)) + def extension = new ThrowingL2UpdateExtension() + List extensions = bean(PluginRegistry.class) + .getExtensionList(L2NetworkUpdateExtensionPoint.class) + extensions.add(extension) + ErrorCode failure + + try { + bean(L2NetworkExtensionPointEmitter.class).beforeUpdate( + inventory, new Completion(null) { + @Override + void success() { assert false } + + @Override + void fail(ErrorCode errorCode) { failure = errorCode } + }) + } finally { + extensions.remove(extension) + } + + assert failure.globalErrorCode == ORG_ZSTACK_NETWORK_L2_10024 + } + + void testL2MetadataMutationCommitsRemoteBeforeLocal() { + SdkL2NetworkInventory l2 = env.inventoryByName("metadata-update-l2") + def extension = new RecordingMetadataMutation(dbf: dbf, l2Uuid: l2.uuid, failRemote: true) + bean(PluginRegistry.class).defineDynamicExtension( + NetworkConfigMutationExtensionPoint.class, extension) + + try { + expectError { + updateL2Network { + uuid = l2.uuid + name = "must-not-commit" + description = "must-not-commit" + } + } + assert dbf.findByUuid(l2.uuid, L2NetworkVO.class).name == "metadata-update-l2" + + extension.failRemote = false + updateL2Network { + uuid = l2.uuid + name = "metadata-updated" + description = "metadata-description" + } + } finally { + extension.l2Uuid = null + } + + L2NetworkVO updated = dbf.findByUuid(l2.uuid, L2NetworkVO.class) + assert extension.nameBeforeContinuation == "metadata-update-l2" + assert extension.mutations*.kind == [ + NetworkConfigMutation.Kind.L2_METADATA, + NetworkConfigMutation.Kind.L2_METADATA] + assert updated.name == "metadata-updated" + assert updated.description == "metadata-description" + } + + @Override + void clean() { + env.delete() + } + + private static class RecordingDeleteConfirmation implements L2DeleteConfirmExtensionPoint { + boolean supported = true + boolean failOnSecondCheck + boolean failCheck + boolean failDelete + boolean throwOnSecondBegin + int checkCount + List events = [] + List begun = [] + List cancelled = [] + List localMetadataDeletes = [] + List operationUuids = [] + List forceDeleteFlags = [] + List checked = [] + List checkContexts = [] + + @Override + boolean supports(L2NetworkInventory inventory) { + return supported + } + + @Override + ErrorCode begin(L2NetworkInventory inventory) { + begun.add(inventory.uuid) + events.add("begin:${inventory.uuid}") + if (throwOnSecondBegin && begun.size() == 2) { + throw new IllegalStateException("simulated begin failure") + } + return null + } + + @Override + ErrorCode begin(L2NetworkInventory inventory, NetworkDeletionContext context) { + operationUuids.add(context.operationUuid) + forceDeleteFlags.add(context.forceDelete) + return begin(inventory) + } + + @Override + ErrorCode check(L2NetworkInventory inventory) { + checkCount++ + return failCheck || failOnSecondCheck && checkCount == 2 ? + new ErrorCode("TEST", "simulated confirmation failure") : null + } + + @Override + ErrorCode check(L2NetworkInventory inventory, NetworkDeletionContext context) { + checked.add(inventory.uuid) + checkContexts.add(context) + return check(inventory) + } + + @Override + ErrorCode delete(L2NetworkInventory inventory) { + return failDelete ? new ErrorCode("TEST", "simulated provider delete failure") : null + } + + @Override + ErrorCode cancel(L2NetworkInventory inventory) { + cancelled.add(inventory.uuid) + return null + } + + @Override + void deleteLocalMetadata(L2NetworkInventory inventory) { + localMetadataDeletes.add(inventory.uuid) + } + } + + private static class ThrowingCascadeFilter implements L2NetworkCascadeFilterExtensionPoint { + @Override + List filterL2NetworkCascade(List inventories, + CascadeAction action) { + throw new IllegalStateException("simulated filter failure") + } + } + + private static class KeepOnlyCascadeFilter implements L2NetworkCascadeFilterExtensionPoint { + String keptUuid + + @Override + List filterL2NetworkCascade(List inventories, + CascadeAction action) { + return inventories.findAll { it.uuid == keptUuid } + } + } + + private static class ThrowingL2UpdateExtension implements L2NetworkUpdateExtensionPoint { + @Override + void beforeChangeL2NetworkVlanId(L2NetworkInventory inventory) { + throw new IllegalStateException("simulated update failure") + } + + @Override + void afterChangeL2NetworkVlanId(L2NetworkInventory inventory) { } + } + + private static class RecordingL3DeleteCheck implements L3NetworkDeleteExtensionPoint { + List events + boolean failCheck + + @Override + String preDeleteL3Network(L3NetworkInventory inventory) throws L3NetworkException { + events.add("child-check:${inventory.uuid}") + if (failCheck) { + throw new L3NetworkException("simulated child check failure") + } + return null + } + + @Override + void beforeDeleteL3Network(L3NetworkInventory inventory) { + } + + @Override + void afterDeleteL3Network(L3NetworkInventory inventory) { + } + } + + private static class RecordingMetadataMutation implements NetworkConfigMutationExtensionPoint { + DatabaseFacade dbf + String l2Uuid + boolean failRemote + String nameBeforeContinuation + List mutations = [] + + @Override + boolean supports(NetworkConfigMutation mutation) { + return mutation.kind == NetworkConfigMutation.Kind.L2_METADATA && mutation.l2Uuid == l2Uuid + } + + @Override + void mutate(NetworkConfigMutation mutation, NetworkConfigLocalContinuation continuation, + Completion completion) { + mutations.add(mutation) + nameBeforeContinuation = dbf.findByUuid(l2Uuid, L2NetworkVO.class).name + if (failRemote) { + completion.fail(new ErrorCode("TEST.REMOTE.FAILURE", "simulated remote failure")) + return + } + continuation.run(completion) + } + } +} diff --git a/test/src/test/groovy/org/zstack/test/integration/network/sdnController/SdnControllerCase.groovy b/test/src/test/groovy/org/zstack/test/integration/network/sdnController/SdnControllerCase.groovy index c07c9c3314c..49946caf631 100644 --- a/test/src/test/groovy/org/zstack/test/integration/network/sdnController/SdnControllerCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/network/sdnController/SdnControllerCase.groovy @@ -360,6 +360,23 @@ class SdnControllerCase extends SubCase { conditions=["type=" + SdnControllerConstant.HARDWARE_VXLAN_NETWORK_TYPE]} assert vxlanNetworks.size() == 2 + expectError { + pullSdnController { + uuid = sdn2.uuid + resourceType = "Segment" + resourceUuids = ["dc9905ec-e6fc-4122-8861-7f56decafcaa"] + } + } + assert queryL2Network { conditions=["type=" + SdnControllerConstant.HARDWARE_VXLAN_NETWORK_TYPE] }.size() == 2 + + expectError { + pullSdnController { + uuid = sdn2.uuid + resourceType = "Segment" + resourceUuids = ["not-a-uuid"] + } + } + removeSdnController { uuid = sdn2.uuid } diff --git a/test/src/test/groovy/org/zstack/test/unittest/network/Zcf5485CanonicalContractCase.groovy b/test/src/test/groovy/org/zstack/test/unittest/network/Zcf5485CanonicalContractCase.groovy new file mode 100644 index 00000000000..7c8c7fe6c51 --- /dev/null +++ b/test/src/test/groovy/org/zstack/test/unittest/network/Zcf5485CanonicalContractCase.groovy @@ -0,0 +1,47 @@ +package org.zstack.test.unittest.network + +import groovy.json.JsonSlurper +import org.junit.Test + +import java.nio.charset.StandardCharsets +import java.security.MessageDigest + +class Zcf5485CanonicalContractCase { + @Test + void coreConsumesCanonicalSegmentCloudContract() { + File fixture = new File('src/test/resources/zns/zcf5485/segment-cloud-contract.json') + File checksums = new File('src/test/resources/zns/zcf5485/SHA256SUMS') + assert fixture.isFile() + assert checksums.isFile() + assert sha256(fixture.bytes) == checksums.text.trim().split(/\s+/)[0] + + def contract = new JsonSlurper().parse(fixture) + assert contract.contract == 'ZCF-5485' + assert contract.revision == 'zcf5485-final-v3' + assert contract.version == 3 + + assert contract.cloud_api.pull.message == 'APIPullSdnControllerMsg' + assert contract.cloud_api.pull.event == 'APIPullSdnControllerEvent' + assert contract.cloud_api.pull.supported_resource_types == ['Segment', 'TenantRouter'] + assert contract.cloud_api.pull.resource_uuid_limit == 100 + assert contract.cloud_api.pull.full_refresh_when_uuids_empty + assert contract.cloud_api.projection_query.side_effect_free + assert contract.cloud_api.projection_query.required_inventory_fields.containsAll([ + 'znsSegmentUuid', 'sdnControllerUuid', 'zoneUuid', 'l2NetworkUuid', + 'l2Network', 'state', 'createDate', 'lastOpDate' + ]) + + def digest = contract.digest_vectors[0] + assert sha256(digest.canonical_request.getBytes(StandardCharsets.UTF_8)) == digest.sha256 + assert contract.child_operation_uuid_vectors*.derived_uuid_v3 == [ + '07c1e718-28c8-3e0a-b2cc-44212e14c3ad', + '814a7757-32b3-3782-9fff-895abe559f06' + ] + } + + private static String sha256(byte[] bytes) { + MessageDigest.getInstance('SHA-256').digest(bytes).collect { + String.format('%02x', it & 0xff) + }.join() + } +} diff --git a/test/src/test/groovy/org/zstack/test/unittest/network/Zcf5485NetworkContextCase.groovy b/test/src/test/groovy/org/zstack/test/unittest/network/Zcf5485NetworkContextCase.groovy new file mode 100644 index 00000000000..8f2d3b6acf6 --- /dev/null +++ b/test/src/test/groovy/org/zstack/test/unittest/network/Zcf5485NetworkContextCase.groovy @@ -0,0 +1,142 @@ +package org.zstack.test.unittest.network + +import org.junit.Test +import org.zstack.header.core.Completion +import org.zstack.header.core.ReturnValueCompletion +import org.zstack.header.errorcode.ErrorCode +import org.zstack.header.network.NetworkConfigMutation +import org.zstack.header.network.l2.ExternalNetworkRef +import org.zstack.header.network.l2.NetworkCreateContext +import org.zstack.header.network.l2.NetworkDeletionContext +import org.zstack.header.network.l2.NetworkOperationOrigin +import org.zstack.header.network.l3.APIAddIpRangeMsg +import org.zstack.header.network.l3.IpRangeInventory +import org.zstack.header.network.l3.L3NetworkInventory +import org.zstack.header.network.l3.SdnControllerL3 +import org.zstack.network.l3.AddressPoolIpRangeFactory +import org.zstack.network.l3.NormalIpRangeFactory + +import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_NETWORK_L3_10092 +import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_NETWORK_L3_10093 + +class Zcf5485NetworkContextCase { + @Test + void creationContextCarriesCommitAndContinuationEvidence() { + def api = NetworkCreateContext.api() + assert api.origin == NetworkOperationOrigin.API + assert !api.remoteCommitted + assert api.expectedConfigVersion == null + assert api.continuationStep == null + + def committed = NetworkCreateContext.cloudCommit( + 'operation-1', 7L, NetworkCreateContext.APPLY_LOCAL_STEP) + assert committed.origin == NetworkOperationOrigin.CLOUD_COMMIT + assert committed.remoteCommitted + assert committed.expectedConfigVersion == 7L + assert committed.continuationStep == NetworkCreateContext.APPLY_LOCAL_STEP + assert committed.operationStep == committed.continuationStep + + def projected = NetworkCreateContext.projection( + NetworkOperationOrigin.ZNS_REFRESH, + new ExternalNetworkRef('segment-1', 'account-1'), + 'operation-2', 9L, 'REPAIR_LOCAL') + assert projected.projection + assert projected.remoteWriteSuppressed + assert projected.remoteCommitted + assert projected.expectedConfigVersion == 9L + assert projected.continuationStep == 'REPAIR_LOCAL' + } + + @Test + void deletionContextCarriesCommitAndContinuationEvidence() { + def context = new NetworkDeletionContext( + NetworkDeletionContext.Origin.WHOLE_L2_SEGMENT_DELETE, + 'operation-3', 'l2-1', 'L2NetworkVO') + context.expectedConfigVersion = 11L + context.remoteCommitted = true + context.continuationStep = 'DELETE_LOCAL' + + assert context.expectedConfigVersion == 11L + assert context.remoteCommitted + assert context.continuationStep == 'DELETE_LOCAL' + } + + @Test + void metadataMutationCarriesExactBeforeLocalCommitTarget() { + def mutation = NetworkConfigMutation.metadata( + 'l2-1', NetworkOperationOrigin.API, 'operation-4', 'account-1', + 'new-name', 'new-description') + + assert mutation.kind == NetworkConfigMutation.Kind.L2_METADATA + assert mutation.l2Uuid == 'l2-1' + assert mutation.operationUuid == 'operation-4' + assert mutation.accountUuid == 'account-1' + assert mutation.metadata.name == 'new-name' + assert mutation.metadata.description == 'new-description' + } + + @Test + void ipRangeFactoriesRejectMissingAccountBeforePersistence() { + ErrorCode normalError + new NormalIpRangeFactory().createIpRange( + [new IpRangeInventory()], new APIAddIpRangeMsg(), null, + new ReturnValueCompletion>(null) { + @Override + void success(List value) { assert false } + + @Override + void fail(ErrorCode errorCode) { normalError = errorCode } + }) + assert normalError.globalErrorCode == ORG_ZSTACK_NETWORK_L3_10092 + + ErrorCode addressPoolError + new AddressPoolIpRangeFactory().createIpRange( + [new IpRangeInventory()], new APIAddIpRangeMsg(), NetworkCreateContext.api(), + new ReturnValueCompletion>(null) { + @Override + void success(List value) { assert false } + + @Override + void fail(ErrorCode errorCode) { addressPoolError = errorCode } + }) + assert addressPoolError.globalErrorCode == ORG_ZSTACK_NETWORK_L3_10093 + } + + @Test + void legacySdnIpRangeDeletionRemainsBestEffort() { + boolean providerCalled = false + boolean completed = false + SdnControllerL3 controller = new SdnControllerL3() { + @Override + void createL3Network(L3NetworkInventory inv, List systemTags, + Completion completion) { completion.success() } + + @Override + void deleteL3Network(L3NetworkInventory inv, Completion completion) { + completion.success() + } + + @Override + void createIpRange(IpRangeInventory inv, Completion completion) { + completion.success() + } + + @Override + void deleteIpRange(IpRangeInventory inv, Completion completion) { + providerCalled = true + completion.fail(new ErrorCode("TEST", "legacy provider failure")) + } + } + + controller.deleteIpRange(new IpRangeInventory(), true, null, new Completion(null) { + @Override + void success() { completed = true } + + @Override + void fail(ErrorCode errorCode) { assert false: errorCode } + }) + + assert providerCalled + assert completed + } +} diff --git a/test/src/test/java/org/zstack/test/network/TestSdnControllerL3.java b/test/src/test/java/org/zstack/test/network/TestSdnControllerL3.java new file mode 100644 index 00000000000..add09019ca8 --- /dev/null +++ b/test/src/test/java/org/zstack/test/network/TestSdnControllerL3.java @@ -0,0 +1,52 @@ +package org.zstack.test.network; + +import org.junit.Assert; +import org.junit.Test; +import org.zstack.header.core.Completion; +import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.network.l3.IpRangeInventory; +import org.zstack.header.network.l3.L3NetworkInventory; +import org.zstack.header.network.l3.SdnControllerL3; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +public class TestSdnControllerL3 { + @Test + public void testDeleteLastIpRangePropagatesControllerFailure() { + ErrorCode expected = new ErrorCode("SYS.1006", "Operation Error", "controller failed"); + AtomicReference actual = new AtomicReference<>(); + SdnControllerL3 controller = new SdnControllerL3() { + @Override + public void createL3Network(L3NetworkInventory inv, List systemTags, Completion completion) { + } + + @Override + public void deleteL3Network(L3NetworkInventory inv, Completion completion) { + } + + @Override + public void createIpRange(IpRangeInventory inv, Completion completion) { + } + + @Override + public void deleteIpRange(IpRangeInventory inv, Completion completion) { + completion.fail(expected); + } + }; + + controller.deleteIpRange(null, true, new Completion(null) { + @Override + public void success() { + Assert.fail("controller failure must not be converted to success"); + } + + @Override + public void fail(ErrorCode errorCode) { + actual.set(errorCode); + } + }); + + Assert.assertSame(expected, actual.get()); + } +} diff --git a/test/src/test/resources/zns/zcf5485/SHA256SUMS b/test/src/test/resources/zns/zcf5485/SHA256SUMS new file mode 100644 index 00000000000..2a0ce5784f3 --- /dev/null +++ b/test/src/test/resources/zns/zcf5485/SHA256SUMS @@ -0,0 +1 @@ +bdeba91b2207dd73a33699f8c35f75423624fc75a50b97cf3c4dd3cd9eb7452e *segment-cloud-contract.json diff --git a/test/src/test/resources/zns/zcf5485/segment-cloud-contract.json b/test/src/test/resources/zns/zcf5485/segment-cloud-contract.json new file mode 100644 index 00000000000..4171e5ddcd2 --- /dev/null +++ b/test/src/test/resources/zns/zcf5485/segment-cloud-contract.json @@ -0,0 +1,330 @@ +{ + "contract": "ZCF-5485", + "revision": "zcf5485-final-v3", + "version": 3, + "identities": { + "segment_uuid": "00000000-0000-0000-0000-000000000101", + "compute_manager_uuid": "00000000-0000-0000-0000-000000000102", + "cloud_zone_uuid": "00000000-0000-0000-0000-000000000103", + "cloud_l2_uuid": "00000000-0000-0000-0000-000000000104", + "tenant_router_uuid": "00000000-0000-0000-0000-000000000105", + "segment_port_uuid": "00000000-0000-0000-0000-000000000106", + "cms_resource_uuid": "00000000-0000-0000-0000-000000000107", + "transport_zone_uuid": "00000000-0000-0000-0000-000000000201", + "cluster_uuid": "00000000-0000-0000-0000-000000000401", + "operation_uuid": "00000000-0000-0000-0000-000000000701" + }, + "cloud_api": { + "pull": { + "message": "APIPullSdnControllerMsg", + "event": "APIPullSdnControllerEvent", + "method": "PUT", + "path": "/sdn-controllers/{uuid}/resources/actions", + "request": { + "pullSdnController": { + "resourceType": "Segment", + "resourceUuids": ["00000000-0000-0000-0000-000000000101"] + } + }, + "supported_resource_types": ["Segment", "TenantRouter"], + "resource_uuid_limit": 100, + "full_refresh_when_uuids_empty": true, + "event_means_accepted_only": true + }, + "projection_query": { + "message": "APIQueryZnsSegmentCloudProjectionMsg", + "reply": "APIQueryZnsSegmentCloudProjectionReply", + "method": "GET", + "path": "/zns-segment-cloud-projections", + "side_effect_free": true, + "required_inventory_fields": [ + "znsSegmentUuid", + "sdnControllerUuid", + "zoneUuid", + "l2NetworkUuid", + "l2Network", + "state", + "createDate", + "lastOpDate" + ], + "optional_inventory_fields": [ + "l3NetworkUuid", + "l3Network", + "currentConfigVersion", + "appliedConfigVersion", + "operationUuid", + "operationStep", + "lastError" + ] + } + }, + "cloud_inventory": { + "segment_uuid": "00000000-0000-0000-0000-000000000101", + "name": "canonical-segment", + "transport_zone_uuid": "00000000-0000-0000-0000-000000000201", + "transport_type": "overlay", + "availability": "Available", + "reason": "", + "placement": { + "transport_zone_uuid": "00000000-0000-0000-0000-000000000201", + "transport_type": "overlay", + "topology_version": 11, + "targets": [ + { + "compute_manager_uuid": "00000000-0000-0000-0000-000000000102", + "zones": [ + { + "cloud_zone_uuid": "00000000-0000-0000-0000-000000000103", + "complete_cluster_uuids": ["00000000-0000-0000-0000-000000000401"] + } + ] + } + ] + }, + "cloud_zone_uuid": "00000000-0000-0000-0000-000000000103", + "cluster_uuids": ["00000000-0000-0000-0000-000000000401"], + "tenant_router_uuid": "00000000-0000-0000-0000-000000000105" + }, + "cloud_projection": { + "segment_uuid": "00000000-0000-0000-0000-000000000101", + "name": "canonical-segment", + "description": "canonical description", + "config_version": 7, + "etag": "\"7-abc\"", + "mtu": 1400, + "mtu_inherited": true, + "virtual_network_id": 30001, + "transport_zone_uuid": "00000000-0000-0000-0000-000000000201", + "transport_type": "overlay", + "router_uuid": "00000000-0000-0000-0000-000000000105", + "placement": { + "transport_zone_uuid": "00000000-0000-0000-0000-000000000201", + "transport_type": "overlay", + "topology_version": 11, + "targets": [ + { + "compute_manager_uuid": "00000000-0000-0000-0000-000000000102", + "zones": [ + { + "cloud_zone_uuid": "00000000-0000-0000-0000-000000000103", + "complete_cluster_uuids": ["00000000-0000-0000-0000-000000000401"] + } + ] + } + ] + }, + "ipams": [ + { + "ip_version": 4, + "range_uuid": "00000000-0000-0000-0000-000000000601", + "cidr": "192.0.2.0/24", + "start_ip": "192.0.2.10", + "end_ip": "192.0.2.200" + } + ], + "dhcp": {"enabled": true} + }, + "digest_vectors": [ + { + "name": "patch-segment-name-description", + "purpose": "PATCH_SEGMENT", + "canonical_request": "{\"purpose\":\"PATCH_SEGMENT\",\"request\":{\"description\":\"canonical description\",\"name\":\"canonical-name\",\"segment_uuid\":\"00000000-0000-0000-0000-000000000101\"}}", + "sha256": "7fa8b82380eabd7de547dbd61ea16e023bde7d9a26919ebc2ddcee60e83dc47b" + } + ], + "child_operation_uuid_vectors": [ + { + "purpose": "delete-orphan-port", + "root_operation_uuid": "00000000-0000-0000-0000-000000000701", + "resource_uuid": "00000000-0000-0000-0000-000000000106", + "derived_uuid_v3": "07c1e718-28c8-3e0a-b2cc-44212e14c3ad" + }, + { + "purpose": "delete-router-interface", + "root_operation_uuid": "00000000-0000-0000-0000-000000000701", + "resource_uuid": "00000000-0000-0000-0000-000000000105", + "derived_uuid_v3": "814a7757-32b3-3782-9fff-895abe559f06" + } + ], + "mutations": [ + { + "method": "PATCH", + "path": "/zns/api/v1/segments/00000000-0000-0000-0000-000000000101/ipams", + "headers": {"If-Match": "7", "x-job-uuid": "00000000-0000-0000-0000-000000000711"}, + "body": { + "ip_version": 4, + "ip_ranges": [{"range_uuid": "00000000-0000-0000-0000-000000000601", "start_ip": "192.0.2.10", "end_ip": "192.0.2.200"}] + }, + "wire": {"status": 200, "has_data": true, "result_config_version": 8} + }, + { + "method": "PATCH", + "path": "/zns/api/v1/segments/00000000-0000-0000-0000-000000000101", + "headers": {"If-Match": "7", "x-job-uuid": "00000000-0000-0000-0000-000000000712"}, + "body": {"mtu": 1400, "virtual_network_id": 30001}, + "fields": ["mtu", "virtual_network_id"], + "wire": {"status": 202, "has_data": false, "accepted_only": true} + }, + { + "method": "PATCH", + "path": "/zns/api/v1/segments/00000000-0000-0000-0000-000000000101", + "headers": {"If-Match": "7", "x-job-uuid": "00000000-0000-0000-0000-000000000713"}, + "body": {"name": "canonical-name", "description": "canonical description"}, + "fields": ["name", "description"], + "wire": {"status": 202, "has_data": false, "accepted_only": true} + } + ], + "binding_contract": { + "collection_path": "/zns/api/v1/segments/{segmentUuid}/cloud-bindings", + "exact_path": "/zns/api/v1/segments/{segmentUuid}/cloud-bindings/{computeManagerUuid}/{cloudZoneUuid}", + "active_key": ["segment_uuid", "compute_manager_uuid", "cloud_zone_uuid"], + "active_value": "cloud_l2_uuid", + "cloud_l2_unique_key": ["compute_manager_uuid", "cloud_l2_uuid"], + "put_body": {"cloud_l2_uuid": "00000000-0000-0000-0000-000000000104"}, + "data": { + "segment_uuid": "00000000-0000-0000-0000-000000000101", + "compute_manager_uuid": "00000000-0000-0000-0000-000000000102", + "cloud_zone_uuid": "00000000-0000-0000-0000-000000000103", + "cloud_l2_uuid": "00000000-0000-0000-0000-000000000104", + "create_date": "2026-08-15T00:00:00Z", + "last_op_date": "2026-08-15T00:00:01Z" + }, + "principal_target_match_required": true + }, + "operation_contract": { + "path": "/zns/api/v1/segment-operations/{operationUuid}", + "statuses": ["Preparing", "Committed", "Failed"], + "terminal_statuses": ["Committed", "Failed"], + "terminal_result_immutable": true, + "authorization_scope_required": true, + "data": { + "operation_uuid": "00000000-0000-0000-0000-000000000701", + "segment_uuid": "00000000-0000-0000-0000-000000000101", + "purpose": "PATCH_SEGMENT", + "request_digest": "7fa8b82380eabd7de547dbd61ea16e023bde7d9a26919ebc2ddcee60e83dc47b", + "status": "Committed", + "config_version": 8, + "result": {"success": true, "config_version": 8}, + "create_date": "2026-08-15T00:00:00Z", + "last_op_date": "2026-08-15T00:00:01Z" + }, + "authorization_scope": { + "scope_version": 1, + "principal_type": "COMPUTE_MANAGER", + "principal_uuid": "00000000-0000-0000-0000-000000000102", + "compute_manager_uuids": ["00000000-0000-0000-0000-000000000102"] + } + }, + "fence_contract": { + "command_path": "/zns/notify/check-segment-change", + "phases": ["PREPARE", "COMPLETE", "CANCEL"], + "persisted_phases": ["PREPARING", "PERMITTED", "CANCELLED", "COMPLETED"], + "request": { + "computerManagerUuid": "00000000-0000-0000-0000-000000000102", + "segmentUuid": "00000000-0000-0000-0000-000000000101", + "bindingKeys": [{"zoneUuid": "00000000-0000-0000-0000-000000000103", "cloudL2Uuid": "00000000-0000-0000-0000-000000000104"}], + "operationUuid": "00000000-0000-0000-0000-000000000701", + "phase": "PREPARE", + "expectedConfigVersion": 7, + "changeSet": {} + }, + "replay_key": ["operationUuid", "phase", "bindingDigest"], + "cancel_after_commit_forbidden": true + }, + "delete_contract": { + "check_delete": { + "method": "POST", + "path": "/zns/api/v1/segments/00000000-0000-0000-0000-000000000101/actions/check-delete", + "headers": {"x-job-uuid": "00000000-0000-0000-0000-000000000701"}, + "request": {"cloud_delete_binding": {"compute_manager_uuid": "00000000-0000-0000-0000-000000000102", "zone_uuid": "00000000-0000-0000-0000-000000000103", "cloud_l2_uuid": "00000000-0000-0000-0000-000000000104"}}, + "responses": [ + {"name": "permitted", "status": 200, "body": {"success": true, "data": {"segment_uuid": "00000000-0000-0000-0000-000000000101", "zns_permitted": true, "config_version": 7, "binding_match": true, "blockers": [], "owned_config_digest": "sha256:0123456789abcdef", "port_candidates": [{"segment_port_uuid": "00000000-0000-0000-0000-000000000106", "cms_resource_uuid": "00000000-0000-0000-0000-000000000107"}]}}}, + {"name": "denied", "status": 200, "body": {"success": true, "data": {"segment_uuid": "00000000-0000-0000-0000-000000000101", "zns_permitted": false, "config_version": 7, "binding_match": true, "blockers": [{"type": "ROUTER", "resource_uuid": "00000000-0000-0000-0000-000000000105", "reason": "segment has an external router attachment"}], "owned_config_digest": "sha256:0123456789abcdef", "port_candidates": []}}}, + {"name": "identity_conflict", "status": 409, "body": {"success": false, "code": "SEGMENT.CONFLICT.DELETE_BINDING_MISMATCH", "message": "The exact Cloud delete binding does not match the Segment binding"}} + ] + }, + "final_delete": { + "method": "DELETE", + "path": "/zns/api/v1/segments", + "headers": {"If-Match": "7", "x-job-uuid": "00000000-0000-0000-0000-000000000701"}, + "request": {"uuids": ["00000000-0000-0000-0000-000000000101"], "cloud_delete_binding": {"compute_manager_uuid": "00000000-0000-0000-0000-000000000102", "zone_uuid": "00000000-0000-0000-0000-000000000103", "cloud_l2_uuid": "00000000-0000-0000-0000-000000000104"}}, + "responses": [ + {"name": "success", "status": 202, "body": {"success": true}}, + {"name": "replay_success", "status": 202, "body": {"success": true}}, + {"name": "version_conflict", "status": 409, "body": {"success": false, "code": "SEGMENT.CONFLICT.STALE_VERSION", "message": "The Segment config version changed"}}, + {"name": "missing_if_match", "status": 428, "body": {"success": false, "code": "SEGMENT.INVALID.IF_MATCH_REQUIRED", "message": "If-Match is required for Cloud-authorized Segment deletion"}}, + {"name": "response_loss_retry", "status": 202, "body": {"success": true}}, + {"name": "bare_404_negative", "status": 404, "body": {"success": false, "code": "SEGMENT.NOT_FOUND.ID", "message": "The specified Segment was not found"}} + ] + } + }, + "compatibility_contract": { + "modes": ["LEGACY", "MIGRATING_READ_ONLY", "STRICT", "INCOMPATIBLE_READ_ONLY"], + "wire_contracts": ["LEGACY_V1", "RESOURCE_V2"], + "get_path": "/zns/api/v1/fabric/compute-managers/{computeManagerUuid}", + "transition_path": "/zns/api/v1/fabric/compute-managers/{computeManagerUuid}/compatibility", + "transition_request": {"expected_epoch": 7, "mode": "STRICT"}, + "transition_response": {"success": true, "mode": "STRICT", "epoch": 8}, + "missing_fields_default": {"mode": "LEGACY", "epoch": 0}, + "strict_never_falls_back": true + }, + "outbox_contract": { + "statuses": ["pending", "delivering", "delivered", "failed", "delivery_expired"], + "retryable_statuses": [408, 425, 429, 500, 502, 503, 504], + "max_attempts": 12, + "max_age_seconds": 86400, + "request_timeout_seconds": 10, + "lease_seconds": 30, + "backoff_cap_seconds": 60, + "unique_key": ["subscriber", "resourceType", "externalId", "deliveryKey"], + "resource_v2": { + "command_path": "/zns/notify/sync-resource", + "wire_contract": "RESOURCE_V2", + "body": {"computerManagerUuid": "00000000-0000-0000-0000-000000000102", "resourceType": "SEGMENT", "action": "UPDATE", "resourceUuid": "00000000-0000-0000-0000-000000000101"}, + "delivery_key": "config:8", + "mode_epoch": 8 + }, + "legacy_v1": { + "command_path": "/zns/notify/sync-fabric", + "wire_contract": "LEGACY_V1", + "body": {"computerManagerUuid": "00000000-0000-0000-0000-000000000102", "scope": "fabric"}, + "mode_epoch": 0 + }, + "wire_frozen_after_enqueue": true, + "fallback_or_dual_delivery_forbidden": true + }, + "cloud_state_contract": { + "controller_state": { + "primary_key": ["sdnControllerUuid"], + "migration_states": ["NotStarted", "InventoryScanning", "RelationMigrating", "Ready"], + "compatibility_modes": ["LEGACY", "MIGRATING_READ_ONLY", "STRICT", "INCOMPATIBLE_READ_ONLY"] + }, + "segment_inventory": { + "unique_key": ["sdnControllerUuid", "znsSegmentUuid"], + "required_fields": ["uuid", "sdnControllerUuid", "znsSegmentUuid", "name", "transportZoneUuid", "availability", "lastSeenToken", "lastSeenDate"] + }, + "segment_ref": { + "unique_keys": [["sdnControllerUuid", "znsSegmentUuid"], ["l2NetworkUuid"]], + "states": ["Initializing", "AwaitL3", "Syncing", "Ready", "Error", "PlacementConflict", "SourceUnavailable", "PreparingChange", "Deleting", "DeleteError", "MigrationFailed", "UpgradeSyncError"] + }, + "operation": { + "primary_key": ["operation_uuid"], + "statuses": ["Preparing", "Committed", "Failed"], + "authorization_scope_required": true + } + }, + "error_codes": [ + "SEGMENT.CONFLICT.STALE_VERSION", + "SEGMENT.CONFLICT.OPERATION_REPLAY", + "SEGMENT.CONFLICT.CLOUD_FENCE_REJECTED", + "SEGMENT.INVALID.IF_MATCH_REQUIRED", + "SEGMENT.CONFLICT.DELETE_BINDING_MISMATCH", + "SEGMENT.CONFLICT.DELETE_DEPENDENCY", + "SEGMENT_CLOUD_BINDING.NOT_FOUND.ID", + "SEGMENT_CLOUD_BINDING.CONFLICT.IDENTITY", + "SEGMENT.NOT_FOUND.ID", + "COMPUTE_MANAGER.CONFLICT.COMPATIBILITY_EPOCH", + "ORG_ZSTACK_NETWORK_ZNS_10072" + ], + "wire": {"error_code": "", "retryable": false} +} diff --git a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy index 74c3ac573dc..2d3d0ec2289 100644 --- a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy +++ b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy @@ -24425,6 +24425,33 @@ abstract class ApiHelper { } + def getModelServiceLaunchCommands(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.GetModelServiceLaunchCommandsAction.class) Closure c) { + def a = new org.zstack.sdk.GetModelServiceLaunchCommandsAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def getMonitorItem(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.GetMonitorItemAction.class) Closure c) { def a = new org.zstack.sdk.GetMonitorItemAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -29015,6 +29042,33 @@ abstract class ApiHelper { } + def pullSdnController(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.PullSdnControllerAction.class) Closure c) { + def a = new org.zstack.sdk.PullSdnControllerAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def pullSdnControllerTenant(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.PullSdnControllerTenantAction.class) Closure c) { def a = new org.zstack.sdk.PullSdnControllerTenantAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -48553,6 +48607,7 @@ abstract class ApiHelper { c.delegate = a c() + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid @@ -52035,6 +52090,33 @@ abstract class ApiHelper { } + def setIAM2RoleScope(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.iam2.api.SetIAM2RoleScopeAction.class) Closure c) { + def a = new org.zstack.sdk.iam2.api.SetIAM2RoleScopeAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def setOrganizationOperation(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.iam2.api.SetOrganizationOperationAction.class) Closure c) { def a = new org.zstack.sdk.iam2.api.SetOrganizationOperationAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -53227,6 +53309,35 @@ abstract class ApiHelper { } + def queryZnsSegmentCloudProjection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.network.zns.QueryZnsSegmentCloudProjectionAction.class) Closure c) { + def a = new org.zstack.sdk.network.zns.QueryZnsSegmentCloudProjectionAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + a.conditions = a.conditions.collect { it.toString() } + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def queryZnsTenant(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.network.zns.QueryZnsTenantAction.class) Closure c) { def a = new org.zstack.sdk.network.zns.QueryZnsTenantAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -54915,6 +55026,7 @@ abstract class ApiHelper { c.delegate = a c() + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid diff --git a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java index 9fc175de2c1..4940d6cde63 100644 --- a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java +++ b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java @@ -995,6 +995,26 @@ public class CloudOperationsErrorCode { public static final String ORG_ZSTACK_NETWORK_L3_10081 = "ORG_ZSTACK_NETWORK_L3_10081"; public static final String ORG_ZSTACK_NETWORK_L3_10082 = "ORG_ZSTACK_NETWORK_L3_10082"; + public static final String ORG_ZSTACK_NETWORK_L3_10083 = "ORG_ZSTACK_NETWORK_L3_10083"; + public static final String ORG_ZSTACK_NETWORK_L3_10084 = "ORG_ZSTACK_NETWORK_L3_10084"; + public static final String ORG_ZSTACK_NETWORK_L3_10085 = "ORG_ZSTACK_NETWORK_L3_10085"; + public static final String ORG_ZSTACK_NETWORK_L3_10086 = "ORG_ZSTACK_NETWORK_L3_10086"; + public static final String ORG_ZSTACK_NETWORK_L3_10087 = "ORG_ZSTACK_NETWORK_L3_10087"; + public static final String ORG_ZSTACK_NETWORK_L3_10088 = "ORG_ZSTACK_NETWORK_L3_10088"; + public static final String ORG_ZSTACK_NETWORK_L3_10089 = "ORG_ZSTACK_NETWORK_L3_10089"; + public static final String ORG_ZSTACK_NETWORK_L3_10090 = "ORG_ZSTACK_NETWORK_L3_10090"; + public static final String ORG_ZSTACK_NETWORK_L3_10091 = "ORG_ZSTACK_NETWORK_L3_10091"; + + public static final String ORG_ZSTACK_NETWORK_L3_10092 = "ORG_ZSTACK_NETWORK_L3_10092"; + + public static final String ORG_ZSTACK_NETWORK_L3_10093 = "ORG_ZSTACK_NETWORK_L3_10093"; + public static final String ORG_ZSTACK_NETWORK_L3_10094 = "ORG_ZSTACK_NETWORK_L3_10094"; + public static final String ORG_ZSTACK_NETWORK_L3_10095 = "ORG_ZSTACK_NETWORK_L3_10095"; + public static final String ORG_ZSTACK_NETWORK_L3_10096 = "ORG_ZSTACK_NETWORK_L3_10096"; + public static final String ORG_ZSTACK_NETWORK_L3_10097 = "ORG_ZSTACK_NETWORK_L3_10097"; + public static final String ORG_ZSTACK_NETWORK_L3_10098 = "ORG_ZSTACK_NETWORK_L3_10098"; + public static final String ORG_ZSTACK_NETWORK_L3_10099 = "ORG_ZSTACK_NETWORK_L3_10099"; + public static final String ORG_ZSTACK_NETWORK_L3_10100 = "ORG_ZSTACK_NETWORK_L3_10100"; public static final String ORG_ZSTACK_SNS_PLATFORM_UNIVERSALSMS_SUPPLIER_EMAY_10000 = "ORG_ZSTACK_SNS_PLATFORM_UNIVERSALSMS_SUPPLIER_EMAY_10000"; @@ -1044,6 +1064,16 @@ public class CloudOperationsErrorCode { public static final String ORG_ZSTACK_NETWORK_L2_10021 = "ORG_ZSTACK_NETWORK_L2_10021"; + public static final String ORG_ZSTACK_NETWORK_L2_10022 = "ORG_ZSTACK_NETWORK_L2_10022"; + + public static final String ORG_ZSTACK_NETWORK_L2_10023 = "ORG_ZSTACK_NETWORK_L2_10023"; + + public static final String ORG_ZSTACK_NETWORK_L2_10024 = "ORG_ZSTACK_NETWORK_L2_10024"; + public static final String ORG_ZSTACK_NETWORK_L2_10025 = "ORG_ZSTACK_NETWORK_L2_10025"; + public static final String ORG_ZSTACK_NETWORK_L2_10026 = "ORG_ZSTACK_NETWORK_L2_10026"; + public static final String ORG_ZSTACK_NETWORK_L2_10027 = "ORG_ZSTACK_NETWORK_L2_10027"; + public static final String ORG_ZSTACK_NETWORK_L2_10028 = "ORG_ZSTACK_NETWORK_L2_10028"; + public static final String ORG_ZSTACK_CONSOLE_10000 = "ORG_ZSTACK_CONSOLE_10000"; public static final String ORG_ZSTACK_CONSOLE_10001 = "ORG_ZSTACK_CONSOLE_10001"; @@ -1618,6 +1648,9 @@ public class CloudOperationsErrorCode { public static final String ORG_ZSTACK_CORE_10001 = "ORG_ZSTACK_CORE_10001"; + public static final String ORG_ZSTACK_CORE_10002 = "ORG_ZSTACK_CORE_10002"; + public static final String ORG_ZSTACK_CORE_10003 = "ORG_ZSTACK_CORE_10003"; + public static final String ORG_ZSTACK_VMWARE_CASCADE_10000 = "ORG_ZSTACK_VMWARE_CASCADE_10000"; public static final String ORG_ZSTACK_VMWARE_CASCADE_10001 = "ORG_ZSTACK_VMWARE_CASCADE_10001"; @@ -12240,6 +12273,7 @@ public class CloudOperationsErrorCode { // 10046 ZNS DHCP 409 fallback GET returned empty // 10047 ZNS DHCP update has no Cloud-reserved DHCPv4 server IP // 10048 failed to allocate DHCPv4 server IP in ZNS DHCP backend + // 10051 public ZNS L3 requires a VLAN-backed Basic network public static final String ORG_ZSTACK_NETWORK_ZNS_10035 = "ORG_ZSTACK_NETWORK_ZNS_10035"; public static final String ORG_ZSTACK_NETWORK_ZNS_10036 = "ORG_ZSTACK_NETWORK_ZNS_10036"; public static final String ORG_ZSTACK_NETWORK_ZNS_10037 = "ORG_ZSTACK_NETWORK_ZNS_10037"; @@ -12254,6 +12288,146 @@ public class CloudOperationsErrorCode { public static final String ORG_ZSTACK_NETWORK_ZNS_10046 = "ORG_ZSTACK_NETWORK_ZNS_10046"; public static final String ORG_ZSTACK_NETWORK_ZNS_10047 = "ORG_ZSTACK_NETWORK_ZNS_10047"; public static final String ORG_ZSTACK_NETWORK_ZNS_10048 = "ORG_ZSTACK_NETWORK_ZNS_10048"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10051 = "ORG_ZSTACK_NETWORK_ZNS_10051"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10052 = "ORG_ZSTACK_NETWORK_ZNS_10052"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10053 = "ORG_ZSTACK_NETWORK_ZNS_10053"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10054 = "ORG_ZSTACK_NETWORK_ZNS_10054"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10055 = "ORG_ZSTACK_NETWORK_ZNS_10055"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10056 = "ORG_ZSTACK_NETWORK_ZNS_10056"; + // 10057 targeted Tenant Router pull completed without a local projection + // 10058 targeted Tenant Router pull returned an unknown projection state + public static final String ORG_ZSTACK_NETWORK_ZNS_10057 = "ORG_ZSTACK_NETWORK_ZNS_10057"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10058 = "ORG_ZSTACK_NETWORK_ZNS_10058"; + // 10059 ZNS L2 owner change found a non-Ready relation + // 10060 ZNS L2 owner change found an active or unknown operation step + // 10061 ZNS L2 owner change found an invalid L2 ownership reference count + // 10062 ZNS L2 owner change found an invalid relation ownership reference count + // 10063 ZNS L2 and relation ownership records are inconsistent + // 10064 account cascade cannot prove the outcome of a ZNS reservation + // 10065 account cascade failed to compensate an exact ZNS binding + public static final String ORG_ZSTACK_NETWORK_ZNS_10059 = "ORG_ZSTACK_NETWORK_ZNS_10059"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10060 = "ORG_ZSTACK_NETWORK_ZNS_10060"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10061 = "ORG_ZSTACK_NETWORK_ZNS_10061"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10062 = "ORG_ZSTACK_NETWORK_ZNS_10062"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10063 = "ORG_ZSTACK_NETWORK_ZNS_10063"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10064 = "ORG_ZSTACK_NETWORK_ZNS_10064"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10065 = "ORG_ZSTACK_NETWORK_ZNS_10065"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10066 = "ORG_ZSTACK_NETWORK_ZNS_10066"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10067 = "ORG_ZSTACK_NETWORK_ZNS_10067"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10068 = "ORG_ZSTACK_NETWORK_ZNS_10068"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10069 = "ORG_ZSTACK_NETWORK_ZNS_10069"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10070 = "ORG_ZSTACK_NETWORK_ZNS_10070"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10071 = "ORG_ZSTACK_NETWORK_ZNS_10071"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10072 = "ORG_ZSTACK_NETWORK_ZNS_10072"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10073 = "ORG_ZSTACK_NETWORK_ZNS_10073"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10074 = "ORG_ZSTACK_NETWORK_ZNS_10074"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10075 = "ORG_ZSTACK_NETWORK_ZNS_10075"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10076 = "ORG_ZSTACK_NETWORK_ZNS_10076"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10077 = "ORG_ZSTACK_NETWORK_ZNS_10077"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10078 = "ORG_ZSTACK_NETWORK_ZNS_10078"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10079 = "ORG_ZSTACK_NETWORK_ZNS_10079"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10080 = "ORG_ZSTACK_NETWORK_ZNS_10080"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10081 = "ORG_ZSTACK_NETWORK_ZNS_10081"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10082 = "ORG_ZSTACK_NETWORK_ZNS_10082"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10083 = "ORG_ZSTACK_NETWORK_ZNS_10083"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10084 = "ORG_ZSTACK_NETWORK_ZNS_10084"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10085 = "ORG_ZSTACK_NETWORK_ZNS_10085"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10086 = "ORG_ZSTACK_NETWORK_ZNS_10086"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10087 = "ORG_ZSTACK_NETWORK_ZNS_10087"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10088 = "ORG_ZSTACK_NETWORK_ZNS_10088"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10089 = "ORG_ZSTACK_NETWORK_ZNS_10089"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10090 = "ORG_ZSTACK_NETWORK_ZNS_10090"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10091 = "ORG_ZSTACK_NETWORK_ZNS_10091"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10092 = "ORG_ZSTACK_NETWORK_ZNS_10092"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10093 = "ORG_ZSTACK_NETWORK_ZNS_10093"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10094 = "ORG_ZSTACK_NETWORK_ZNS_10094"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10095 = "ORG_ZSTACK_NETWORK_ZNS_10095"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10096 = "ORG_ZSTACK_NETWORK_ZNS_10096"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10097 = "ORG_ZSTACK_NETWORK_ZNS_10097"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10098 = "ORG_ZSTACK_NETWORK_ZNS_10098"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10099 = "ORG_ZSTACK_NETWORK_ZNS_10099"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10100 = "ORG_ZSTACK_NETWORK_ZNS_10100"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10101 = "ORG_ZSTACK_NETWORK_ZNS_10101"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10102 = "ORG_ZSTACK_NETWORK_ZNS_10102"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10103 = "ORG_ZSTACK_NETWORK_ZNS_10103"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10104 = "ORG_ZSTACK_NETWORK_ZNS_10104"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10105 = "ORG_ZSTACK_NETWORK_ZNS_10105"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10106 = "ORG_ZSTACK_NETWORK_ZNS_10106"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10107 = "ORG_ZSTACK_NETWORK_ZNS_10107"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10108 = "ORG_ZSTACK_NETWORK_ZNS_10108"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10109 = "ORG_ZSTACK_NETWORK_ZNS_10109"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10110 = "ORG_ZSTACK_NETWORK_ZNS_10110"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10111 = "ORG_ZSTACK_NETWORK_ZNS_10111"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10112 = "ORG_ZSTACK_NETWORK_ZNS_10112"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10113 = "ORG_ZSTACK_NETWORK_ZNS_10113"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10114 = "ORG_ZSTACK_NETWORK_ZNS_10114"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10115 = "ORG_ZSTACK_NETWORK_ZNS_10115"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10116 = "ORG_ZSTACK_NETWORK_ZNS_10116"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10117 = "ORG_ZSTACK_NETWORK_ZNS_10117"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10118 = "ORG_ZSTACK_NETWORK_ZNS_10118"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10119 = "ORG_ZSTACK_NETWORK_ZNS_10119"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10120 = "ORG_ZSTACK_NETWORK_ZNS_10120"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10121 = "ORG_ZSTACK_NETWORK_ZNS_10121"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10122 = "ORG_ZSTACK_NETWORK_ZNS_10122"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10123 = "ORG_ZSTACK_NETWORK_ZNS_10123"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10124 = "ORG_ZSTACK_NETWORK_ZNS_10124"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10125 = "ORG_ZSTACK_NETWORK_ZNS_10125"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10126 = "ORG_ZSTACK_NETWORK_ZNS_10126"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10127 = "ORG_ZSTACK_NETWORK_ZNS_10127"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10128 = "ORG_ZSTACK_NETWORK_ZNS_10128"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10129 = "ORG_ZSTACK_NETWORK_ZNS_10129"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10130 = "ORG_ZSTACK_NETWORK_ZNS_10130"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10131 = "ORG_ZSTACK_NETWORK_ZNS_10131"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10132 = "ORG_ZSTACK_NETWORK_ZNS_10132"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10133 = "ORG_ZSTACK_NETWORK_ZNS_10133"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10134 = "ORG_ZSTACK_NETWORK_ZNS_10134"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10135 = "ORG_ZSTACK_NETWORK_ZNS_10135"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10136 = "ORG_ZSTACK_NETWORK_ZNS_10136"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10137 = "ORG_ZSTACK_NETWORK_ZNS_10137"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10138 = "ORG_ZSTACK_NETWORK_ZNS_10138"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10139 = "ORG_ZSTACK_NETWORK_ZNS_10139"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10140 = "ORG_ZSTACK_NETWORK_ZNS_10140"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10141 = "ORG_ZSTACK_NETWORK_ZNS_10141"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10142 = "ORG_ZSTACK_NETWORK_ZNS_10142"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10143 = "ORG_ZSTACK_NETWORK_ZNS_10143"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10144 = "ORG_ZSTACK_NETWORK_ZNS_10144"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10145 = "ORG_ZSTACK_NETWORK_ZNS_10145"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10146 = "ORG_ZSTACK_NETWORK_ZNS_10146"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10147 = "ORG_ZSTACK_NETWORK_ZNS_10147"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10148 = "ORG_ZSTACK_NETWORK_ZNS_10148"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10149 = "ORG_ZSTACK_NETWORK_ZNS_10149"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10150 = "ORG_ZSTACK_NETWORK_ZNS_10150"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10151 = "ORG_ZSTACK_NETWORK_ZNS_10151"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10152 = "ORG_ZSTACK_NETWORK_ZNS_10152"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10153 = "ORG_ZSTACK_NETWORK_ZNS_10153"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10154 = "ORG_ZSTACK_NETWORK_ZNS_10154"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10155 = "ORG_ZSTACK_NETWORK_ZNS_10155"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10156 = "ORG_ZSTACK_NETWORK_ZNS_10156"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10157 = "ORG_ZSTACK_NETWORK_ZNS_10157"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10158 = "ORG_ZSTACK_NETWORK_ZNS_10158"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10159 = "ORG_ZSTACK_NETWORK_ZNS_10159"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10160 = "ORG_ZSTACK_NETWORK_ZNS_10160"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10161 = "ORG_ZSTACK_NETWORK_ZNS_10161"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10162 = "ORG_ZSTACK_NETWORK_ZNS_10162"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10163 = "ORG_ZSTACK_NETWORK_ZNS_10163"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10164 = "ORG_ZSTACK_NETWORK_ZNS_10164"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10165 = "ORG_ZSTACK_NETWORK_ZNS_10165"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10166 = "ORG_ZSTACK_NETWORK_ZNS_10166"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10167 = "ORG_ZSTACK_NETWORK_ZNS_10167"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10168 = "ORG_ZSTACK_NETWORK_ZNS_10168"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10169 = "ORG_ZSTACK_NETWORK_ZNS_10169"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10170 = "ORG_ZSTACK_NETWORK_ZNS_10170"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10171 = "ORG_ZSTACK_NETWORK_ZNS_10171"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10172 = "ORG_ZSTACK_NETWORK_ZNS_10172"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10173 = "ORG_ZSTACK_NETWORK_ZNS_10173"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10174 = "ORG_ZSTACK_NETWORK_ZNS_10174"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10175 = "ORG_ZSTACK_NETWORK_ZNS_10175"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10176 = "ORG_ZSTACK_NETWORK_ZNS_10176"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10177 = "ORG_ZSTACK_NETWORK_ZNS_10177"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10178 = "ORG_ZSTACK_NETWORK_ZNS_10178"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10179 = "ORG_ZSTACK_NETWORK_ZNS_10179"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10180 = "ORG_ZSTACK_NETWORK_ZNS_10180"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10181 = "ORG_ZSTACK_NETWORK_ZNS_10181"; public static final String ORG_ZSTACK_PREMIUM_EXTERNALSERVICE_MARKETPLACE_10000 = "ORG_ZSTACK_PREMIUM_EXTERNALSERVICE_MARKETPLACE_10000"; @@ -15741,6 +15915,13 @@ public class CloudOperationsErrorCode { public static final String ORG_ZSTACK_SDNCONTROLLER_10033 = "ORG_ZSTACK_SDNCONTROLLER_10033"; + public static final String ORG_ZSTACK_SDNCONTROLLER_10034 = "ORG_ZSTACK_SDNCONTROLLER_10034"; + public static final String ORG_ZSTACK_SDNCONTROLLER_10035 = "ORG_ZSTACK_SDNCONTROLLER_10035"; + public static final String ORG_ZSTACK_SDNCONTROLLER_10036 = "ORG_ZSTACK_SDNCONTROLLER_10036"; + public static final String ORG_ZSTACK_SDNCONTROLLER_10037 = "ORG_ZSTACK_SDNCONTROLLER_10037"; + public static final String ORG_ZSTACK_SDNCONTROLLER_10038 = "ORG_ZSTACK_SDNCONTROLLER_10038"; + public static final String ORG_ZSTACK_SDNCONTROLLER_10039 = "ORG_ZSTACK_SDNCONTROLLER_10039"; + public static final String ORG_ZSTACK_TEST_INTEGRATION_PREMIUM_ZSV_SNAPSHOT_10000 = "ORG_ZSTACK_TEST_INTEGRATION_PREMIUM_ZSV_SNAPSHOT_10000"; public static final String ORG_ZSTACK_TEST_INTEGRATION_PREMIUM_ZSV_SNAPSHOT_10001 = "ORG_ZSTACK_TEST_INTEGRATION_PREMIUM_ZSV_SNAPSHOT_10001"; From 4f71d28eeb5030c13734d866500a3217297c26c5 Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Sun, 16 Aug 2026 06:47:13 +0900 Subject: [PATCH 2/7] [zns]: convert ZNS contract cases Resolves: ZCF-5485 Change-Id: I545596b90f9f2a9df0e385ad40778f264a370234 --- .../rest/Zcf5485RestStatusContractCase.groovy | 99 ------ .../Zcf5485SdnPullContractCase.groovy | 29 -- .../sdnController/SdnControllerCase.groovy | 17 - .../Zcf5485CanonicalContractCase.groovy | 47 --- .../network/Zcf5485NetworkContextCase.groovy | 142 -------- .../test/network/TestSdnControllerL3.java | 52 --- .../src/test/resources/zns/zcf5485/SHA256SUMS | 1 - .../zns/zcf5485/segment-cloud-contract.json | 330 ------------------ 8 files changed, 717 deletions(-) delete mode 100644 test/src/test/groovy/org/zstack/core/rest/Zcf5485RestStatusContractCase.groovy delete mode 100644 test/src/test/groovy/org/zstack/sdnController/Zcf5485SdnPullContractCase.groovy delete mode 100644 test/src/test/groovy/org/zstack/test/unittest/network/Zcf5485CanonicalContractCase.groovy delete mode 100644 test/src/test/groovy/org/zstack/test/unittest/network/Zcf5485NetworkContextCase.groovy delete mode 100644 test/src/test/java/org/zstack/test/network/TestSdnControllerL3.java delete mode 100644 test/src/test/resources/zns/zcf5485/SHA256SUMS delete mode 100644 test/src/test/resources/zns/zcf5485/segment-cloud-contract.json diff --git a/test/src/test/groovy/org/zstack/core/rest/Zcf5485RestStatusContractCase.groovy b/test/src/test/groovy/org/zstack/core/rest/Zcf5485RestStatusContractCase.groovy deleted file mode 100644 index 749e30850e2..00000000000 --- a/test/src/test/groovy/org/zstack/core/rest/Zcf5485RestStatusContractCase.groovy +++ /dev/null @@ -1,99 +0,0 @@ -package org.zstack.core.rest - -import org.junit.Test -import org.zstack.core.rest.webhook.WebhookCallbackClient -import org.zstack.core.rest.webhook.WebhookProtocol -import org.zstack.core.thread.ThreadFacade -import org.zstack.core.thread.ThreadFacadeImpl -import org.zstack.header.core.ReturnValueCompletion -import org.zstack.header.errorcode.ErrorCode -import org.zstack.header.rest.RESTFacade -import org.zstack.header.rest.SyncHttpResponse - -import javax.servlet.http.HttpServletResponse -import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicInteger - -import static org.mockito.ArgumentMatchers.any -import static org.mockito.ArgumentMatchers.anyLong -import static org.mockito.Mockito.mock -import static org.mockito.Mockito.verify -import static org.mockito.Mockito.when - -class Zcf5485RestStatusContractCase { - @Test - void statusAndBodyAreWrittenWithoutCollapsingNon2xxToTransportFailure() { - HttpServletResponse response = mock(HttpServletResponse.class) - StringWriter body = new StringWriter() - when(response.writer).thenReturn(new PrintWriter(body)) - - RESTFacadeImpl.writeSyncHttpResponse( - new SyncHttpResponse(503, '{"condition":"receiver-overloaded"}'), response) - - verify(response).setStatus(503) - verify(response).setHeader('Content-Type', 'application/json') - assert body.toString() == '{"condition":"receiver-overloaded"}' - } - - @Test - void nullResponseIsAcceptedAsEmptyHttp200() { - HttpServletResponse response = mock(HttpServletResponse.class) - RESTFacadeImpl.writeSyncHttpResponse(null, response) - verify(response).setStatus(200) - } - - @Test - void webhookTaskIsRegisteredBeforeTimeoutScheduling() { - ThreadFacade threadFacade = mock(ThreadFacade.class) - def receipt = mock(ThreadFacadeImpl.TimeoutTaskReceipt.class) - def scheduled = new AtomicInteger() - def registeredAtSchedule = new AtomicBoolean() - WebhookCallbackClient client - when(threadFacade.submitTimeoutTask(any(Runnable.class), any(TimeUnit.class), anyLong())) - .thenAnswer { invocation -> - scheduled.incrementAndGet() - def field = WebhookCallbackClient.getDeclaredField("pendingCalls") - field.accessible = true - registeredAtSchedule.set((field.get(client) as Map).containsKey("same-task")) - receipt - } - WebhookProtocol protocol = new WebhookProtocol() { - @Override - String getCallbackPath() { "/test-webhook" } - - @Override - Class getCallbackClass() { Map.class } - - @Override - String extractTaskId(Map callback) { callback.taskId } - - @Override - boolean isSuccess(Map callback) { true } - - @Override - String extractError(Map callback) { null } - - @Override - void decorateRequest(Map headers, String taskId, String callbackUrl) { } - } - client = new WebhookCallbackClient( - protocol, mock(RESTFacade.class), threadFacade) - def successCount = new AtomicInteger() - def failureCount = new AtomicInteger() - def original = new ReturnValueCompletion(null) { - @Override - void success(Map value) { successCount.incrementAndGet() } - - @Override - void fail(ErrorCode errorCode) { failureCount.incrementAndGet() } - } - client.submit("same-task", original, TimeUnit.MILLISECONDS, 0) - client.deliverCallback([taskId: "same-task"]) - - assert scheduled.get() == 1 - assert registeredAtSchedule.get() - assert successCount.get() == 1 - assert failureCount.get() == 0 - } -} diff --git a/test/src/test/groovy/org/zstack/sdnController/Zcf5485SdnPullContractCase.groovy b/test/src/test/groovy/org/zstack/sdnController/Zcf5485SdnPullContractCase.groovy deleted file mode 100644 index aa4efc22255..00000000000 --- a/test/src/test/groovy/org/zstack/sdnController/Zcf5485SdnPullContractCase.groovy +++ /dev/null @@ -1,29 +0,0 @@ -package org.zstack.sdnController - -import org.junit.Test - -class Zcf5485SdnPullContractCase { - @Test - void resourceUuidsAreValidatedAndDeduplicatedBeforeRouting() { - String znsUuid = 'dc9905ec-e6fc-4122-8861-7f56decafcaa' - String cloudUuid = 'dc9905ece6fc412288617f56decafcaa' - - assert SdnControllerApiInterceptor.normalizeResourceUuids( - [znsUuid, znsUuid, cloudUuid]) == [znsUuid, cloudUuid] - assert SdnControllerApiInterceptor.normalizeResourceUuids([]).isEmpty() - assert SdnControllerApiInterceptor.normalizeResourceUuids(null) == null - - try { - SdnControllerApiInterceptor.normalizeResourceUuids(['not-a-uuid']) - assert false: 'invalid resource UUID must be rejected' - } catch (IllegalArgumentException ignored) { - } - } - - @Test - void duplicateUuidsDoNotConsumeTheOneHundredResourceLimit() { - String uuid = 'dc9905ec-e6fc-4122-8861-7f56decafcaa' - assert SdnControllerApiInterceptor.normalizeResourceUuids( - Collections.nCopies(101, uuid)) == [uuid] - } -} diff --git a/test/src/test/groovy/org/zstack/test/integration/network/sdnController/SdnControllerCase.groovy b/test/src/test/groovy/org/zstack/test/integration/network/sdnController/SdnControllerCase.groovy index 49946caf631..c07c9c3314c 100644 --- a/test/src/test/groovy/org/zstack/test/integration/network/sdnController/SdnControllerCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/network/sdnController/SdnControllerCase.groovy @@ -360,23 +360,6 @@ class SdnControllerCase extends SubCase { conditions=["type=" + SdnControllerConstant.HARDWARE_VXLAN_NETWORK_TYPE]} assert vxlanNetworks.size() == 2 - expectError { - pullSdnController { - uuid = sdn2.uuid - resourceType = "Segment" - resourceUuids = ["dc9905ec-e6fc-4122-8861-7f56decafcaa"] - } - } - assert queryL2Network { conditions=["type=" + SdnControllerConstant.HARDWARE_VXLAN_NETWORK_TYPE] }.size() == 2 - - expectError { - pullSdnController { - uuid = sdn2.uuid - resourceType = "Segment" - resourceUuids = ["not-a-uuid"] - } - } - removeSdnController { uuid = sdn2.uuid } diff --git a/test/src/test/groovy/org/zstack/test/unittest/network/Zcf5485CanonicalContractCase.groovy b/test/src/test/groovy/org/zstack/test/unittest/network/Zcf5485CanonicalContractCase.groovy deleted file mode 100644 index 7c8c7fe6c51..00000000000 --- a/test/src/test/groovy/org/zstack/test/unittest/network/Zcf5485CanonicalContractCase.groovy +++ /dev/null @@ -1,47 +0,0 @@ -package org.zstack.test.unittest.network - -import groovy.json.JsonSlurper -import org.junit.Test - -import java.nio.charset.StandardCharsets -import java.security.MessageDigest - -class Zcf5485CanonicalContractCase { - @Test - void coreConsumesCanonicalSegmentCloudContract() { - File fixture = new File('src/test/resources/zns/zcf5485/segment-cloud-contract.json') - File checksums = new File('src/test/resources/zns/zcf5485/SHA256SUMS') - assert fixture.isFile() - assert checksums.isFile() - assert sha256(fixture.bytes) == checksums.text.trim().split(/\s+/)[0] - - def contract = new JsonSlurper().parse(fixture) - assert contract.contract == 'ZCF-5485' - assert contract.revision == 'zcf5485-final-v3' - assert contract.version == 3 - - assert contract.cloud_api.pull.message == 'APIPullSdnControllerMsg' - assert contract.cloud_api.pull.event == 'APIPullSdnControllerEvent' - assert contract.cloud_api.pull.supported_resource_types == ['Segment', 'TenantRouter'] - assert contract.cloud_api.pull.resource_uuid_limit == 100 - assert contract.cloud_api.pull.full_refresh_when_uuids_empty - assert contract.cloud_api.projection_query.side_effect_free - assert contract.cloud_api.projection_query.required_inventory_fields.containsAll([ - 'znsSegmentUuid', 'sdnControllerUuid', 'zoneUuid', 'l2NetworkUuid', - 'l2Network', 'state', 'createDate', 'lastOpDate' - ]) - - def digest = contract.digest_vectors[0] - assert sha256(digest.canonical_request.getBytes(StandardCharsets.UTF_8)) == digest.sha256 - assert contract.child_operation_uuid_vectors*.derived_uuid_v3 == [ - '07c1e718-28c8-3e0a-b2cc-44212e14c3ad', - '814a7757-32b3-3782-9fff-895abe559f06' - ] - } - - private static String sha256(byte[] bytes) { - MessageDigest.getInstance('SHA-256').digest(bytes).collect { - String.format('%02x', it & 0xff) - }.join() - } -} diff --git a/test/src/test/groovy/org/zstack/test/unittest/network/Zcf5485NetworkContextCase.groovy b/test/src/test/groovy/org/zstack/test/unittest/network/Zcf5485NetworkContextCase.groovy deleted file mode 100644 index 8f2d3b6acf6..00000000000 --- a/test/src/test/groovy/org/zstack/test/unittest/network/Zcf5485NetworkContextCase.groovy +++ /dev/null @@ -1,142 +0,0 @@ -package org.zstack.test.unittest.network - -import org.junit.Test -import org.zstack.header.core.Completion -import org.zstack.header.core.ReturnValueCompletion -import org.zstack.header.errorcode.ErrorCode -import org.zstack.header.network.NetworkConfigMutation -import org.zstack.header.network.l2.ExternalNetworkRef -import org.zstack.header.network.l2.NetworkCreateContext -import org.zstack.header.network.l2.NetworkDeletionContext -import org.zstack.header.network.l2.NetworkOperationOrigin -import org.zstack.header.network.l3.APIAddIpRangeMsg -import org.zstack.header.network.l3.IpRangeInventory -import org.zstack.header.network.l3.L3NetworkInventory -import org.zstack.header.network.l3.SdnControllerL3 -import org.zstack.network.l3.AddressPoolIpRangeFactory -import org.zstack.network.l3.NormalIpRangeFactory - -import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_NETWORK_L3_10092 -import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_NETWORK_L3_10093 - -class Zcf5485NetworkContextCase { - @Test - void creationContextCarriesCommitAndContinuationEvidence() { - def api = NetworkCreateContext.api() - assert api.origin == NetworkOperationOrigin.API - assert !api.remoteCommitted - assert api.expectedConfigVersion == null - assert api.continuationStep == null - - def committed = NetworkCreateContext.cloudCommit( - 'operation-1', 7L, NetworkCreateContext.APPLY_LOCAL_STEP) - assert committed.origin == NetworkOperationOrigin.CLOUD_COMMIT - assert committed.remoteCommitted - assert committed.expectedConfigVersion == 7L - assert committed.continuationStep == NetworkCreateContext.APPLY_LOCAL_STEP - assert committed.operationStep == committed.continuationStep - - def projected = NetworkCreateContext.projection( - NetworkOperationOrigin.ZNS_REFRESH, - new ExternalNetworkRef('segment-1', 'account-1'), - 'operation-2', 9L, 'REPAIR_LOCAL') - assert projected.projection - assert projected.remoteWriteSuppressed - assert projected.remoteCommitted - assert projected.expectedConfigVersion == 9L - assert projected.continuationStep == 'REPAIR_LOCAL' - } - - @Test - void deletionContextCarriesCommitAndContinuationEvidence() { - def context = new NetworkDeletionContext( - NetworkDeletionContext.Origin.WHOLE_L2_SEGMENT_DELETE, - 'operation-3', 'l2-1', 'L2NetworkVO') - context.expectedConfigVersion = 11L - context.remoteCommitted = true - context.continuationStep = 'DELETE_LOCAL' - - assert context.expectedConfigVersion == 11L - assert context.remoteCommitted - assert context.continuationStep == 'DELETE_LOCAL' - } - - @Test - void metadataMutationCarriesExactBeforeLocalCommitTarget() { - def mutation = NetworkConfigMutation.metadata( - 'l2-1', NetworkOperationOrigin.API, 'operation-4', 'account-1', - 'new-name', 'new-description') - - assert mutation.kind == NetworkConfigMutation.Kind.L2_METADATA - assert mutation.l2Uuid == 'l2-1' - assert mutation.operationUuid == 'operation-4' - assert mutation.accountUuid == 'account-1' - assert mutation.metadata.name == 'new-name' - assert mutation.metadata.description == 'new-description' - } - - @Test - void ipRangeFactoriesRejectMissingAccountBeforePersistence() { - ErrorCode normalError - new NormalIpRangeFactory().createIpRange( - [new IpRangeInventory()], new APIAddIpRangeMsg(), null, - new ReturnValueCompletion>(null) { - @Override - void success(List value) { assert false } - - @Override - void fail(ErrorCode errorCode) { normalError = errorCode } - }) - assert normalError.globalErrorCode == ORG_ZSTACK_NETWORK_L3_10092 - - ErrorCode addressPoolError - new AddressPoolIpRangeFactory().createIpRange( - [new IpRangeInventory()], new APIAddIpRangeMsg(), NetworkCreateContext.api(), - new ReturnValueCompletion>(null) { - @Override - void success(List value) { assert false } - - @Override - void fail(ErrorCode errorCode) { addressPoolError = errorCode } - }) - assert addressPoolError.globalErrorCode == ORG_ZSTACK_NETWORK_L3_10093 - } - - @Test - void legacySdnIpRangeDeletionRemainsBestEffort() { - boolean providerCalled = false - boolean completed = false - SdnControllerL3 controller = new SdnControllerL3() { - @Override - void createL3Network(L3NetworkInventory inv, List systemTags, - Completion completion) { completion.success() } - - @Override - void deleteL3Network(L3NetworkInventory inv, Completion completion) { - completion.success() - } - - @Override - void createIpRange(IpRangeInventory inv, Completion completion) { - completion.success() - } - - @Override - void deleteIpRange(IpRangeInventory inv, Completion completion) { - providerCalled = true - completion.fail(new ErrorCode("TEST", "legacy provider failure")) - } - } - - controller.deleteIpRange(new IpRangeInventory(), true, null, new Completion(null) { - @Override - void success() { completed = true } - - @Override - void fail(ErrorCode errorCode) { assert false: errorCode } - }) - - assert providerCalled - assert completed - } -} diff --git a/test/src/test/java/org/zstack/test/network/TestSdnControllerL3.java b/test/src/test/java/org/zstack/test/network/TestSdnControllerL3.java deleted file mode 100644 index add09019ca8..00000000000 --- a/test/src/test/java/org/zstack/test/network/TestSdnControllerL3.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.zstack.test.network; - -import org.junit.Assert; -import org.junit.Test; -import org.zstack.header.core.Completion; -import org.zstack.header.errorcode.ErrorCode; -import org.zstack.header.network.l3.IpRangeInventory; -import org.zstack.header.network.l3.L3NetworkInventory; -import org.zstack.header.network.l3.SdnControllerL3; - -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; - -public class TestSdnControllerL3 { - @Test - public void testDeleteLastIpRangePropagatesControllerFailure() { - ErrorCode expected = new ErrorCode("SYS.1006", "Operation Error", "controller failed"); - AtomicReference actual = new AtomicReference<>(); - SdnControllerL3 controller = new SdnControllerL3() { - @Override - public void createL3Network(L3NetworkInventory inv, List systemTags, Completion completion) { - } - - @Override - public void deleteL3Network(L3NetworkInventory inv, Completion completion) { - } - - @Override - public void createIpRange(IpRangeInventory inv, Completion completion) { - } - - @Override - public void deleteIpRange(IpRangeInventory inv, Completion completion) { - completion.fail(expected); - } - }; - - controller.deleteIpRange(null, true, new Completion(null) { - @Override - public void success() { - Assert.fail("controller failure must not be converted to success"); - } - - @Override - public void fail(ErrorCode errorCode) { - actual.set(errorCode); - } - }); - - Assert.assertSame(expected, actual.get()); - } -} diff --git a/test/src/test/resources/zns/zcf5485/SHA256SUMS b/test/src/test/resources/zns/zcf5485/SHA256SUMS deleted file mode 100644 index 2a0ce5784f3..00000000000 --- a/test/src/test/resources/zns/zcf5485/SHA256SUMS +++ /dev/null @@ -1 +0,0 @@ -bdeba91b2207dd73a33699f8c35f75423624fc75a50b97cf3c4dd3cd9eb7452e *segment-cloud-contract.json diff --git a/test/src/test/resources/zns/zcf5485/segment-cloud-contract.json b/test/src/test/resources/zns/zcf5485/segment-cloud-contract.json deleted file mode 100644 index 4171e5ddcd2..00000000000 --- a/test/src/test/resources/zns/zcf5485/segment-cloud-contract.json +++ /dev/null @@ -1,330 +0,0 @@ -{ - "contract": "ZCF-5485", - "revision": "zcf5485-final-v3", - "version": 3, - "identities": { - "segment_uuid": "00000000-0000-0000-0000-000000000101", - "compute_manager_uuid": "00000000-0000-0000-0000-000000000102", - "cloud_zone_uuid": "00000000-0000-0000-0000-000000000103", - "cloud_l2_uuid": "00000000-0000-0000-0000-000000000104", - "tenant_router_uuid": "00000000-0000-0000-0000-000000000105", - "segment_port_uuid": "00000000-0000-0000-0000-000000000106", - "cms_resource_uuid": "00000000-0000-0000-0000-000000000107", - "transport_zone_uuid": "00000000-0000-0000-0000-000000000201", - "cluster_uuid": "00000000-0000-0000-0000-000000000401", - "operation_uuid": "00000000-0000-0000-0000-000000000701" - }, - "cloud_api": { - "pull": { - "message": "APIPullSdnControllerMsg", - "event": "APIPullSdnControllerEvent", - "method": "PUT", - "path": "/sdn-controllers/{uuid}/resources/actions", - "request": { - "pullSdnController": { - "resourceType": "Segment", - "resourceUuids": ["00000000-0000-0000-0000-000000000101"] - } - }, - "supported_resource_types": ["Segment", "TenantRouter"], - "resource_uuid_limit": 100, - "full_refresh_when_uuids_empty": true, - "event_means_accepted_only": true - }, - "projection_query": { - "message": "APIQueryZnsSegmentCloudProjectionMsg", - "reply": "APIQueryZnsSegmentCloudProjectionReply", - "method": "GET", - "path": "/zns-segment-cloud-projections", - "side_effect_free": true, - "required_inventory_fields": [ - "znsSegmentUuid", - "sdnControllerUuid", - "zoneUuid", - "l2NetworkUuid", - "l2Network", - "state", - "createDate", - "lastOpDate" - ], - "optional_inventory_fields": [ - "l3NetworkUuid", - "l3Network", - "currentConfigVersion", - "appliedConfigVersion", - "operationUuid", - "operationStep", - "lastError" - ] - } - }, - "cloud_inventory": { - "segment_uuid": "00000000-0000-0000-0000-000000000101", - "name": "canonical-segment", - "transport_zone_uuid": "00000000-0000-0000-0000-000000000201", - "transport_type": "overlay", - "availability": "Available", - "reason": "", - "placement": { - "transport_zone_uuid": "00000000-0000-0000-0000-000000000201", - "transport_type": "overlay", - "topology_version": 11, - "targets": [ - { - "compute_manager_uuid": "00000000-0000-0000-0000-000000000102", - "zones": [ - { - "cloud_zone_uuid": "00000000-0000-0000-0000-000000000103", - "complete_cluster_uuids": ["00000000-0000-0000-0000-000000000401"] - } - ] - } - ] - }, - "cloud_zone_uuid": "00000000-0000-0000-0000-000000000103", - "cluster_uuids": ["00000000-0000-0000-0000-000000000401"], - "tenant_router_uuid": "00000000-0000-0000-0000-000000000105" - }, - "cloud_projection": { - "segment_uuid": "00000000-0000-0000-0000-000000000101", - "name": "canonical-segment", - "description": "canonical description", - "config_version": 7, - "etag": "\"7-abc\"", - "mtu": 1400, - "mtu_inherited": true, - "virtual_network_id": 30001, - "transport_zone_uuid": "00000000-0000-0000-0000-000000000201", - "transport_type": "overlay", - "router_uuid": "00000000-0000-0000-0000-000000000105", - "placement": { - "transport_zone_uuid": "00000000-0000-0000-0000-000000000201", - "transport_type": "overlay", - "topology_version": 11, - "targets": [ - { - "compute_manager_uuid": "00000000-0000-0000-0000-000000000102", - "zones": [ - { - "cloud_zone_uuid": "00000000-0000-0000-0000-000000000103", - "complete_cluster_uuids": ["00000000-0000-0000-0000-000000000401"] - } - ] - } - ] - }, - "ipams": [ - { - "ip_version": 4, - "range_uuid": "00000000-0000-0000-0000-000000000601", - "cidr": "192.0.2.0/24", - "start_ip": "192.0.2.10", - "end_ip": "192.0.2.200" - } - ], - "dhcp": {"enabled": true} - }, - "digest_vectors": [ - { - "name": "patch-segment-name-description", - "purpose": "PATCH_SEGMENT", - "canonical_request": "{\"purpose\":\"PATCH_SEGMENT\",\"request\":{\"description\":\"canonical description\",\"name\":\"canonical-name\",\"segment_uuid\":\"00000000-0000-0000-0000-000000000101\"}}", - "sha256": "7fa8b82380eabd7de547dbd61ea16e023bde7d9a26919ebc2ddcee60e83dc47b" - } - ], - "child_operation_uuid_vectors": [ - { - "purpose": "delete-orphan-port", - "root_operation_uuid": "00000000-0000-0000-0000-000000000701", - "resource_uuid": "00000000-0000-0000-0000-000000000106", - "derived_uuid_v3": "07c1e718-28c8-3e0a-b2cc-44212e14c3ad" - }, - { - "purpose": "delete-router-interface", - "root_operation_uuid": "00000000-0000-0000-0000-000000000701", - "resource_uuid": "00000000-0000-0000-0000-000000000105", - "derived_uuid_v3": "814a7757-32b3-3782-9fff-895abe559f06" - } - ], - "mutations": [ - { - "method": "PATCH", - "path": "/zns/api/v1/segments/00000000-0000-0000-0000-000000000101/ipams", - "headers": {"If-Match": "7", "x-job-uuid": "00000000-0000-0000-0000-000000000711"}, - "body": { - "ip_version": 4, - "ip_ranges": [{"range_uuid": "00000000-0000-0000-0000-000000000601", "start_ip": "192.0.2.10", "end_ip": "192.0.2.200"}] - }, - "wire": {"status": 200, "has_data": true, "result_config_version": 8} - }, - { - "method": "PATCH", - "path": "/zns/api/v1/segments/00000000-0000-0000-0000-000000000101", - "headers": {"If-Match": "7", "x-job-uuid": "00000000-0000-0000-0000-000000000712"}, - "body": {"mtu": 1400, "virtual_network_id": 30001}, - "fields": ["mtu", "virtual_network_id"], - "wire": {"status": 202, "has_data": false, "accepted_only": true} - }, - { - "method": "PATCH", - "path": "/zns/api/v1/segments/00000000-0000-0000-0000-000000000101", - "headers": {"If-Match": "7", "x-job-uuid": "00000000-0000-0000-0000-000000000713"}, - "body": {"name": "canonical-name", "description": "canonical description"}, - "fields": ["name", "description"], - "wire": {"status": 202, "has_data": false, "accepted_only": true} - } - ], - "binding_contract": { - "collection_path": "/zns/api/v1/segments/{segmentUuid}/cloud-bindings", - "exact_path": "/zns/api/v1/segments/{segmentUuid}/cloud-bindings/{computeManagerUuid}/{cloudZoneUuid}", - "active_key": ["segment_uuid", "compute_manager_uuid", "cloud_zone_uuid"], - "active_value": "cloud_l2_uuid", - "cloud_l2_unique_key": ["compute_manager_uuid", "cloud_l2_uuid"], - "put_body": {"cloud_l2_uuid": "00000000-0000-0000-0000-000000000104"}, - "data": { - "segment_uuid": "00000000-0000-0000-0000-000000000101", - "compute_manager_uuid": "00000000-0000-0000-0000-000000000102", - "cloud_zone_uuid": "00000000-0000-0000-0000-000000000103", - "cloud_l2_uuid": "00000000-0000-0000-0000-000000000104", - "create_date": "2026-08-15T00:00:00Z", - "last_op_date": "2026-08-15T00:00:01Z" - }, - "principal_target_match_required": true - }, - "operation_contract": { - "path": "/zns/api/v1/segment-operations/{operationUuid}", - "statuses": ["Preparing", "Committed", "Failed"], - "terminal_statuses": ["Committed", "Failed"], - "terminal_result_immutable": true, - "authorization_scope_required": true, - "data": { - "operation_uuid": "00000000-0000-0000-0000-000000000701", - "segment_uuid": "00000000-0000-0000-0000-000000000101", - "purpose": "PATCH_SEGMENT", - "request_digest": "7fa8b82380eabd7de547dbd61ea16e023bde7d9a26919ebc2ddcee60e83dc47b", - "status": "Committed", - "config_version": 8, - "result": {"success": true, "config_version": 8}, - "create_date": "2026-08-15T00:00:00Z", - "last_op_date": "2026-08-15T00:00:01Z" - }, - "authorization_scope": { - "scope_version": 1, - "principal_type": "COMPUTE_MANAGER", - "principal_uuid": "00000000-0000-0000-0000-000000000102", - "compute_manager_uuids": ["00000000-0000-0000-0000-000000000102"] - } - }, - "fence_contract": { - "command_path": "/zns/notify/check-segment-change", - "phases": ["PREPARE", "COMPLETE", "CANCEL"], - "persisted_phases": ["PREPARING", "PERMITTED", "CANCELLED", "COMPLETED"], - "request": { - "computerManagerUuid": "00000000-0000-0000-0000-000000000102", - "segmentUuid": "00000000-0000-0000-0000-000000000101", - "bindingKeys": [{"zoneUuid": "00000000-0000-0000-0000-000000000103", "cloudL2Uuid": "00000000-0000-0000-0000-000000000104"}], - "operationUuid": "00000000-0000-0000-0000-000000000701", - "phase": "PREPARE", - "expectedConfigVersion": 7, - "changeSet": {} - }, - "replay_key": ["operationUuid", "phase", "bindingDigest"], - "cancel_after_commit_forbidden": true - }, - "delete_contract": { - "check_delete": { - "method": "POST", - "path": "/zns/api/v1/segments/00000000-0000-0000-0000-000000000101/actions/check-delete", - "headers": {"x-job-uuid": "00000000-0000-0000-0000-000000000701"}, - "request": {"cloud_delete_binding": {"compute_manager_uuid": "00000000-0000-0000-0000-000000000102", "zone_uuid": "00000000-0000-0000-0000-000000000103", "cloud_l2_uuid": "00000000-0000-0000-0000-000000000104"}}, - "responses": [ - {"name": "permitted", "status": 200, "body": {"success": true, "data": {"segment_uuid": "00000000-0000-0000-0000-000000000101", "zns_permitted": true, "config_version": 7, "binding_match": true, "blockers": [], "owned_config_digest": "sha256:0123456789abcdef", "port_candidates": [{"segment_port_uuid": "00000000-0000-0000-0000-000000000106", "cms_resource_uuid": "00000000-0000-0000-0000-000000000107"}]}}}, - {"name": "denied", "status": 200, "body": {"success": true, "data": {"segment_uuid": "00000000-0000-0000-0000-000000000101", "zns_permitted": false, "config_version": 7, "binding_match": true, "blockers": [{"type": "ROUTER", "resource_uuid": "00000000-0000-0000-0000-000000000105", "reason": "segment has an external router attachment"}], "owned_config_digest": "sha256:0123456789abcdef", "port_candidates": []}}}, - {"name": "identity_conflict", "status": 409, "body": {"success": false, "code": "SEGMENT.CONFLICT.DELETE_BINDING_MISMATCH", "message": "The exact Cloud delete binding does not match the Segment binding"}} - ] - }, - "final_delete": { - "method": "DELETE", - "path": "/zns/api/v1/segments", - "headers": {"If-Match": "7", "x-job-uuid": "00000000-0000-0000-0000-000000000701"}, - "request": {"uuids": ["00000000-0000-0000-0000-000000000101"], "cloud_delete_binding": {"compute_manager_uuid": "00000000-0000-0000-0000-000000000102", "zone_uuid": "00000000-0000-0000-0000-000000000103", "cloud_l2_uuid": "00000000-0000-0000-0000-000000000104"}}, - "responses": [ - {"name": "success", "status": 202, "body": {"success": true}}, - {"name": "replay_success", "status": 202, "body": {"success": true}}, - {"name": "version_conflict", "status": 409, "body": {"success": false, "code": "SEGMENT.CONFLICT.STALE_VERSION", "message": "The Segment config version changed"}}, - {"name": "missing_if_match", "status": 428, "body": {"success": false, "code": "SEGMENT.INVALID.IF_MATCH_REQUIRED", "message": "If-Match is required for Cloud-authorized Segment deletion"}}, - {"name": "response_loss_retry", "status": 202, "body": {"success": true}}, - {"name": "bare_404_negative", "status": 404, "body": {"success": false, "code": "SEGMENT.NOT_FOUND.ID", "message": "The specified Segment was not found"}} - ] - } - }, - "compatibility_contract": { - "modes": ["LEGACY", "MIGRATING_READ_ONLY", "STRICT", "INCOMPATIBLE_READ_ONLY"], - "wire_contracts": ["LEGACY_V1", "RESOURCE_V2"], - "get_path": "/zns/api/v1/fabric/compute-managers/{computeManagerUuid}", - "transition_path": "/zns/api/v1/fabric/compute-managers/{computeManagerUuid}/compatibility", - "transition_request": {"expected_epoch": 7, "mode": "STRICT"}, - "transition_response": {"success": true, "mode": "STRICT", "epoch": 8}, - "missing_fields_default": {"mode": "LEGACY", "epoch": 0}, - "strict_never_falls_back": true - }, - "outbox_contract": { - "statuses": ["pending", "delivering", "delivered", "failed", "delivery_expired"], - "retryable_statuses": [408, 425, 429, 500, 502, 503, 504], - "max_attempts": 12, - "max_age_seconds": 86400, - "request_timeout_seconds": 10, - "lease_seconds": 30, - "backoff_cap_seconds": 60, - "unique_key": ["subscriber", "resourceType", "externalId", "deliveryKey"], - "resource_v2": { - "command_path": "/zns/notify/sync-resource", - "wire_contract": "RESOURCE_V2", - "body": {"computerManagerUuid": "00000000-0000-0000-0000-000000000102", "resourceType": "SEGMENT", "action": "UPDATE", "resourceUuid": "00000000-0000-0000-0000-000000000101"}, - "delivery_key": "config:8", - "mode_epoch": 8 - }, - "legacy_v1": { - "command_path": "/zns/notify/sync-fabric", - "wire_contract": "LEGACY_V1", - "body": {"computerManagerUuid": "00000000-0000-0000-0000-000000000102", "scope": "fabric"}, - "mode_epoch": 0 - }, - "wire_frozen_after_enqueue": true, - "fallback_or_dual_delivery_forbidden": true - }, - "cloud_state_contract": { - "controller_state": { - "primary_key": ["sdnControllerUuid"], - "migration_states": ["NotStarted", "InventoryScanning", "RelationMigrating", "Ready"], - "compatibility_modes": ["LEGACY", "MIGRATING_READ_ONLY", "STRICT", "INCOMPATIBLE_READ_ONLY"] - }, - "segment_inventory": { - "unique_key": ["sdnControllerUuid", "znsSegmentUuid"], - "required_fields": ["uuid", "sdnControllerUuid", "znsSegmentUuid", "name", "transportZoneUuid", "availability", "lastSeenToken", "lastSeenDate"] - }, - "segment_ref": { - "unique_keys": [["sdnControllerUuid", "znsSegmentUuid"], ["l2NetworkUuid"]], - "states": ["Initializing", "AwaitL3", "Syncing", "Ready", "Error", "PlacementConflict", "SourceUnavailable", "PreparingChange", "Deleting", "DeleteError", "MigrationFailed", "UpgradeSyncError"] - }, - "operation": { - "primary_key": ["operation_uuid"], - "statuses": ["Preparing", "Committed", "Failed"], - "authorization_scope_required": true - } - }, - "error_codes": [ - "SEGMENT.CONFLICT.STALE_VERSION", - "SEGMENT.CONFLICT.OPERATION_REPLAY", - "SEGMENT.CONFLICT.CLOUD_FENCE_REJECTED", - "SEGMENT.INVALID.IF_MATCH_REQUIRED", - "SEGMENT.CONFLICT.DELETE_BINDING_MISMATCH", - "SEGMENT.CONFLICT.DELETE_DEPENDENCY", - "SEGMENT_CLOUD_BINDING.NOT_FOUND.ID", - "SEGMENT_CLOUD_BINDING.CONFLICT.IDENTITY", - "SEGMENT.NOT_FOUND.ID", - "COMPUTE_MANAGER.CONFLICT.COMPATIBILITY_EPOCH", - "ORG_ZSTACK_NETWORK_ZNS_10072" - ], - "wire": {"error_code": "", "retryable": false} -} From 1314208afdc764dbc1379d0c63d529e23c25a992 Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Sun, 16 Aug 2026 08:10:25 +0900 Subject: [PATCH 3/7] [zns]: add async prepare error code Reserve a unique global code for asynchronous request preparation failures. Change-Id: Id46327fac368b51a57ace746b75c4e498ff38d60 --- .../zstack/utils/clouderrorcode/CloudOperationsErrorCode.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java index 4940d6cde63..7e860d5ec22 100644 --- a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java +++ b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java @@ -12429,6 +12429,8 @@ public class CloudOperationsErrorCode { public static final String ORG_ZSTACK_NETWORK_ZNS_10180 = "ORG_ZSTACK_NETWORK_ZNS_10180"; public static final String ORG_ZSTACK_NETWORK_ZNS_10181 = "ORG_ZSTACK_NETWORK_ZNS_10181"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10182 = "ORG_ZSTACK_NETWORK_ZNS_10182"; + public static final String ORG_ZSTACK_PREMIUM_EXTERNALSERVICE_MARKETPLACE_10000 = "ORG_ZSTACK_PREMIUM_EXTERNALSERVICE_MARKETPLACE_10000"; public static final String ORG_ZSTACK_ALIYUN_NAS_STORAGE_PRIMARY_IMAGESTORE_10000 = "ORG_ZSTACK_ALIYUN_NAS_STORAGE_PRIMARY_IMAGESTORE_10000"; From a88cd29855242bbc67843805dac00d91d11db387 Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Mon, 17 Aug 2026 09:53:42 +0900 Subject: [PATCH 4/7] [zns]: persist managed access keys Add encrypted ZNS access-key columns for managed request signing. Resolves: ZCF-5485 Change-Id: I655868461502a536db2add83f810b7f0c9a5d936 --- conf/db/upgrade/V5.5.38__schema.sql | 3 +++ 1 file changed, 3 insertions(+) diff --git a/conf/db/upgrade/V5.5.38__schema.sql b/conf/db/upgrade/V5.5.38__schema.sql index 2b71c870dbd..5da0e194ef8 100644 --- a/conf/db/upgrade/V5.5.38__schema.sql +++ b/conf/db/upgrade/V5.5.38__schema.sql @@ -23,6 +23,9 @@ CREATE TABLE IF NOT EXISTS `zstack`.`SnmpEngineVO` ( -- ZNS segment binding state and controller inventory +CALL ADD_COLUMN('ZnsControllerVO', 'zcfAccessKeyId', 'VARCHAR(64)', 1, NULL); +CALL ADD_COLUMN('ZnsControllerVO', 'zcfAccessKeySecret', 'TEXT', 1, NULL); + CREATE TABLE IF NOT EXISTS `zstack`.`ZnsControllerStateVO` ( `sdnControllerUuid` varchar(32) NOT NULL, `activeScanToken` varchar(255) DEFAULT NULL, From c26df3edb0e726bdb6c15a8fe006afe8340808ae Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Mon, 17 Aug 2026 09:55:11 +0900 Subject: [PATCH 5/7] [test]: stabilize affected UT cases Use unique REST paths and remove a stale test invocation. Resolves: ZCF-5485 Change-Id: I4d0f8df29c041b11871774ea421140f0b5be5532 --- .../zstack/test/integration/core/rest/RestFacadeCase.groovy | 4 ++-- .../integration/network/l2network/AttachL2NetworkCase.groovy | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/test/src/test/groovy/org/zstack/test/integration/core/rest/RestFacadeCase.groovy b/test/src/test/groovy/org/zstack/test/integration/core/rest/RestFacadeCase.groovy index d1af759ee78..8068e40a63f 100644 --- a/test/src/test/groovy/org/zstack/test/integration/core/rest/RestFacadeCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/core/rest/RestFacadeCase.groovy @@ -53,7 +53,7 @@ class RestFacadeCase extends SubCase { void testSyncHandlerPreservesStatusAndBody() { RESTFacadeImpl restf = bean(RESTFacadeImpl.class) - String commandPath = "/test-rest-facade-status-body" + String commandPath = "/test-rest-facade-status-body-${UUID.randomUUID()}" String body = '{"condition":"receiver-overloaded"}' restf.registerSyncHttpStatusBodyCallHandler(commandPath, Map.class) { Map ignored -> new SyncHttpResponse(429, body) @@ -74,7 +74,7 @@ class RestFacadeCase extends SubCase { void testSyncHandlerWritesResponseBody() { RESTFacadeImpl restf = bean(RESTFacadeImpl.class) - String commandPath = "/test-rest-facade-response-body" + String commandPath = "/test-rest-facade-response-body-${UUID.randomUUID()}" String body = '{"result":"ok"}' restf.registerSyncHttpCallHandler(commandPath, Map.class) { Map ignored -> body } diff --git a/test/src/test/groovy/org/zstack/test/integration/network/l2network/AttachL2NetworkCase.groovy b/test/src/test/groovy/org/zstack/test/integration/network/l2network/AttachL2NetworkCase.groovy index 4c6637d5592..7d5e8bbfa1d 100644 --- a/test/src/test/groovy/org/zstack/test/integration/network/l2network/AttachL2NetworkCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/network/l2network/AttachL2NetworkCase.groovy @@ -97,7 +97,6 @@ public class AttachL2NetworkCase extends SubCase{ testCreateL2NetworkWithoutPhysicalInterface() testAttachL2NoVlanNetwork() testAttachL2ValnNetwork() - testAttachL2NetworkReturnsFailureWhenAdmissionThrows() testAttachL2NoVlanNetworkSynchronously() testAttachL2VlanNetworkSynchronously() testAddHost() From a37d51c2ceb60ce4f1e1c434aa95d711623fd16c Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Mon, 17 Aug 2026 10:58:10 +0900 Subject: [PATCH 6/7] [zns]: complete projected network updates Add typed local metadata updates for ZNS projection and propagate mutation timeouts. Commit the SDK output generated by ./runMavenProfile sdk. Resolves: ZCF-5485 Change-Id: I21522be40c3cf31dc2660ca8fdacd7bec4f0381a --- .../l2/UpdateL2NetworkMetadataMsg.java | 43 ++++++++++++++ .../zstack/network/l2/L2NoVlanNetwork.java | 59 +++++++++++++++++++ .../org/zstack/network/l3/L3BasicNetwork.java | 1 + .../zstack/sdk/PullSdnControllerAction.java | 8 +-- .../QueryZnsSegmentCloudProjectionAction.java | 8 +-- 5 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 header/src/main/java/org/zstack/header/network/l2/UpdateL2NetworkMetadataMsg.java diff --git a/header/src/main/java/org/zstack/header/network/l2/UpdateL2NetworkMetadataMsg.java b/header/src/main/java/org/zstack/header/network/l2/UpdateL2NetworkMetadataMsg.java new file mode 100644 index 00000000000..7abfac67bc3 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l2/UpdateL2NetworkMetadataMsg.java @@ -0,0 +1,43 @@ +package org.zstack.header.network.l2; + +import org.zstack.header.message.NeedReplyMessage; + +public class UpdateL2NetworkMetadataMsg extends NeedReplyMessage implements L2NetworkMessage { + private String l2NetworkUuid; + private String name; + private String description; + private NetworkCreateContext context; + + @Override + public String getL2NetworkUuid() { + return l2NetworkUuid; + } + + public void setL2NetworkUuid(String l2NetworkUuid) { + this.l2NetworkUuid = l2NetworkUuid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public NetworkCreateContext getContext() { + return context; + } + + public void setContext(NetworkCreateContext context) { + this.context = context; + } +} diff --git a/network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java b/network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java index e97167d448b..d74ece1d984 100755 --- a/network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java +++ b/network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java @@ -126,11 +126,70 @@ private void handleLocalMessage(Message msg) { handle((L2NetworkDetachFromClusterMsg) msg); } else if (msg instanceof AttachL2NetworkToClusterMsg) { handle((AttachL2NetworkToClusterMsg) msg); + } else if (msg instanceof UpdateL2NetworkMetadataMsg) { + handle((UpdateL2NetworkMetadataMsg) msg); } else { bus.dealWithUnknownMessage(msg); } } + private void handle(UpdateL2NetworkMetadataMsg msg) { + MessageReply reply = new MessageReply(); + if (Objects.equals(self.getName(), msg.getName()) + && Objects.equals(self.getDescription(), msg.getDescription())) { + bus.reply(msg, reply); + return; + } + + thdf.chainSubmit(new ChainTask(msg) { + @Override + public String getSyncSignature() { + return String.format("update-l2-network-%s-metadata", msg.getL2NetworkUuid()); + } + + @Override + public void run(SyncTaskChain chain) { + NetworkConfigMutation mutation = NetworkConfigMutation.metadata( + msg.getL2NetworkUuid(), msg.getContext().getOrigin(), + msg.getContext().getOperationUuid(), + msg.getContext().getExternalRef().getAccountUuid(), + msg.getName(), msg.getDescription()); + mutateNetworkConfig(mutation, localCompletion -> { + String originalName = self.getName(); + String originalDescription = self.getDescription(); + try { + self.setName(msg.getName()); + self.setDescription(msg.getDescription()); + self = dbf.updateAndRefresh(self); + localCompletion.success(); + } catch (Exception e) { + self.setName(originalName); + self.setDescription(originalDescription); + localCompletion.fail(errf.throwableToInternalError(e)); + } + }, new Completion(chain) { + @Override + public void success() { + bus.reply(msg, reply); + chain.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + chain.next(); + } + }); + } + + @Override + public String getName() { + return getSyncSignature(); + } + }); + } + private void handle(L2NetworkDetachFromClusterMsg msg) { L2NetworkDetachFromClusterReply reply = new L2NetworkDetachFromClusterReply(); diff --git a/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java b/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java index ff3af42a9e5..50c1991685d 100755 --- a/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java +++ b/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java @@ -2352,6 +2352,7 @@ private void attachNetworkServices(APIAttachNetworkServiceToL3NetworkMsg msg, NetworkCreateContext context, ReturnValueCompletion completion) { AttachNetworkServiceToL3Msg attach = new AttachNetworkServiceToL3Msg(); + attach.setTimeout(msg.getTimeout()); attach.setL3NetworkUuid(msg.getL3NetworkUuid()); attach.setNetworkServices(msg.getNetworkServices()); attach.setContext(context); diff --git a/sdk/src/main/java/org/zstack/sdk/PullSdnControllerAction.java b/sdk/src/main/java/org/zstack/sdk/PullSdnControllerAction.java index 509696c69a9..b82f48c573a 100644 --- a/sdk/src/main/java/org/zstack/sdk/PullSdnControllerAction.java +++ b/sdk/src/main/java/org/zstack/sdk/PullSdnControllerAction.java @@ -17,10 +17,10 @@ public static class Result { public Result throwExceptionIfError() { if (error != null) { throw new ApiException( - String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) ); } - + return this; } } @@ -65,9 +65,9 @@ private Result makeResult(ApiResult res) { ret.error = res.error; return ret; } - + org.zstack.sdk.PullSdnControllerResult value = res.getResult(org.zstack.sdk.PullSdnControllerResult.class); - ret.value = value == null ? new org.zstack.sdk.PullSdnControllerResult() : value; + ret.value = value == null ? new org.zstack.sdk.PullSdnControllerResult() : value; return ret; } diff --git a/sdk/src/main/java/org/zstack/sdk/network/zns/QueryZnsSegmentCloudProjectionAction.java b/sdk/src/main/java/org/zstack/sdk/network/zns/QueryZnsSegmentCloudProjectionAction.java index 0e8d5e90cac..27b3becd7c5 100644 --- a/sdk/src/main/java/org/zstack/sdk/network/zns/QueryZnsSegmentCloudProjectionAction.java +++ b/sdk/src/main/java/org/zstack/sdk/network/zns/QueryZnsSegmentCloudProjectionAction.java @@ -17,10 +17,10 @@ public static class Result { public Result throwExceptionIfError() { if (error != null) { throw new ApiException( - String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) ); } - + return this; } } @@ -33,9 +33,9 @@ private Result makeResult(ApiResult res) { ret.error = res.error; return ret; } - + org.zstack.sdk.network.zns.QueryZnsSegmentCloudProjectionResult value = res.getResult(org.zstack.sdk.network.zns.QueryZnsSegmentCloudProjectionResult.class); - ret.value = value == null ? new org.zstack.sdk.network.zns.QueryZnsSegmentCloudProjectionResult() : value; + ret.value = value == null ? new org.zstack.sdk.network.zns.QueryZnsSegmentCloudProjectionResult() : value; return ret; } From 75adf1b7733dbce0b1785623ba22590ec53a21a9 Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Mon, 17 Aug 2026 11:31:01 +0900 Subject: [PATCH 7/7] [zns]: sync generated api docs Match the deterministic docpremium output required by UTBuild. Resolves: ZCF-5485 Change-Id: I19b28c1cf6350b9d61d9bcf19e9e6d0bc65ba72a --- .../header/APIPullSdnControllerMsgDoc_zh_cn.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerMsgDoc_zh_cn.groovy b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerMsgDoc_zh_cn.groovy index 41355167313..00a3b23ced7 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerMsgDoc_zh_cn.groovy +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerMsgDoc_zh_cn.groovy @@ -18,7 +18,7 @@ doc { clz APIPullSdnControllerMsg.class desc """触发Cloud从指定SDN控制器读取Segment或TenantRouter;可指定资源UUID列表,未指定时拉取该类型的全部资源""" - + params { column { @@ -74,4 +74,4 @@ doc { clz APIPullSdnControllerEvent.class } } -} +} \ No newline at end of file