From e9d63201a2e69797caf903b633968087fb5eb5b3 Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Tue, 18 Aug 2026 16:42:49 +0900 Subject: [PATCH 1/8] [zns]: support automatic segment projection Project ZNS Segments into Cloud, coordinate confirmed network deletion with typed dependency evidence, and enforce Segment identity in Java without database triggers. Change-Id: Ibb6ecf08072e07a96398a0ed5ec1163fc61976db --- .../zstack/compute/vm/VmAllocateNicFlow.java | 81 +- .../org/zstack/compute/zone/ZoneBase.java | 44 +- conf/db/upgrade/V5.5.38__schema.sql | 109 ++ conf/serviceConfig/sdnController.xml | 4 + conf/springConfigXml/NetworkManager.xml | 13 +- conf/springConfigXml/sdnController.xml | 1 + .../zstack/core/cascade/CascadeAction.java | 13 + .../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 | 16 + .../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 | 138 +++ .../network/l2/NetworkOperationOrigin.java | 9 + .../l2/UpdateL2NetworkMetadataMsg.java | 43 + .../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 + .../java/org/zstack/identity/AccountBase.java | 104 +- ...oreAccountCascadeDeleteExtensionPoint.java | 11 + .../network/l2/L2NetworkCascadeExtension.java | 96 +- .../L2NetworkConfirmedDeleteCoordinator.java | 291 +++++ .../l2/L2NetworkExtensionPointEmitter.java | 39 + .../network/l2/L2NetworkManagerImpl.java | 105 +- .../network/l2/L2NoVlanL2NetworkFactory.java | 8 +- .../zstack/network/l2/L2NoVlanNetwork.java | 301 +++++- .../network/l2/L2VlanNetworkFactory.java | 8 +- .../network/l2/NetworkDeletionContexts.java | 28 + .../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 | 990 ++++++++++++++++-- .../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 | 69 +- .../zstack/sdnController/SdnControllerL2.java | 25 + .../SdnControllerManagerImpl.java | 134 ++- .../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 + .../core/async/AsyncCascadeCase.groovy | 6 +- .../core/rest/RestFacadeCase.groovy | 45 + .../account/DeleteNormalAccountCase.groovy | 85 ++ .../l2network/L2NetworkCascadeCase.groovy | 721 +++++++++++++ .../java/org/zstack/testlib/ApiHelper.groovy | 112 ++ .../CloudOperationsErrorCode.java | 211 ++++ 104 files changed, 5773 insertions(+), 253 deletions(-) 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/l2/UpdateL2NetworkMetadataMsg.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 identity/src/main/java/org/zstack/identity/BeforeAccountCascadeDeleteExtensionPoint.java create mode 100644 network/src/main/java/org/zstack/network/l2/L2NetworkConfirmedDeleteCoordinator.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/test/integration/network/l2network/L2NetworkCascadeCase.groovy 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..568a9f76416 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; @@ -24,6 +25,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.Message; import org.zstack.header.zone.*; +import org.zstack.network.l2.L2NetworkCascadeExtension; import org.zstack.utils.Utils; import org.zstack.utils.logging.CLogger; @@ -55,6 +57,8 @@ public class ZoneBase extends AbstractZone { protected ErrorFacade errf; @Autowired protected ThreadFacade thdf; + @Autowired + protected L2NetworkCascadeExtension l2NetworkCascadeExtension; ZoneBase(ZoneVO self) { this.self = self; @@ -178,13 +182,34 @@ 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())); + chain.then(new NoRollbackFlow() { + @Override + public void run(FlowTrigger trigger, Map data) { + cascadeAction.setActionCode(msg.getDeletionMode() == APIDeleteMessage.DeletionMode.Enforcing + ? CascadeConstant.DELETION_FORCE_DELETE_CODE + : CascadeConstant.DELETION_CHECK_CODE); + l2NetworkCascadeExtension.prepareConfirmedDelete(cascadeAction, new Completion(trigger) { + @Override + public void success() { + trigger.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + trigger.fail(errorCode); + } + }); + } + }); 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(); @@ -192,14 +217,25 @@ public void success() { @Override public void fail(ErrorCode errorCode) { - trigger.fail(errorCode); + l2NetworkCascadeExtension.cancelConfirmedDelete(cascadeAction, + new Completion(trigger) { + @Override + public void success() { + trigger.fail(errorCode); + } + + @Override + public void fail(ErrorCode cancelError) { + trigger.fail(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 +252,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..f56c68348ac 100644 --- a/conf/db/upgrade/V5.5.38__schema.sql +++ b/conf/db/upgrade/V5.5.38__schema.sql @@ -20,3 +20,112 @@ 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 + +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, + `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`.`ZnsControllerCapabilityVO` ( + `uuid` varchar(32) NOT NULL, + `controllerUuid` varchar(32) NOT NULL, + `capabilityName` varchar(64) NOT NULL, + `selectedContract` varchar(32) DEFAULT NULL, + `observedState` varchar(32) NOT NULL DEFAULT 'NOT_ACTIVATED', + `effectiveState` varchar(32) NOT NULL DEFAULT 'NOT_ACTIVATED', + `observedEpoch` bigint NOT NULL DEFAULT 0, + `supportedContracts` text DEFAULT NULL, + `servingClusterSupported` tinyint(1) DEFAULT NULL, + `servingClusterDigest` varchar(128) DEFAULT NULL, + `cloudClusterSupported` tinyint(1) DEFAULT NULL, + `cloudClusterDigest` varchar(128) DEFAULT NULL, + `pendingTransitionUuid` varchar(255) DEFAULT NULL, + `pendingExpectedEpoch` bigint DEFAULT NULL, + `pendingSelectedContract` varchar(32) DEFAULT NULL, + `pendingTargetState` varchar(32) DEFAULT NULL, + `pendingRequestDigest` varchar(64) 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 (`uuid`), + UNIQUE KEY `ukZnsControllerCapabilityVOControllerName` (`controllerUuid`, `capabilityName`), + CONSTRAINT `fkZnsControllerCapabilityVOSdnControllerVO` + FOREIGN KEY (`controllerUuid`) REFERENCES `zstack`.`SdnControllerVO` (`uuid`) ON DELETE CASCADE, + CONSTRAINT `chk_zns_controller_capability_state` + CHECK (`observedState` IN ('NOT_ACTIVATED', 'MIGRATING_READ_ONLY', 'ACTIVE', 'INCOMPATIBLE_READ_ONLY') + AND `effectiveState` IN ('NOT_ACTIVATED', 'MIGRATING_READ_ONLY', 'ACTIVE', 'INCOMPATIBLE_READ_ONLY')) +) 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; 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/NetworkManager.xml b/conf/springConfigXml/NetworkManager.xml index 1b964387eb4..b5ddd71ae45 100755 --- a/conf/springConfigXml/NetworkManager.xml +++ b/conf/springConfigXml/NetworkManager.xml @@ -130,11 +130,14 @@ - - - - - + + + + + + + diff --git a/conf/springConfigXml/sdnController.xml b/conf/springConfigXml/sdnController.xml index fc4d85a2f1b..e9dfe9af792 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/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..d5859885512 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l2/L2DeleteConfirmExtensionPoint.java @@ -0,0 +1,16 @@ +package org.zstack.header.network.l2; + +import org.zstack.header.core.Completion; + +/** Provider hook for confirmed remote-first L2 deletion. */ +public interface L2DeleteConfirmExtensionPoint { + boolean supports(L2NetworkInventory inventory); + void begin(L2NetworkInventory inventory, NetworkDeletionContext context, Completion completion); + void check(L2NetworkInventory inventory, NetworkDeletionContext context, Completion completion); + void delete(L2NetworkInventory inventory, NetworkDeletionContext context, Completion completion); + void cancel(L2NetworkInventory inventory, NetworkDeletionContext context, Completion completion); + 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..5d2af489514 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l2/NetworkDeletionContext.java @@ -0,0 +1,138 @@ +package org.zstack.header.network.l2; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +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 String expectedOwnedConfigDigest; + private String expectedDependencySnapshotDigest; + private List expectedCloudChildUuids = Collections.emptyList(); + 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 String getExpectedOwnedConfigDigest() { + return expectedOwnedConfigDigest; + } + + public void setExpectedOwnedConfigDigest(String expectedOwnedConfigDigest) { + this.expectedOwnedConfigDigest = expectedOwnedConfigDigest; + } + + public String getExpectedDependencySnapshotDigest() { + return expectedDependencySnapshotDigest; + } + + public void setExpectedDependencySnapshotDigest(String expectedDependencySnapshotDigest) { + this.expectedDependencySnapshotDigest = expectedDependencySnapshotDigest; + } + + public List getExpectedCloudChildUuids() { + return expectedCloudChildUuids; + } + + public void setExpectedCloudChildUuids(List expectedCloudChildUuids) { + List copy = expectedCloudChildUuids == null + ? new ArrayList<>() : new ArrayList<>(expectedCloudChildUuids); + Collections.sort(copy); + this.expectedCloudChildUuids = Collections.unmodifiableList(copy); + } + + 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/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/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/identity/src/main/java/org/zstack/identity/AccountBase.java b/identity/src/main/java/org/zstack/identity/AccountBase.java index 20f072628c4..0615383675f 100755 --- a/identity/src/main/java/org/zstack/identity/AccountBase.java +++ b/identity/src/main/java/org/zstack/identity/AccountBase.java @@ -7,6 +7,7 @@ import org.springframework.transaction.annotation.Transactional; import org.zstack.core.Platform; 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.EventFacade; @@ -162,7 +163,15 @@ public void fail(ErrorCode errorCode) { private void deleteAccount(Completion completion) { final String issuer = AccountVO.class.getSimpleName(); - final List ctx = list(AccountInventory.valueOf(self)); + final AccountInventory account = AccountInventory.valueOf(self); + final List ctx = list(account); + final CascadeAction cascadeAction = new CascadeAction() + .setRootIssuer(issuer).setRootIssuerContext(ctx) + .setParentIssuer(issuer).setParentIssuerContext(ctx) + .setActionCode(CascadeConstant.DELETION_DELETE_CODE); + final List extensions = + pluginRgty.getExtensionList(BeforeAccountCascadeDeleteExtensionPoint.class); + final List prepared = new ArrayList<>(); List resourceUuids = Q.New(AccountResourceRefVO.class) .select(AccountResourceRefVO_.resourceUuid) .eq(AccountResourceRefVO_.ownerAccountUuid, self.getUuid()) @@ -172,12 +181,32 @@ private void deleteAccount(Completion completion) { chain.then(new ShareFlow() { @Override public void setup() { + flow(new NoRollbackFlow() { + String __name__ = "prepare-account-cascade-delete"; + + @Override + public void run(FlowTrigger trigger, Map data) { + prepareAccountDelete(account, cascadeAction, extensions, 0, prepared, + new Completion(trigger) { + @Override + public void success() { + trigger.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + trigger.fail(errorCode); + } + }); + } + }); + flow(new NoRollbackFlow() { String __name__ = "delete"; @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_DELETE_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction, new Completion(trigger) { @Override public void success() { trigger.next(); @@ -216,6 +245,77 @@ public void handle(ErrorCode errCode, Map data) { }).start(); } + private void prepareAccountDelete(AccountInventory account, CascadeAction action, + List extensions, + int index, + List prepared, + Completion completion) { + if (index == extensions.size()) { + completion.success(); + return; + } + BeforeAccountCascadeDeleteExtensionPoint extension = extensions.get(index); + try { + extension.beforeDelete(account, action, new Completion(completion) { + @Override + public void success() { + prepared.add(extension); + prepareAccountDelete(account, action, extensions, index + 1, prepared, completion); + } + + @Override + public void fail(ErrorCode errorCode) { + failAccountDeletePreparation(account, action, prepared, errorCode, completion); + } + }); + } catch (RuntimeException e) { + failAccountDeletePreparation(account, action, prepared, + errf.throwableToInternalError(e), completion); + } + } + + private void failAccountDeletePreparation( + AccountInventory account, CascadeAction action, + List prepared, + ErrorCode errorCode, Completion completion) { + cancelAccountDelete(account, action, prepared, prepared.size() - 1, + new Completion(completion) { + @Override + public void success() { + completion.fail(errorCode); + } + + @Override + public void fail(ErrorCode cancelError) { + completion.fail(errorCode); + } + }); + } + + private void cancelAccountDelete(AccountInventory account, CascadeAction action, + List prepared, + int index, Completion completion) { + if (index < 0) { + completion.success(); + return; + } + try { + prepared.get(index).cancel(account, action, new Completion(completion) { + @Override + public void success() { + cancelAccountDelete(account, action, prepared, index - 1, completion); + } + + @Override + public void fail(ErrorCode errorCode) { + cancelAccountDelete(account, action, prepared, index - 1, completion); + } + }); + } catch (RuntimeException e) { + cancelAccountDelete(account, action, prepared, index - 1, completion); + } + } + private void handle(final APIDeleteAccountMsg msg) { final APIDeleteAccountEvent evt = new APIDeleteAccountEvent(msg.getId()); diff --git a/identity/src/main/java/org/zstack/identity/BeforeAccountCascadeDeleteExtensionPoint.java b/identity/src/main/java/org/zstack/identity/BeforeAccountCascadeDeleteExtensionPoint.java new file mode 100644 index 00000000000..e7c335e526d --- /dev/null +++ b/identity/src/main/java/org/zstack/identity/BeforeAccountCascadeDeleteExtensionPoint.java @@ -0,0 +1,11 @@ +package org.zstack.identity; + +import org.zstack.core.cascade.CascadeAction; +import org.zstack.header.core.Completion; +import org.zstack.header.identity.AccountInventory; + +public interface BeforeAccountCascadeDeleteExtensionPoint { + void beforeDelete(AccountInventory account, CascadeAction action, Completion completion); + + void cancel(AccountInventory account, CascadeAction action, Completion completion); +} 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..b7282732abb 100755 --- a/network/src/main/java/org/zstack/network/l2/L2NetworkCascadeExtension.java +++ b/network/src/main/java/org/zstack/network/l2/L2NetworkCascadeExtension.java @@ -11,8 +11,10 @@ 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.message.MessageReply; @@ -50,6 +52,8 @@ public class L2NetworkCascadeExtension extends AbstractAsyncCascadeExtension { private ErrorFacade errf; @Autowired private PluginRegistry pluginRgty; + @Autowired + private L2NetworkConfirmedDeleteCoordinator confirmedDeleteCoordinator; private static final String NAME = L2NetworkVO.class.getSimpleName(); @@ -102,7 +106,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,17 +127,16 @@ 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); List msgs = new ArrayList(); for (L2NetworkInventory l2inv : l2invs) { 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 +157,45 @@ public void run(List replies) { List uuids = new ArrayList(); for (MessageReply r : replies) { L2NetworkInventory inv = finalL2invs.get(replies.indexOf(r)); + if (!r.isSuccess()) { + if (!confirmedDeleteCoordinator.confirmedExtensions(inv).isEmpty()) { + completion.fail(r.getError()); + 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(); + confirmedDeleteCoordinator.complete(action, finalL2invs, new Completion(completion) { + @Override + public void success() { + try { + new SQLBatch() { + @Override + protected void scripts() { + for (L2NetworkInventory inv : finalL2invs) { + for (L2DeleteConfirmExtensionPoint ext : + confirmedDeleteCoordinator.confirmedExtensions(inv)) { + 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())); + } + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + }); } }); } @@ -169,15 +207,51 @@ private void handleDeletionCheck(CascadeAction action, Completion completion) { return; } + List filtered = filterInventories(l2invs, action); try { - for (L2NetworkInventory prinv : l2invs) { + for (L2NetworkInventory prinv : filtered) { extpEmitter.preDelete(prinv); } - - completion.success(); } catch (L2NetworkException e) { completion.fail(inerr(ORG_ZSTACK_NETWORK_L2_10000, e.getMessage())); + return; + } + if (confirmedDeleteCoordinator.isPrepared(action, filtered)) { + completion.success(); + } else { + confirmedDeleteCoordinator.prepare(action, filtered, completion); + } + } + + public void prepareConfirmedDelete(CascadeAction action, Completion completion) { + List l2invs = l2NetworkFromAction(action); + if (l2invs == null) { + completion.success(); + return; + } + confirmedDeleteCoordinator.prepare(action, filterInventories(l2invs, action), completion); + } + + public void cancelConfirmedDelete(CascadeAction action, Completion completion) { + List l2invs = l2NetworkFromAction(action); + if (l2invs == null) { + completion.success(); + return; + } + confirmedDeleteCoordinator.cancel(action, filterInventories(l2invs, action), completion); + } + + 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 diff --git a/network/src/main/java/org/zstack/network/l2/L2NetworkConfirmedDeleteCoordinator.java b/network/src/main/java/org/zstack/network/l2/L2NetworkConfirmedDeleteCoordinator.java new file mode 100644 index 00000000000..8bdb0bb92b4 --- /dev/null +++ b/network/src/main/java/org/zstack/network/l2/L2NetworkConfirmedDeleteCoordinator.java @@ -0,0 +1,291 @@ +package org.zstack.network.l2; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.core.Platform; +import org.zstack.core.cascade.CascadeAction; +import org.zstack.core.cascade.CascadeConstant; +import org.zstack.core.componentloader.PluginRegistry; +import org.zstack.header.core.Completion; +import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.errorcode.OperationFailureException; +import org.zstack.header.network.l2.L2DeleteConfirmExtensionPoint; +import org.zstack.header.network.l2.L2NetworkInventory; +import org.zstack.header.network.l2.NetworkDeletionContext; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static org.zstack.core.Platform.inerr; +import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_NETWORK_L2_10023; + +public class L2NetworkConfirmedDeleteCoordinator { + private static final CLogger logger = Utils.getLogger(L2NetworkConfirmedDeleteCoordinator.class); + + @Autowired + private PluginRegistry pluginRgty; + + public void prepare(CascadeAction action, List inventories, + Completion completion) { + prepare(action, inventories == null ? Collections.emptyList() : inventories, + 0, new ArrayList<>(), completion); + } + + public void cancel(CascadeAction action, List inventories, + Completion completion) { + List prepared = new ArrayList<>(); + try { + List candidates = inventories == null + ? Collections.emptyList() : inventories; + for (L2NetworkInventory inventory : candidates) { + NetworkDeletionContext context = NetworkDeletionContexts.get(action, inventory.getUuid()); + if (context == null || !NetworkDeletionContexts.isPrepared(action, inventory.getUuid())) { + continue; + } + for (L2DeleteConfirmExtensionPoint extension : confirmedExtensions(inventory)) { + prepared.add(new ConfirmedDelete(inventory, extension, context)); + } + } + } catch (RuntimeException e) { + completion.fail(toError(e)); + return; + } + cancel(action, prepared, prepared.size() - 1, completion); + } + + public void complete(CascadeAction action, List inventories, + Completion completion) { + List prepared = new ArrayList<>(); + try { + List candidates = inventories == null + ? Collections.emptyList() : inventories; + for (L2NetworkInventory inventory : candidates) { + NetworkDeletionContext context = NetworkDeletionContexts.get(action, inventory.getUuid()); + if (context == null) { + continue; + } + for (L2DeleteConfirmExtensionPoint extension : confirmedExtensions(inventory)) { + prepared.add(new ConfirmedDelete(inventory, extension, context)); + } + } + } catch (RuntimeException e) { + completion.fail(toError(e)); + return; + } + complete(prepared, 0, completion); + } + + public List confirmedExtensions(L2NetworkInventory inventory) { + return pluginRgty.getExtensionList(L2DeleteConfirmExtensionPoint.class).stream() + .filter(extension -> extension.supports(inventory)) + .collect(Collectors.toList()); + } + + public boolean isPrepared(CascadeAction action, List inventories) { + for (L2NetworkInventory inventory : inventories == null + ? Collections.emptyList() : inventories) { + if (!confirmedExtensions(inventory).isEmpty() + && !NetworkDeletionContexts.isPrepared(action, inventory.getUuid())) { + return false; + } + } + return true; + } + + private void prepare(CascadeAction action, List inventories, int inventoryIndex, + List begun, Completion completion) { + if (inventoryIndex == inventories.size()) { + completion.success(); + return; + } + + L2NetworkInventory inventory = inventories.get(inventoryIndex); + List extensions; + try { + extensions = confirmedExtensions(inventory); + } catch (RuntimeException e) { + failPrepared(action, begun, toError(e), completion); + return; + } + if (extensions.isEmpty()) { + prepare(action, inventories, inventoryIndex + 1, begun, completion); + return; + } + + NetworkDeletionContext context = NetworkDeletionContexts.get(action, inventory.getUuid()); + if (context == null) { + context = new NetworkDeletionContext(NetworkDeletionContext.Origin.WHOLE_L2_SEGMENT_DELETE, + Platform.getUuid(), inventory.getUuid(), action.getRootIssuer()); + context.setForceDelete(action.isActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE)); + NetworkDeletionContexts.put(action, context); + } + + NetworkDeletionContext finalContext = context; + if (NetworkDeletionContexts.isPrepared(action, inventory.getUuid())) { + for (L2DeleteConfirmExtensionPoint extension : extensions) { + begun.add(new ConfirmedDelete(inventory, extension, finalContext)); + } + check(action, inventories, inventoryIndex, extensions, 0, begun, inventory, + finalContext, completion); + return; + } + begin(action, inventories, inventoryIndex, extensions, 0, begun, inventory, + finalContext, completion); + } + + private void begin(CascadeAction action, List inventories, int inventoryIndex, + List extensions, int extensionIndex, + List begun, L2NetworkInventory inventory, + NetworkDeletionContext context, Completion completion) { + if (extensionIndex == extensions.size()) { + NetworkDeletionContexts.markPrepared(action, inventory.getUuid(), true); + check(action, inventories, inventoryIndex, extensions, 0, begun, inventory, + context, completion); + return; + } + + L2DeleteConfirmExtensionPoint extension = extensions.get(extensionIndex); + invoke(() -> extension.begin(inventory, context, new Completion(completion) { + @Override + public void success() { + begun.add(new ConfirmedDelete(inventory, extension, context)); + begin(action, inventories, inventoryIndex, extensions, extensionIndex + 1, + begun, inventory, context, completion); + } + + @Override + public void fail(ErrorCode errorCode) { + failPrepared(action, begun, errorCode, completion); + } + }), begun, action, completion); + } + + private void check(CascadeAction action, List inventories, int inventoryIndex, + List extensions, int extensionIndex, + List begun, L2NetworkInventory inventory, + NetworkDeletionContext context, Completion completion) { + if (extensionIndex == extensions.size()) { + prepare(action, inventories, inventoryIndex + 1, begun, completion); + return; + } + + L2DeleteConfirmExtensionPoint extension = extensions.get(extensionIndex); + invoke(() -> extension.check(inventory, context, new Completion(completion) { + @Override + public void success() { + check(action, inventories, inventoryIndex, extensions, extensionIndex + 1, + begun, inventory, context, completion); + } + + @Override + public void fail(ErrorCode errorCode) { + failPrepared(action, begun, errorCode, completion); + } + }), begun, action, completion); + } + + private void failPrepared(CascadeAction action, List begun, ErrorCode error, + Completion completion) { + cancel(action, begun, begun.size() - 1, new Completion(completion) { + @Override + public void success() { + completion.fail(error); + } + + @Override + public void fail(ErrorCode cancelError) { + completion.fail(error); + } + }); + } + + private void cancel(CascadeAction action, List prepared, int index, + Completion completion) { + if (index < 0) { + completion.success(); + return; + } + + ConfirmedDelete delete = prepared.get(index); + invokeCancel(delete, new Completion(completion) { + @Override + public void success() { + NetworkDeletionContexts.markPrepared(action, delete.inventory.getUuid(), false); + cancel(action, prepared, index - 1, completion); + } + + @Override + public void fail(ErrorCode errorCode) { + logger.warn(String.format("failed to cancel confirmed L2Network deletion[uuid:%s]: %s", + delete.inventory.getUuid(), errorCode)); + NetworkDeletionContexts.markPrepared(action, delete.inventory.getUuid(), false); + cancel(action, prepared, index - 1, completion); + } + }); + } + + private void complete(List prepared, int index, Completion completion) { + if (index == prepared.size()) { + completion.success(); + return; + } + ConfirmedDelete delete = prepared.get(index); + try { + delete.extension.delete(delete.inventory, delete.context, new Completion(completion) { + @Override + public void success() { + complete(prepared, index + 1, completion); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + }); + } catch (RuntimeException e) { + completion.fail(toError(e)); + } + } + + private void invoke(Runnable runnable, List begun, CascadeAction action, + Completion completion) { + try { + runnable.run(); + } catch (RuntimeException e) { + failPrepared(action, begun, toError(e), completion); + } + } + + private void invokeCancel(ConfirmedDelete delete, Completion completion) { + try { + delete.extension.cancel(delete.inventory, delete.context, completion); + } catch (RuntimeException e) { + completion.fail(toError(e)); + } + } + + private ErrorCode toError(RuntimeException exception) { + if (exception instanceof OperationFailureException) { + return ((OperationFailureException) exception).getErrorCode(); + } + return inerr(ORG_ZSTACK_NETWORK_L2_10023, + "failed to coordinate confirmed L2Network deletion: %s", exception.getMessage()); + } + + 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; + } + } +} 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..780689fa86a 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; @@ -72,6 +76,8 @@ public class L2NoVlanNetwork implements L2Network { @Autowired protected CascadeFacade casf; @Autowired + protected L2NetworkCascadeExtension l2NetworkCascadeExtension; + @Autowired protected ErrorFacade errf; @Autowired protected ThreadFacade thdf; @@ -122,11 +128,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(); @@ -156,13 +221,16 @@ 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())); + addConfirmedDeletePreflight(chain, cascadeAction, msg.isForceDelete()); 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(); @@ -170,14 +238,14 @@ public void success() { @Override public void fail(ErrorCode errorCode) { - trigger.fail(errorCode); + cancelConfirmedDelete(cascadeAction, errorCode, trigger); } }); } }).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 +262,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 +420,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 +528,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 +582,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 +1030,17 @@ 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())); + addConfirmedDeletePreflight(chain, cascadeAction, + msg.getDeletionMode() == APIDeleteMessage.DeletionMode.Enforcing); 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(); @@ -870,14 +1048,14 @@ public void success() { @Override public void fail(ErrorCode errorCode) { - trigger.fail(errorCode); + cancelConfirmedDelete(cascadeAction, errorCode, trigger); } }); } }).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 +1072,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(); @@ -925,6 +1103,44 @@ public void handle(ErrorCode errCode, Map data) { }).start(); } + private void addConfirmedDeletePreflight(FlowChain chain, CascadeAction action, + boolean forceDelete) { + chain.then(new NoRollbackFlow() { + @Override + public void run(FlowTrigger trigger, Map data) { + action.setActionCode(forceDelete + ? CascadeConstant.DELETION_FORCE_DELETE_CODE + : CascadeConstant.DELETION_CHECK_CODE); + l2NetworkCascadeExtension.prepareConfirmedDelete(action, new Completion(trigger) { + @Override + public void success() { + trigger.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + trigger.fail(errorCode); + } + }); + } + }); + } + + private void cancelConfirmedDelete(CascadeAction action, ErrorCode errorCode, + FlowTrigger trigger) { + l2NetworkCascadeExtension.cancelConfirmedDelete(action, new Completion(trigger) { + @Override + public void success() { + trigger.fail(errorCode); + } + + @Override + public void fail(ErrorCode cancelError) { + trigger.fail(errorCode); + } + }); + } + private void attachL2NetworkToCluster(final AttachL2NetworkToClusterMsg msg, final Completion completion){ long count = Q.New(L2NetworkClusterRefVO.class).eq(L2NetworkClusterRefVO_.clusterUuid, msg.getClusterUuid()) .eq(L2NetworkClusterRefVO_.l2NetworkUuid, msg.getL2NetworkUuid()).count(); @@ -1010,7 +1226,7 @@ protected void scripts() { for (L2VlanNetworkVO vl2 : l2s) { if (vl2.getVlan() == tl2.getVlan() && vl2.getPhysicalInterface().equals(tl2.getPhysicalInterface())) { - throw new OperationFailureException(argerr(ORG_ZSTACK_NETWORK_L2_10007, "There has been a L2VlanNetwork[uuid:%s, name:%s] attached to cluster[uuid:%s] that has physical interface[%s], vlan[%s]. Failed to attach L2VlanNetwork[uuid:%s]", + throw new OperationFailureException(argerr(ORG_ZSTACK_NETWORK_L2_10029, "There has been a L2VlanNetwork[uuid:%s, name:%s] attached to cluster[uuid:%s] that has physical interface[%s], vlan[%s]. Failed to attach L2VlanNetwork[uuid:%s]", vl2.getUuid(), vl2.getName(), msg.getClusterUuid(), vl2.getPhysicalInterface(), vl2.getVlan(), tl2.getUuid())); } } @@ -1039,11 +1255,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..217c1bb31b9 --- /dev/null +++ b/network/src/main/java/org/zstack/network/l2/NetworkDeletionContexts.java @@ -0,0 +1,28 @@ +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 static final String PREPARED_KEY_PREFIX = KEY_PREFIX + "prepared:"; + + 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); + } + + public static boolean isPrepared(CascadeAction action, String l2NetworkUuid) { + return Boolean.TRUE.equals(action.getContext(PREPARED_KEY_PREFIX + l2NetworkUuid)); + } + + public static void markPrepared(CascadeAction action, String l2NetworkUuid, boolean prepared) { + action.putContext(PREPARED_KEY_PREFIX + l2NetworkUuid, prepared); + } +} 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..50c1991685d 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,83 @@ 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.setTimeout(msg.getTimeout()); + 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 +2446,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 +2538,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..09a0b4c2b5f 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; @@ -34,6 +35,7 @@ import org.zstack.header.network.sdncontroller.*; import org.zstack.network.hostNetworkInterface.HostNetworkInterfaceVO; import org.zstack.network.hostNetworkInterface.HostNetworkInterfaceVO_; +import org.zstack.network.l2.L2NetworkCascadeExtension; import org.zstack.sdnController.header.*; import org.zstack.sdnController.h3cVcfc.H3cVcfcV2Commands; import org.zstack.sdnController.h3cVcfc.H3cVcfcV2SdnController; @@ -73,6 +75,8 @@ public class SdnControllerBase { private PluginRegistry pluginRgty; @Autowired private SdnControllerPingTracker sdnPingTracker; + @Autowired + private L2NetworkCascadeExtension l2NetworkCascadeExtension; public SdnControllerVO self; @@ -120,12 +124,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 +956,34 @@ 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())); + chain.then(new NoRollbackFlow() { + @Override + public void run(FlowTrigger trigger, Map data) { + cascadeAction.setActionCode(msg.getDeletionMode() == APIDeleteMessage.DeletionMode.Enforcing + ? CascadeConstant.DELETION_FORCE_DELETE_CODE + : CascadeConstant.DELETION_CHECK_CODE); + l2NetworkCascadeExtension.prepareConfirmedDelete(cascadeAction, new Completion(trigger) { + @Override + public void success() { + trigger.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + trigger.fail(errorCode); + } + }); + } + }); 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(); @@ -962,14 +991,25 @@ public void success() { @Override public void fail(ErrorCode errorCode) { - trigger.fail(errorCode); + l2NetworkCascadeExtension.cancelConfirmedDelete(cascadeAction, + new Completion(trigger) { + @Override + public void success() { + trigger.fail(errorCode); + } + + @Override + public void fail(ErrorCode cancelError) { + trigger.fail(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 +1026,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 +1103,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 +1135,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..b09c8083f60 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerL2.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerL2.java @@ -4,6 +4,8 @@ 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 +22,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 +35,26 @@ 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 void beginConfirmedDelete(L2NetworkInventory inv, NetworkDeletionContext context, + Completion completion) { completion.success(); } + default void checkConfirmedDelete(L2NetworkInventory inv, NetworkDeletionContext context, + Completion completion) { completion.success(); } + default void completeConfirmedDelete(L2NetworkInventory inv, NetworkDeletionContext context, + Completion completion) { completion.success(); } + default void cancelConfirmedDelete(L2NetworkInventory inv, NetworkDeletionContext context, + Completion completion) { completion.success(); } + 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 d02a8737713..b149f69744b 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java @@ -53,7 +53,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 { @@ -303,6 +303,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(); @@ -337,7 +343,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 @@ -357,16 +363,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())); @@ -389,6 +386,117 @@ 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 void begin(L2NetworkInventory inventory, NetworkDeletionContext context, + Completion completion) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + if (controllerL2 == null) { + completion.fail(confirmedDeleteControllerMissing(inventory)); + return; + } + controllerL2.beginConfirmedDelete(inventory, context, completion); + } + + @Override + public void check(L2NetworkInventory inventory, NetworkDeletionContext context, + Completion completion) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + if (controllerL2 == null) { + completion.fail(confirmedDeleteControllerMissing(inventory)); + return; + } + controllerL2.checkConfirmedDelete(inventory, context, completion); + } + + @Override + public void delete(L2NetworkInventory inventory, NetworkDeletionContext context, + Completion completion) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + if (controllerL2 == null) { + completion.fail(confirmedDeleteControllerMissing(inventory)); + return; + } + controllerL2.completeConfirmedDelete(inventory, context, completion); + } + + @Override + public void cancel(L2NetworkInventory inventory, NetworkDeletionContext context, + Completion completion) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + if (controllerL2 == null) { + completion.fail(confirmedDeleteControllerMissing(inventory)); + return; + } + controllerL2.cancelConfirmedDelete(inventory, context, completion); + } + + 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) { @@ -398,7 +506,7 @@ private void sdnAddVmNic(String sdnControllerUuid, List nics, Co SdnControllerVO vo = dbf.findByUuid(sdnControllerUuid, SdnControllerVO.class); SdnControllerFactory factory = getSdnControllerFactory(vo.getVendorType()); if (factory == null) { - completion.fail(operr(ORG_ZSTACK_SDNCONTROLLER_10003, "there is no sdn controller factory for sdn controller type:%s", vo.getVendorType())); + completion.fail(operr(ORG_ZSTACK_SDNCONTROLLER_10040, "there is no sdn controller factory for sdn controller type:%s", vo.getVendorType())); return; } 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..00a3b23ced7 --- /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 + } + } +} \ No newline at end of file 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..b82f48c573a --- /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..27b3becd7c5 --- /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/test/integration/core/async/AsyncCascadeCase.groovy b/test/src/test/groovy/org/zstack/test/integration/core/async/AsyncCascadeCase.groovy index 1394bacae67..260e32a3634 100644 --- a/test/src/test/groovy/org/zstack/test/integration/core/async/AsyncCascadeCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/core/async/AsyncCascadeCase.groovy @@ -23,6 +23,7 @@ class AsyncCascadeCase extends SubCase{ boolean success3 boolean success4 boolean success5 + int clusterChildActionCount @Override void setup() { @@ -49,6 +50,7 @@ class AsyncCascadeCase extends SubCase{ success3 = false success4 = false success5 = false + clusterChildActionCount = 0 casf = bean(CascadeFacade.class) asyncCascade() @@ -103,6 +105,7 @@ class AsyncCascadeCase extends SubCase{ @Override public CascadeAction createActionForChildResource(CascadeAction action) { if (action.getParentIssuer().equals("zone")) { + clusterChildActionCount++ return action.copy().setParentIssuer("cluster") } @@ -181,10 +184,11 @@ class AsyncCascadeCase extends SubCase{ assert success3 assert success4 assert success5 + assert clusterChildActionCount == 1 } @Override void clean() { } -} \ No newline at end of file +} 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..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 @@ -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-${UUID.randomUUID()}" + 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-${UUID.randomUUID()}" + 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/identity/account/DeleteNormalAccountCase.groovy b/test/src/test/groovy/org/zstack/test/integration/identity/account/DeleteNormalAccountCase.groovy index 6ad8b4f466f..5ac1b395965 100644 --- a/test/src/test/groovy/org/zstack/test/integration/identity/account/DeleteNormalAccountCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/identity/account/DeleteNormalAccountCase.groovy @@ -3,9 +3,19 @@ package org.zstack.test.integration.identity.account import org.zstack.compute.vm.VmGlobalConfig import org.zstack.core.db.Q import org.zstack.core.db.SQL +import org.zstack.core.cascade.CascadeAction +import org.zstack.core.cloudbus.CloudBus +import org.zstack.core.componentloader.PluginRegistry +import org.zstack.header.core.Completion +import org.zstack.header.errorcode.ErrorCode import org.zstack.header.identity.AccountConstant +import org.zstack.header.identity.AccountInventory as HeaderAccountInventory import org.zstack.header.identity.AccountResourceRefVO import org.zstack.header.identity.AccountResourceRefVO_ +import org.zstack.header.identity.AccountVO +import org.zstack.header.identity.AccountVO_ +import org.zstack.header.identity.DeleteAccountMsg +import org.zstack.identity.BeforeAccountCascadeDeleteExtensionPoint import org.zstack.header.network.service.NetworkServiceType import org.zstack.header.vm.VmInstanceDeletionPolicyManager import org.zstack.network.securitygroup.SecurityGroupConstant @@ -175,10 +185,61 @@ class DeleteNormalAccountCase extends SubCase { VmGlobalConfig.VM_DELETION_POLICY.updateValue(VmInstanceDeletionPolicyManager.VmInstanceDeletionPolicy.Delay.toString()) + testAccountCascadeDeleteExtensionCoversApiAndInternalMessage() testAdminAdoptOrphanedResourceAfterDeletedNormalAccount() } } + void testAccountCascadeDeleteExtensionCoversApiAndInternalMessage() { + AccountInventory blocked = createAccount { + name = "blocked-account" + password = "password" + } as AccountInventory + AccountInventory allowed = createAccount { + name = "allowed-account" + password = "password" + } as AccountInventory + AccountInventory internal = createAccount { + name = "internal-account" + password = "password" + } as AccountInventory + def first = new RecordingAccountDeleteExtension() + def second = new RecordingAccountDeleteExtension(blockedAccountUuid: blocked.uuid) + PluginRegistry registry = bean(PluginRegistry.class) + registry.defineDynamicExtension(BeforeAccountCascadeDeleteExtensionPoint.class, first) + registry.defineDynamicExtension(BeforeAccountCascadeDeleteExtensionPoint.class, second) + List extensions = registry + .getExtensionList(BeforeAccountCascadeDeleteExtensionPoint.class) + + try { + expectError { + deleteAccount { + uuid = blocked.uuid + } + } + assert Q.New(AccountVO.class).eq(AccountVO_.uuid, blocked.uuid).isExists() + assert first.cancelled == [blocked.uuid] + assert second.cancelled.isEmpty() + + deleteAccount { + uuid = allowed.uuid + } + assert !Q.New(AccountVO.class).eq(AccountVO_.uuid, allowed.uuid).isExists() + + def msg = new DeleteAccountMsg(uuid: internal.uuid) + CloudBus bus = bean(CloudBus.class) + bus.makeTargetServiceIdByResourceUuid(msg, AccountConstant.SERVICE_ID, internal.uuid) + assert bus.call(msg).success + assert !Q.New(AccountVO.class).eq(AccountVO_.uuid, internal.uuid).isExists() + } finally { + extensions.remove(second) + extensions.remove(first) + } + + assert first.prepared.containsAll([blocked.uuid, allowed.uuid, internal.uuid]) + assert second.prepared.containsAll([allowed.uuid, internal.uuid]) + } + void testAdminAdoptOrphanedResourceAfterDeletedNormalAccount() { VmInstanceInventory vm = env.inventoryByName("vm") as VmInstanceInventory VolumeInventory vol = vm.getAllVolumes().find { i -> i.getUuid() != vm.getRootVolumeUuid() } @@ -234,3 +295,27 @@ class DeleteNormalAccountCase extends SubCase { } } } + +class RecordingAccountDeleteExtension implements BeforeAccountCascadeDeleteExtensionPoint { + String blockedAccountUuid + List prepared = [] + List cancelled = [] + + @Override + void beforeDelete(HeaderAccountInventory account, CascadeAction action, + Completion completion) { + if (account.uuid == blockedAccountUuid) { + completion.fail(new ErrorCode("SYS.1000", "blocked account delete")) + return + } + prepared.add(account.uuid) + completion.success() + } + + @Override + void cancel(HeaderAccountInventory account, CascadeAction action, + Completion completion) { + cancelled.add(account.uuid) + completion.success() + } +} 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..b9c6e0e62e6 --- /dev/null +++ b/test/src/test/groovy/org/zstack/test/integration/network/l2network/L2NetworkCascadeCase.groovy @@ -0,0 +1,721 @@ +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.L2NetworkConfirmedDeleteCoordinator +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_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() + testConfirmedDeleteFailureCancelsPreparedFences() + testRepeatedPrepareReusesOperationContext() + testRepeatedPrepareFailureCancelsExistingFence() + testDelayedBeginDoesNotBlockCoordinator() + 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.any { it.startsWith("begin:") } + 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 testConfirmedDeleteFailureCancelsPreparedFences() { + ZoneInventory zone = env.inventoryByName("check-zone") + def inventory = org.zstack.header.zone.ZoneInventory.valueOf( + dbf.findByUuid(zone.uuid, ZoneVO.class)) + def extension = new RecordingDeleteConfirmation(failOnSecondBegin: 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 != null + assert extension.begun.size() == 2 + assert extension.cancelled == [extension.begun.first()] + } + + void testRepeatedPrepareReusesOperationContext() { + SdkL2NetworkInventory l2 = env.inventoryByName("metadata-update-l2") + L2NetworkInventory inventory = L2NetworkInventory.valueOf( + dbf.findByUuid(l2.uuid, L2NetworkVO.class)) + def extension = new RecordingDeleteConfirmation() + List extensions = bean(PluginRegistry.class) + .getExtensionList(L2DeleteConfirmExtensionPoint.class) + extensions.add(extension) + def action = new CascadeAction() + .setRootIssuer(L2NetworkVO.simpleName) + .setActionCode(CascadeConstant.DELETION_CHECK_CODE) + int successCount = 0 + + try { + 2.times { + bean(L2NetworkConfirmedDeleteCoordinator.class).prepare( + action, [inventory], new Completion(null) { + @Override + void success() { successCount++ } + + @Override + void fail(ErrorCode errorCode) { assert false: errorCode } + }) + } + } finally { + extensions.remove(extension) + } + + assert successCount == 2 + assert extension.begun == [l2.uuid] + assert extension.checked == [l2.uuid, l2.uuid] + assert extension.operationUuids.size() == 1 + } + + void testRepeatedPrepareFailureCancelsExistingFence() { + SdkL2NetworkInventory l2 = env.inventoryByName("metadata-update-l2") + L2NetworkInventory inventory = L2NetworkInventory.valueOf( + dbf.findByUuid(l2.uuid, L2NetworkVO.class)) + def extension = new RecordingDeleteConfirmation(failOnSecondCheck: true) + List extensions = bean(PluginRegistry.class) + .getExtensionList(L2DeleteConfirmExtensionPoint.class) + extensions.add(extension) + def action = new CascadeAction() + .setRootIssuer(L2NetworkVO.simpleName) + .setActionCode(CascadeConstant.DELETION_CHECK_CODE) + ErrorCode failure + + try { + bean(L2NetworkConfirmedDeleteCoordinator.class).prepare( + action, [inventory], new Completion(null) { + @Override + void success() { } + + @Override + void fail(ErrorCode errorCode) { assert false: errorCode } + }) + bean(L2NetworkConfirmedDeleteCoordinator.class).prepare( + action, [inventory], new Completion(null) { + @Override + void success() { assert false } + + @Override + void fail(ErrorCode errorCode) { failure = errorCode } + }) + } finally { + extensions.remove(extension) + } + + assert failure != null + assert extension.begun == [l2.uuid] + assert extension.checked == [l2.uuid, l2.uuid] + assert extension.cancelled == [l2.uuid] + } + + void testDelayedBeginDoesNotBlockCoordinator() { + SdkL2NetworkInventory l2 = env.inventoryByName("metadata-update-l2") + L2NetworkInventory inventory = L2NetworkInventory.valueOf( + dbf.findByUuid(l2.uuid, L2NetworkVO.class)) + def extension = new RecordingDeleteConfirmation(deferBegin: true) + List extensions = bean(PluginRegistry.class) + .getExtensionList(L2DeleteConfirmExtensionPoint.class) + extensions.add(extension) + def action = new CascadeAction() + .setRootIssuer(L2NetworkVO.simpleName) + .setActionCode(CascadeConstant.DELETION_CHECK_CODE) + boolean completed + + try { + bean(L2NetworkConfirmedDeleteCoordinator.class).prepare( + action, [inventory], new Completion(null) { + @Override + void success() { completed = true } + + @Override + void fail(ErrorCode errorCode) { assert false: errorCode } + }) + assert !completed + assert extension.pendingBegin != null + extension.pendingBegin.success() + assert completed + } finally { + extensions.remove(extension) + } + } + + 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 } + }) + } 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.isEmpty() + } + + 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.isEmpty() + } + + 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 failOnSecondBegin + boolean deferBegin + Completion pendingBegin + 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 + void begin(L2NetworkInventory inventory, NetworkDeletionContext context, + Completion completion) { + operationUuids.add(context.operationUuid) + forceDeleteFlags.add(context.forceDelete) + begun.add(inventory.uuid) + events.add("begin:${inventory.uuid}") + if (failOnSecondBegin && begun.size() == 2) { + completion.fail(new ErrorCode("TEST", "simulated begin failure")) + return + } + if (deferBegin) { + pendingBegin = completion + } else { + completion.success() + } + } + + @Override + void check(L2NetworkInventory inventory, NetworkDeletionContext context, + Completion completion) { + checked.add(inventory.uuid) + checkContexts.add(context) + checkCount++ + if (failCheck || failOnSecondCheck && checkCount == 2) { + completion.fail(new ErrorCode("TEST", "simulated confirmation failure")) + } else { + completion.success() + } + } + + @Override + void delete(L2NetworkInventory inventory, NetworkDeletionContext context, + Completion completion) { + if (failDelete) { + completion.fail(new ErrorCode("TEST", "simulated provider delete failure")) + } else { + completion.success() + } + } + + @Override + void cancel(L2NetworkInventory inventory, NetworkDeletionContext context, + Completion completion) { + cancelled.add(inventory.uuid) + completion.success() + } + + @Override + void deleteLocalMetadata(L2NetworkInventory inventory) { + localMetadataDeletes.add(inventory.uuid) + } + } + + 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/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..fddbb43a10a 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,17 @@ 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_NETWORK_L2_10029 = "ORG_ZSTACK_NETWORK_L2_10029"; + 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 +1649,8 @@ public class CloudOperationsErrorCode { public static final String ORG_ZSTACK_CORE_10001 = "ORG_ZSTACK_CORE_10001"; + 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,175 @@ 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_NETWORK_ZNS_10182 = "ORG_ZSTACK_NETWORK_ZNS_10182"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10183 = "ORG_ZSTACK_NETWORK_ZNS_10183"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10184 = "ORG_ZSTACK_NETWORK_ZNS_10184"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10185 = "ORG_ZSTACK_NETWORK_ZNS_10185"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10186 = "ORG_ZSTACK_NETWORK_ZNS_10186"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10187 = "ORG_ZSTACK_NETWORK_ZNS_10187"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10188 = "ORG_ZSTACK_NETWORK_ZNS_10188"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10189 = "ORG_ZSTACK_NETWORK_ZNS_10189"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10190 = "ORG_ZSTACK_NETWORK_ZNS_10190"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10191 = "ORG_ZSTACK_NETWORK_ZNS_10191"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10192 = "ORG_ZSTACK_NETWORK_ZNS_10192"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10193 = "ORG_ZSTACK_NETWORK_ZNS_10193"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10194 = "ORG_ZSTACK_NETWORK_ZNS_10194"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10195 = "ORG_ZSTACK_NETWORK_ZNS_10195"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10196 = "ORG_ZSTACK_NETWORK_ZNS_10196"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10197 = "ORG_ZSTACK_NETWORK_ZNS_10197"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10198 = "ORG_ZSTACK_NETWORK_ZNS_10198"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10199 = "ORG_ZSTACK_NETWORK_ZNS_10199"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10200 = "ORG_ZSTACK_NETWORK_ZNS_10200"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10201 = "ORG_ZSTACK_NETWORK_ZNS_10201"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10202 = "ORG_ZSTACK_NETWORK_ZNS_10202"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10203 = "ORG_ZSTACK_NETWORK_ZNS_10203"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10204 = "ORG_ZSTACK_NETWORK_ZNS_10204"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10205 = "ORG_ZSTACK_NETWORK_ZNS_10205"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10206 = "ORG_ZSTACK_NETWORK_ZNS_10206"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10207 = "ORG_ZSTACK_NETWORK_ZNS_10207"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10208 = "ORG_ZSTACK_NETWORK_ZNS_10208"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10209 = "ORG_ZSTACK_NETWORK_ZNS_10209"; public static final String ORG_ZSTACK_PREMIUM_EXTERNALSERVICE_MARKETPLACE_10000 = "ORG_ZSTACK_PREMIUM_EXTERNALSERVICE_MARKETPLACE_10000"; @@ -15741,6 +15944,14 @@ 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_SDNCONTROLLER_10040 = "ORG_ZSTACK_SDNCONTROLLER_10040"; + 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 e2d2707f2ad19f1c2a1d0ab380d52b6f05792d21 Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Wed, 19 Aug 2026 00:36:05 +0900 Subject: [PATCH 2/8] [zns]: refine segment cloud change flow Change-Id: Iad6fc1b1a1d59d58314ed08592f2fa6ec407d5cf --- conf/db/upgrade/V5.5.38__schema.sql | 54 +++ .../org/zstack/core/rest/RESTFacadeImpl.java | 2 +- .../CompleteZnsSegmentMutationMsg.java | 69 ---- .../network/LocalNetworkConfigChange.java | 7 + .../header/network/NetworkConfigChange.java | 330 ++++++++++++++++++ .../NetworkConfigChangeCoordinator.java | 11 + .../NetworkConfigLocalContinuation.java | 7 - .../header/network/NetworkConfigMutation.java | 330 ------------------ .../NetworkConfigMutationExtensionPoint.java | 11 - .../network/PrepareZnsSegmentMutationMsg.java | 51 --- .../PrepareZnsSegmentMutationReply.java | 15 - .../header/network/l3/AllocateIpMsg.java | 19 - .../header/network/l3/IpAllocateMessage.java | 8 - .../sdncontroller/SdnControllerConstant.java | 4 +- .../java/org/zstack/identity/AccountBase.java | 107 +++--- .../network/l2/L2NetworkCascadeExtension.java | 50 ++- .../L2NetworkConfirmedDeleteCoordinator.java | 243 +++++++------ .../l2/L2NetworkExtensionPointEmitter.java | 64 ++-- .../zstack/network/l2/L2NoVlanNetwork.java | 73 ++-- .../l3/AbstractIpAllocatorStrategy.java | 6 +- .../AscDelayRecycleIpAllocatorStrategy.java | 3 +- .../l3/FirstAvailableIpAllocatorStrategy.java | 3 +- .../FirstAvailableIpv6AllocatorStrategy.java | 3 +- .../org/zstack/network/l3/L3BasicNetwork.java | 126 +++++-- .../zstack/network/l3/L3NetworkManager.java | 3 - .../network/l3/L3NetworkManagerImpl.java | 37 +- .../network/l3/RandomIpAllocatorStrategy.java | 3 +- .../l3/RandomIpv6AllocatorStrategy.java | 3 +- .../zstack/sdnController/SdnController.java | 7 +- .../SdnControllerApiInterceptor.java | 8 +- .../sdnController/SdnControllerBase.java | 12 +- .../zstack/sdnController/SdnControllerL2.java | 33 ++ .../header/PullSdnControllerMsg.java | 8 - .../l2/vxlan/vxlanNetwork/VxlanNetwork.java | 4 +- .../core/rest/RestFacadeCase.groovy | 8 +- .../l2network/L2NetworkCascadeCase.groovy | 82 +---- .../SdnControllerApiInterceptorTest.java | 19 + .../CloudOperationsErrorCode.java | 8 + 38 files changed, 930 insertions(+), 901 deletions(-) delete mode 100644 header/src/main/java/org/zstack/header/network/CompleteZnsSegmentMutationMsg.java create mode 100644 header/src/main/java/org/zstack/header/network/LocalNetworkConfigChange.java create mode 100644 header/src/main/java/org/zstack/header/network/NetworkConfigChange.java create mode 100644 header/src/main/java/org/zstack/header/network/NetworkConfigChangeCoordinator.java delete mode 100644 header/src/main/java/org/zstack/header/network/NetworkConfigLocalContinuation.java delete mode 100644 header/src/main/java/org/zstack/header/network/NetworkConfigMutation.java delete mode 100644 header/src/main/java/org/zstack/header/network/NetworkConfigMutationExtensionPoint.java delete mode 100644 header/src/main/java/org/zstack/header/network/PrepareZnsSegmentMutationMsg.java delete mode 100644 header/src/main/java/org/zstack/header/network/PrepareZnsSegmentMutationReply.java create mode 100644 test/src/test/java/org/zstack/sdnController/SdnControllerApiInterceptorTest.java diff --git a/conf/db/upgrade/V5.5.38__schema.sql b/conf/db/upgrade/V5.5.38__schema.sql index f56c68348ac..778c753a14b 100644 --- a/conf/db/upgrade/V5.5.38__schema.sql +++ b/conf/db/upgrade/V5.5.38__schema.sql @@ -129,3 +129,57 @@ CREATE TABLE IF NOT EXISTS `zstack`.`ZnsSegmentRefVO` ( 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_controller_capability_validate_insert`; +DELIMITER $$ +CREATE TRIGGER `zstack`.`trg_zns_controller_capability_validate_insert` +BEFORE INSERT ON `zstack`.`ZnsControllerCapabilityVO` +FOR EACH ROW +BEGIN + IF NEW.`observedState` NOT IN ('NOT_ACTIVATED', 'MIGRATING_READ_ONLY', 'ACTIVE', 'INCOMPATIBLE_READ_ONLY') + OR NEW.`effectiveState` NOT IN ('NOT_ACTIVATED', 'MIGRATING_READ_ONLY', 'ACTIVE', 'INCOMPATIBLE_READ_ONLY') THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'ZnsControllerCapabilityVO has an invalid capability state'; + END IF; +END$$ +DELIMITER ; + +DROP TRIGGER IF EXISTS `zstack`.`trg_zns_controller_capability_validate_update`; +DELIMITER $$ +CREATE TRIGGER `zstack`.`trg_zns_controller_capability_validate_update` +BEFORE UPDATE ON `zstack`.`ZnsControllerCapabilityVO` +FOR EACH ROW +BEGIN + IF NEW.`observedState` NOT IN ('NOT_ACTIVATED', 'MIGRATING_READ_ONLY', 'ACTIVE', 'INCOMPATIBLE_READ_ONLY') + OR NEW.`effectiveState` NOT IN ('NOT_ACTIVATED', 'MIGRATING_READ_ONLY', 'ACTIVE', 'INCOMPATIBLE_READ_ONLY') THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'ZnsControllerCapabilityVO has an invalid capability state'; + END IF; +END$$ +DELIMITER ; + +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/core/src/main/java/org/zstack/core/rest/RESTFacadeImpl.java b/core/src/main/java/org/zstack/core/rest/RESTFacadeImpl.java index d227d888caa..fa04921d861 100755 --- a/core/src/main/java/org/zstack/core/rest/RESTFacadeImpl.java +++ b/core/src/main/java/org/zstack/core/rest/RESTFacadeImpl.java @@ -1093,7 +1093,7 @@ static void writeSyncHttpResponse(SyncHttpResponse response, if (response.getBody() == null) { return; } - servletResponse.setHeader("Content-Type", "application/json"); + servletResponse.setContentType(RESTConstant.APP_JSON_UTF8); try { servletResponse.getWriter().write(response.getBody()); } catch (IOException e) { diff --git a/header/src/main/java/org/zstack/header/network/CompleteZnsSegmentMutationMsg.java b/header/src/main/java/org/zstack/header/network/CompleteZnsSegmentMutationMsg.java deleted file mode 100644 index 0c160f481aa..00000000000 --- a/header/src/main/java/org/zstack/header/network/CompleteZnsSegmentMutationMsg.java +++ /dev/null @@ -1,69 +0,0 @@ -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/LocalNetworkConfigChange.java b/header/src/main/java/org/zstack/header/network/LocalNetworkConfigChange.java new file mode 100644 index 00000000000..9d03f776dad --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/LocalNetworkConfigChange.java @@ -0,0 +1,7 @@ +package org.zstack.header.network; + +import org.zstack.header.core.Completion; + +public interface LocalNetworkConfigChange { + void apply(Completion completion); +} diff --git a/header/src/main/java/org/zstack/header/network/NetworkConfigChange.java b/header/src/main/java/org/zstack/header/network/NetworkConfigChange.java new file mode 100644 index 00000000000..e89bb057fe5 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/NetworkConfigChange.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 NetworkConfigChange { + public enum Kind { + L2_ENCAPSULATION_CHANGE, + L2_METADATA_CHANGE, + L3_CREATE, + IP_RANGE_CONFIGURATION, + DHCP_DNS_CONFIGURATION + } + + public static final class L2MetadataChange { + private final String name; + private final String description; + + private L2MetadataChange(String name, String description) { + this.name = name; + this.description = description; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + } + + public static final class L2EncapsulationChange { + private final String l2Type; + private final int virtualNetworkId; + + private L2EncapsulationChange(String l2Type, int virtualNetworkId) { + this.l2Type = l2Type; + this.virtualNetworkId = virtualNetworkId; + } + + public String getL2Type() { + return l2Type; + } + + public int getVirtualNetworkId() { + return virtualNetworkId; + } + } + + public static final class L3Creation { + private final String l3Uuid; + private final String l3Type; + private final List systemTags; + + private L3Creation(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 IpRange { + private final String rangeUuid; + private final String startIp; + private final String endIp; + + public IpRange(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 IpRangeConfiguration { + private final String l3Uuid; + private final int ipVersion; + private final String gatewayAddress; + private final List ranges; + private final boolean delete; + + private IpRangeConfiguration(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 DhcpDnsConfiguration { + private final String l3Uuid; + private final boolean enabled; + private final List systemTags; + private final Integer ipVersion; + private final List dnsServers; + + private DhcpDnsConfiguration(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 L2EncapsulationChange l2EncapsulationChange; + private final L2MetadataChange l2MetadataChange; + private final L3Creation l3Creation; + private final IpRangeConfiguration ipRangeConfiguration; + private final DhcpDnsConfiguration dhcpDnsConfiguration; + + private NetworkConfigChange(Kind kind, String l2Uuid, NetworkOperationOrigin origin, + String operationUuid, String accountUuid, + L2EncapsulationChange l2EncapsulationChange, + L2MetadataChange l2MetadataChange, + L3Creation l3Creation, + IpRangeConfiguration ipRangeConfiguration, + DhcpDnsConfiguration dhcpDnsConfiguration) { + this.kind = kind; + this.l2Uuid = l2Uuid; + this.origin = origin; + this.operationUuid = operationUuid; + this.accountUuid = accountUuid; + this.l2EncapsulationChange = l2EncapsulationChange; + this.l2MetadataChange = l2MetadataChange; + this.l3Creation = l3Creation; + this.ipRangeConfiguration = ipRangeConfiguration; + this.dhcpDnsConfiguration = dhcpDnsConfiguration; + } + + public static NetworkConfigChange changeL2Encapsulation(String l2Uuid, + NetworkOperationOrigin origin, + String operationUuid, + String accountUuid, + String l2Type, + int virtualNetworkId) { + return new NetworkConfigChange(Kind.L2_ENCAPSULATION_CHANGE, l2Uuid, origin, operationUuid, + accountUuid, new L2EncapsulationChange(l2Type, virtualNetworkId), null, + null, null, null); + } + + public static NetworkConfigChange updateL2Metadata(String l2Uuid, + NetworkOperationOrigin origin, + String operationUuid, + String accountUuid, + String name, + String description) { + return new NetworkConfigChange(Kind.L2_METADATA_CHANGE, l2Uuid, origin, operationUuid, + accountUuid, null, new L2MetadataChange(name, description), + null, null, null); + } + + public static NetworkConfigChange createL3(String l2Uuid, + NetworkOperationOrigin origin, + String operationUuid, + String accountUuid, + String l3Uuid, + String l3Type, + List systemTags) { + return new NetworkConfigChange(Kind.L3_CREATE, l2Uuid, origin, operationUuid, + accountUuid, null, null, + new L3Creation(l3Uuid, l3Type, systemTags), null, null); + } + + public static NetworkConfigChange replaceIpRangeConfiguration(String l2Uuid, + NetworkOperationOrigin origin, + String operationUuid, + String accountUuid, + String l3Uuid, + int ipVersion, + String gatewayAddress, + List ranges) { + return new NetworkConfigChange(Kind.IP_RANGE_CONFIGURATION, l2Uuid, origin, operationUuid, + accountUuid, null, null, null, + new IpRangeConfiguration(l3Uuid, ipVersion, gatewayAddress, ranges, false), null); + } + + public static NetworkConfigChange removeIpRangeConfiguration(String l2Uuid, + NetworkOperationOrigin origin, + String operationUuid, + String accountUuid, + String l3Uuid, + int ipVersion) { + return new NetworkConfigChange(Kind.IP_RANGE_CONFIGURATION, l2Uuid, origin, operationUuid, + accountUuid, null, null, null, + new IpRangeConfiguration(l3Uuid, ipVersion, null, Collections.emptyList(), true), null); + } + + public static NetworkConfigChange updateDhcpConfiguration(String l2Uuid, + NetworkOperationOrigin origin, + String operationUuid, + String accountUuid, + String l3Uuid, + boolean enabled, + List systemTags) { + return new NetworkConfigChange(Kind.DHCP_DNS_CONFIGURATION, l2Uuid, origin, operationUuid, + accountUuid, null, null, null, null, + new DhcpDnsConfiguration(l3Uuid, enabled, systemTags, null, null)); + } + + public static NetworkConfigChange updateDnsConfiguration(String l2Uuid, + NetworkOperationOrigin origin, + String operationUuid, + String accountUuid, + String l3Uuid, + int ipVersion, + List dnsServers) { + return new NetworkConfigChange(Kind.DHCP_DNS_CONFIGURATION, l2Uuid, origin, operationUuid, + accountUuid, null, null, null, null, + new DhcpDnsConfiguration(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 L2EncapsulationChange getL2EncapsulationChange() { + return l2EncapsulationChange; + } + + public L2MetadataChange getL2MetadataChange() { + return l2MetadataChange; + } + + public L3Creation getL3Creation() { + return l3Creation; + } + + public IpRangeConfiguration getIpRangeConfiguration() { + return ipRangeConfiguration; + } + + public DhcpDnsConfiguration getDhcpDnsConfiguration() { + return dhcpDnsConfiguration; + } +} diff --git a/header/src/main/java/org/zstack/header/network/NetworkConfigChangeCoordinator.java b/header/src/main/java/org/zstack/header/network/NetworkConfigChangeCoordinator.java new file mode 100644 index 00000000000..3499ef53de3 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/NetworkConfigChangeCoordinator.java @@ -0,0 +1,11 @@ +package org.zstack.header.network; + +import org.zstack.header.core.Completion; + +public interface NetworkConfigChangeCoordinator { + boolean isApplicable(NetworkConfigChange change); + + void coordinate(NetworkConfigChange change, + LocalNetworkConfigChange localChange, + Completion completion); +} diff --git a/header/src/main/java/org/zstack/header/network/NetworkConfigLocalContinuation.java b/header/src/main/java/org/zstack/header/network/NetworkConfigLocalContinuation.java deleted file mode 100644 index 26b00a0ac58..00000000000 --- a/header/src/main/java/org/zstack/header/network/NetworkConfigLocalContinuation.java +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index 53035115c17..00000000000 --- a/header/src/main/java/org/zstack/header/network/NetworkConfigMutation.java +++ /dev/null @@ -1,330 +0,0 @@ -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 deleted file mode 100644 index 2bc95a80914..00000000000 --- a/header/src/main/java/org/zstack/header/network/NetworkConfigMutationExtensionPoint.java +++ /dev/null @@ -1,11 +0,0 @@ -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/PrepareZnsSegmentMutationMsg.java b/header/src/main/java/org/zstack/header/network/PrepareZnsSegmentMutationMsg.java deleted file mode 100644 index 0286c4845e6..00000000000 --- a/header/src/main/java/org/zstack/header/network/PrepareZnsSegmentMutationMsg.java +++ /dev/null @@ -1,51 +0,0 @@ -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 deleted file mode 100644 index 479280f8d14..00000000000 --- a/header/src/main/java/org/zstack/header/network/PrepareZnsSegmentMutationReply.java +++ /dev/null @@ -1,15 +0,0 @@ -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/l3/AllocateIpMsg.java b/header/src/main/java/org/zstack/header/network/l3/AllocateIpMsg.java index e75bea0b25d..750c4dcd304 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,8 +16,6 @@ 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; @@ -117,21 +115,4 @@ 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/IpAllocateMessage.java b/header/src/main/java/org/zstack/header/network/l3/IpAllocateMessage.java index 2df457e9755..0d886bab7b5 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,14 +31,6 @@ 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/sdncontroller/SdnControllerConstant.java b/header/src/main/java/org/zstack/header/network/sdncontroller/SdnControllerConstant.java index 6077e352049..75246260a39 100644 --- a/header/src/main/java/org/zstack/header/network/sdncontroller/SdnControllerConstant.java +++ b/header/src/main/java/org/zstack/header/network/sdncontroller/SdnControllerConstant.java @@ -57,7 +57,9 @@ public enum Operations { public enum ResourceTypes{ SdnController, VxlanNetworkPool, - VxlanNetwork + VxlanNetwork, + Segment, + TenantRouter } public enum Params { diff --git a/identity/src/main/java/org/zstack/identity/AccountBase.java b/identity/src/main/java/org/zstack/identity/AccountBase.java index 0615383675f..befd26a69a2 100755 --- a/identity/src/main/java/org/zstack/identity/AccountBase.java +++ b/identity/src/main/java/org/zstack/identity/AccountBase.java @@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Configurable; import org.springframework.transaction.annotation.Transactional; 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; @@ -23,12 +24,14 @@ import org.zstack.core.workflow.FlowChainBuilder; import org.zstack.core.workflow.ShareFlow; import org.zstack.header.core.Completion; +import org.zstack.header.core.WhileDoneCompletion; import org.zstack.header.core.workflow.FlowChain; import org.zstack.header.core.workflow.FlowDoneHandler; import org.zstack.header.core.workflow.FlowErrorHandler; import org.zstack.header.core.workflow.FlowTrigger; import org.zstack.header.core.workflow.NoRollbackFlow; import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.errorcode.ErrorCodeList; import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.identity.*; import org.zstack.header.identity.IdentityCanonicalEvents.AccountDeletedData; @@ -49,7 +52,9 @@ import javax.persistence.Query; import javax.persistence.Tuple; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -171,7 +176,7 @@ private void deleteAccount(Completion completion) { .setActionCode(CascadeConstant.DELETION_DELETE_CODE); final List extensions = pluginRgty.getExtensionList(BeforeAccountCascadeDeleteExtensionPoint.class); - final List prepared = new ArrayList<>(); + final Deque prepared = new ArrayDeque<>(); List resourceUuids = Q.New(AccountResourceRefVO.class) .select(AccountResourceRefVO_.resourceUuid) .eq(AccountResourceRefVO_.ownerAccountUuid, self.getUuid()) @@ -186,7 +191,7 @@ public void setup() { @Override public void run(FlowTrigger trigger, Map data) { - prepareAccountDelete(account, cascadeAction, extensions, 0, prepared, + prepareAccountDelete(account, cascadeAction, extensions, prepared, new Completion(trigger) { @Override public void success() { @@ -247,38 +252,45 @@ public void handle(ErrorCode errCode, Map data) { private void prepareAccountDelete(AccountInventory account, CascadeAction action, List extensions, - int index, - List prepared, + Deque prepared, Completion completion) { - if (index == extensions.size()) { - completion.success(); - return; - } - BeforeAccountCascadeDeleteExtensionPoint extension = extensions.get(index); - try { - extension.beforeDelete(account, action, new Completion(completion) { - @Override - public void success() { - prepared.add(extension); - prepareAccountDelete(account, action, extensions, index + 1, prepared, completion); - } + new While<>(extensions).each((extension, whileCompletion) -> { + try { + extension.beforeDelete(account, action, new Completion(whileCompletion) { + @Override + public void success() { + prepared.push(extension); + whileCompletion.done(); + } - @Override - public void fail(ErrorCode errorCode) { - failAccountDeletePreparation(account, action, prepared, errorCode, completion); + @Override + public void fail(ErrorCode errorCode) { + whileCompletion.addError(errorCode); + whileCompletion.allDone(); + } + }); + } catch (RuntimeException e) { + whileCompletion.addError(errf.throwableToInternalError(e)); + whileCompletion.allDone(); + } + }).run(new WhileDoneCompletion(completion) { + @Override + public void done(ErrorCodeList errors) { + if (errors.getCauses().isEmpty()) { + completion.success(); + return; } - }); - } catch (RuntimeException e) { - failAccountDeletePreparation(account, action, prepared, - errf.throwableToInternalError(e), completion); - } + failAccountDeletePreparation(account, action, prepared, + errors.getCauses().get(0), completion); + } + }); } private void failAccountDeletePreparation( AccountInventory account, CascadeAction action, - List prepared, + Deque prepared, ErrorCode errorCode, Completion completion) { - cancelAccountDelete(account, action, prepared, prepared.size() - 1, + cancelAccountDelete(account, action, prepared, new Completion(completion) { @Override public void success() { @@ -293,27 +305,30 @@ public void fail(ErrorCode cancelError) { } private void cancelAccountDelete(AccountInventory account, CascadeAction action, - List prepared, - int index, Completion completion) { - if (index < 0) { - completion.success(); - return; - } - try { - prepared.get(index).cancel(account, action, new Completion(completion) { - @Override - public void success() { - cancelAccountDelete(account, action, prepared, index - 1, completion); - } + Deque prepared, + Completion completion) { + new While<>(prepared).each((extension, whileCompletion) -> { + try { + extension.cancel(account, action, new Completion(whileCompletion) { + @Override + public void success() { + whileCompletion.done(); + } - @Override - public void fail(ErrorCode errorCode) { - cancelAccountDelete(account, action, prepared, index - 1, completion); - } - }); - } catch (RuntimeException e) { - cancelAccountDelete(account, action, prepared, index - 1, completion); - } + @Override + public void fail(ErrorCode errorCode) { + whileCompletion.done(); + } + }); + } catch (RuntimeException ignored) { + whileCompletion.done(); + } + }).run(new WhileDoneCompletion(completion) { + @Override + public void done(ErrorCodeList errors) { + completion.success(); + } + }); } private void handle(final APIDeleteAccountMsg msg) { 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 b7282732abb..d4b830ce975 100755 --- a/network/src/main/java/org/zstack/network/l2/L2NetworkCascadeExtension.java +++ b/network/src/main/java/org/zstack/network/l2/L2NetworkCascadeExtension.java @@ -128,20 +128,25 @@ private void handleDeletion(final CascadeAction action, final Completion complet } l2invs = filterInventories(l2invs, action); + List confirmedInventories = confirmedInventories(l2invs); List msgs = new ArrayList(); for (L2NetworkInventory l2inv : l2invs) { 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()); + if (confirmedInventories.contains(l2inv)) { + 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); } List finalL2invs = l2invs; + List finalConfirmedInventories = confirmedInventories; bus.send(msgs, new CloudBusListCallBack(completion) { @Override public void run(List replies) { @@ -158,7 +163,7 @@ public void run(List replies) { for (MessageReply r : replies) { L2NetworkInventory inv = finalL2invs.get(replies.indexOf(r)); if (!r.isSuccess()) { - if (!confirmedDeleteCoordinator.confirmedExtensions(inv).isEmpty()) { + if (finalConfirmedInventories.contains(inv)) { completion.fail(r.getError()); return; } @@ -167,23 +172,29 @@ public void run(List replies) { logger.debug(String.format("delete l2 network[uuid:%s, name:%s]", inv.getUuid(), inv.getName())); } - confirmedDeleteCoordinator.complete(action, finalL2invs, new Completion(completion) { + if (finalConfirmedInventories.isEmpty()) { + dbf.removeByPrimaryKeys(uuids, L2NetworkVO.class); + completion.success(); + return; + } + + confirmedDeleteCoordinator.complete(action, finalConfirmedInventories, new Completion(completion) { @Override public void success() { try { new SQLBatch() { @Override protected void scripts() { - for (L2NetworkInventory inv : finalL2invs) { + for (L2NetworkInventory inv : finalConfirmedInventories) { for (L2DeleteConfirmExtensionPoint ext : confirmedDeleteCoordinator.confirmedExtensions(inv)) { ext.deleteLocalMetadata(inv, NetworkDeletionContexts.get(action, inv.getUuid())); } } - databaseFacade.removeByPrimaryKeys(uuids, L2NetworkVO.class); } }.execute(); + dbf.removeByPrimaryKeys(uuids, L2NetworkVO.class); completion.success(); } catch (RuntimeException e) { completion.fail(inerr(ORG_ZSTACK_NETWORK_L2_10022, @@ -207,19 +218,20 @@ private void handleDeletionCheck(CascadeAction action, Completion completion) { return; } - List filtered = filterInventories(l2invs, action); try { - for (L2NetworkInventory prinv : filtered) { + for (L2NetworkInventory prinv : l2invs) { extpEmitter.preDelete(prinv); } } catch (L2NetworkException e) { - completion.fail(inerr(ORG_ZSTACK_NETWORK_L2_10000, e.getMessage())); + completion.fail(inerr(ORG_ZSTACK_NETWORK_L2_10031, e.getMessage())); return; } - if (confirmedDeleteCoordinator.isPrepared(action, filtered)) { + List confirmedInventories = confirmedInventories(l2invs); + if (confirmedInventories.isEmpty() + || confirmedDeleteCoordinator.isPrepared(action, confirmedInventories)) { completion.success(); } else { - confirmedDeleteCoordinator.prepare(action, filtered, completion); + confirmedDeleteCoordinator.prepare(action, confirmedInventories, completion); } } @@ -229,7 +241,7 @@ public void prepareConfirmedDelete(CascadeAction action, Completion completion) completion.success(); return; } - confirmedDeleteCoordinator.prepare(action, filterInventories(l2invs, action), completion); + confirmedDeleteCoordinator.prepare(action, confirmedInventories(l2invs), completion); } public void cancelConfirmedDelete(CascadeAction action, Completion completion) { @@ -238,7 +250,7 @@ public void cancelConfirmedDelete(CascadeAction action, Completion completion) { completion.success(); return; } - confirmedDeleteCoordinator.cancel(action, filterInventories(l2invs, action), completion); + confirmedDeleteCoordinator.cancel(action, confirmedInventories(l2invs), completion); } private List filterInventories(List inventories, @@ -254,6 +266,16 @@ private List filterInventories(List inve return result; } + private List confirmedInventories(List inventories) { + List result = new ArrayList<>(); + for (L2NetworkInventory inventory : inventories) { + if (!confirmedDeleteCoordinator.confirmedExtensions(inventory).isEmpty()) { + result.add(inventory); + } + } + return result; + } + @Override public List getEdgeNames() { return Arrays.asList(ZoneVO.class.getSimpleName(), diff --git a/network/src/main/java/org/zstack/network/l2/L2NetworkConfirmedDeleteCoordinator.java b/network/src/main/java/org/zstack/network/l2/L2NetworkConfirmedDeleteCoordinator.java index 8bdb0bb92b4..d76d908ef00 100644 --- a/network/src/main/java/org/zstack/network/l2/L2NetworkConfirmedDeleteCoordinator.java +++ b/network/src/main/java/org/zstack/network/l2/L2NetworkConfirmedDeleteCoordinator.java @@ -2,11 +2,15 @@ import org.springframework.beans.factory.annotation.Autowired; import org.zstack.core.Platform; +import org.zstack.core.asyncbatch.While; import org.zstack.core.cascade.CascadeAction; import org.zstack.core.cascade.CascadeConstant; import org.zstack.core.componentloader.PluginRegistry; import org.zstack.header.core.Completion; +import org.zstack.header.core.WhileCompletion; +import org.zstack.header.core.WhileDoneCompletion; import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.errorcode.ErrorCodeList; import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.network.l2.L2DeleteConfirmExtensionPoint; import org.zstack.header.network.l2.L2NetworkInventory; @@ -30,8 +34,20 @@ public class L2NetworkConfirmedDeleteCoordinator { public void prepare(CascadeAction action, List inventories, Completion completion) { - prepare(action, inventories == null ? Collections.emptyList() : inventories, - 0, new ArrayList<>(), completion); + List begun = new ArrayList<>(); + new While<>(inventories == null ? Collections.emptyList() : inventories) + .each((inventory, inventoryCompletion) -> prepareInventory(action, inventory, begun, + inventoryCompletion)) + .run(new WhileDoneCompletion(completion) { + @Override + public void done(ErrorCodeList errors) { + if (errors.getCauses().isEmpty()) { + completion.success(); + } else { + failPrepared(action, begun, errors.getCauses().get(0), completion); + } + } + }); } public void cancel(CascadeAction action, List inventories, @@ -53,7 +69,7 @@ public void cancel(CascadeAction action, List inventories, completion.fail(toError(e)); return; } - cancel(action, prepared, prepared.size() - 1, completion); + cancelPrepared(action, prepared, completion); } public void complete(CascadeAction action, List inventories, @@ -75,7 +91,7 @@ public void complete(CascadeAction action, List inventories, completion.fail(toError(e)); return; } - complete(prepared, 0, completion); + complete(prepared, completion); } public List confirmedExtensions(L2NetworkInventory inventory) { @@ -95,23 +111,18 @@ public boolean isPrepared(CascadeAction action, List invento return true; } - private void prepare(CascadeAction action, List inventories, int inventoryIndex, - List begun, Completion completion) { - if (inventoryIndex == inventories.size()) { - completion.success(); - return; - } - - L2NetworkInventory inventory = inventories.get(inventoryIndex); + private void prepareInventory(CascadeAction action, L2NetworkInventory inventory, + List begun, WhileCompletion completion) { List extensions; try { extensions = confirmedExtensions(inventory); } catch (RuntimeException e) { - failPrepared(action, begun, toError(e), completion); + completion.addError(toError(e)); + completion.allDone(); return; } if (extensions.isEmpty()) { - prepare(action, inventories, inventoryIndex + 1, begun, completion); + completion.done(); return; } @@ -128,68 +139,84 @@ private void prepare(CascadeAction action, List inventories, for (L2DeleteConfirmExtensionPoint extension : extensions) { begun.add(new ConfirmedDelete(inventory, extension, finalContext)); } - check(action, inventories, inventoryIndex, extensions, 0, begun, inventory, - finalContext, completion); + check(inventory, extensions, finalContext, completion); return; } - begin(action, inventories, inventoryIndex, extensions, 0, begun, inventory, - finalContext, completion); + begin(action, extensions, begun, inventory, finalContext, completion); } - private void begin(CascadeAction action, List inventories, int inventoryIndex, - List extensions, int extensionIndex, + private void begin(CascadeAction action, List extensions, List begun, L2NetworkInventory inventory, - NetworkDeletionContext context, Completion completion) { - if (extensionIndex == extensions.size()) { - NetworkDeletionContexts.markPrepared(action, inventory.getUuid(), true); - check(action, inventories, inventoryIndex, extensions, 0, begun, inventory, - context, completion); - return; - } - - L2DeleteConfirmExtensionPoint extension = extensions.get(extensionIndex); - invoke(() -> extension.begin(inventory, context, new Completion(completion) { - @Override - public void success() { - begun.add(new ConfirmedDelete(inventory, extension, context)); - begin(action, inventories, inventoryIndex, extensions, extensionIndex + 1, - begun, inventory, context, completion); + NetworkDeletionContext context, WhileCompletion completion) { + new While<>(extensions).each((extension, extensionCompletion) -> { + try { + extension.begin(inventory, context, new Completion(extensionCompletion) { + @Override + public void success() { + begun.add(new ConfirmedDelete(inventory, extension, context)); + extensionCompletion.done(); + } + + @Override + public void fail(ErrorCode errorCode) { + extensionCompletion.addError(errorCode); + extensionCompletion.allDone(); + } + }); + } catch (RuntimeException e) { + extensionCompletion.addError(toError(e)); + extensionCompletion.allDone(); } - + }).run(new WhileDoneCompletion(completion) { @Override - public void fail(ErrorCode errorCode) { - failPrepared(action, begun, errorCode, completion); + public void done(ErrorCodeList errors) { + if (!errors.getCauses().isEmpty()) { + completion.addError(errors.getCauses().get(0)); + completion.allDone(); + return; + } + NetworkDeletionContexts.markPrepared(action, inventory.getUuid(), true); + check(inventory, extensions, context, completion); } - }), begun, action, completion); + }); } - private void check(CascadeAction action, List inventories, int inventoryIndex, - List extensions, int extensionIndex, - List begun, L2NetworkInventory inventory, - NetworkDeletionContext context, Completion completion) { - if (extensionIndex == extensions.size()) { - prepare(action, inventories, inventoryIndex + 1, begun, completion); - return; - } - - L2DeleteConfirmExtensionPoint extension = extensions.get(extensionIndex); - invoke(() -> extension.check(inventory, context, new Completion(completion) { - @Override - public void success() { - check(action, inventories, inventoryIndex, extensions, extensionIndex + 1, - begun, inventory, context, completion); + private void check(L2NetworkInventory inventory, List extensions, + NetworkDeletionContext context, WhileCompletion completion) { + new While<>(extensions).each((extension, extensionCompletion) -> { + try { + extension.check(inventory, context, new Completion(extensionCompletion) { + @Override + public void success() { + extensionCompletion.done(); + } + + @Override + public void fail(ErrorCode errorCode) { + extensionCompletion.addError(errorCode); + extensionCompletion.allDone(); + } + }); + } catch (RuntimeException e) { + extensionCompletion.addError(toError(e)); + extensionCompletion.allDone(); } - + }).run(new WhileDoneCompletion(completion) { @Override - public void fail(ErrorCode errorCode) { - failPrepared(action, begun, errorCode, completion); + public void done(ErrorCodeList errors) { + if (errors.getCauses().isEmpty()) { + completion.done(); + } else { + completion.addError(errors.getCauses().get(0)); + completion.allDone(); + } } - }), begun, action, completion); + }); } private void failPrepared(CascadeAction action, List begun, ErrorCode error, Completion completion) { - cancel(action, begun, begun.size() - 1, new Completion(completion) { + cancelPrepared(action, begun, new Completion(completion) { @Override public void success() { completion.fail(error); @@ -202,61 +229,61 @@ public void fail(ErrorCode cancelError) { }); } - private void cancel(CascadeAction action, List prepared, int index, - Completion completion) { - if (index < 0) { - completion.success(); - return; - } - - ConfirmedDelete delete = prepared.get(index); - invokeCancel(delete, new Completion(completion) { - @Override - public void success() { - NetworkDeletionContexts.markPrepared(action, delete.inventory.getUuid(), false); - cancel(action, prepared, index - 1, completion); - } - + private void cancelPrepared(CascadeAction action, List prepared, Completion completion) { + List reverse = new ArrayList<>(prepared); + Collections.reverse(reverse); + new While<>(reverse).each((delete, cancelCompletion) -> invokeCancel(delete, + new Completion(cancelCompletion) { + @Override + public void success() { + NetworkDeletionContexts.markPrepared(action, delete.inventory.getUuid(), false); + cancelCompletion.done(); + } + + @Override + public void fail(ErrorCode errorCode) { + logger.warn(String.format("failed to cancel confirmed L2Network deletion[uuid:%s]: %s", + delete.inventory.getUuid(), errorCode)); + NetworkDeletionContexts.markPrepared(action, delete.inventory.getUuid(), false); + cancelCompletion.done(); + } + })).run(new WhileDoneCompletion(completion) { @Override - public void fail(ErrorCode errorCode) { - logger.warn(String.format("failed to cancel confirmed L2Network deletion[uuid:%s]: %s", - delete.inventory.getUuid(), errorCode)); - NetworkDeletionContexts.markPrepared(action, delete.inventory.getUuid(), false); - cancel(action, prepared, index - 1, completion); + public void done(ErrorCodeList errors) { + completion.success(); } }); } - private void complete(List prepared, int index, Completion completion) { - if (index == prepared.size()) { - completion.success(); - return; - } - ConfirmedDelete delete = prepared.get(index); - try { - delete.extension.delete(delete.inventory, delete.context, new Completion(completion) { - @Override - public void success() { - complete(prepared, index + 1, completion); - } - - @Override - public void fail(ErrorCode errorCode) { - completion.fail(errorCode); + private void complete(List prepared, Completion completion) { + new While<>(prepared).each((delete, deleteCompletion) -> { + try { + delete.extension.delete(delete.inventory, delete.context, new Completion(deleteCompletion) { + @Override + public void success() { + deleteCompletion.done(); + } + + @Override + public void fail(ErrorCode errorCode) { + deleteCompletion.addError(errorCode); + deleteCompletion.allDone(); + } + }); + } catch (RuntimeException e) { + deleteCompletion.addError(toError(e)); + deleteCompletion.allDone(); + } + }).run(new WhileDoneCompletion(completion) { + @Override + public void done(ErrorCodeList errors) { + if (errors.getCauses().isEmpty()) { + completion.success(); + } else { + completion.fail(errors.getCauses().get(0)); } - }); - } catch (RuntimeException e) { - completion.fail(toError(e)); - } - } - - private void invoke(Runnable runnable, List begun, CascadeAction action, - Completion completion) { - try { - runnable.run(); - } catch (RuntimeException e) { - failPrepared(action, begun, toError(e), completion); - } + } + }); } private void invokeCancel(ConfirmedDelete delete, Completion completion) { 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 14beaaa63f3..779beead49e 100755 --- a/network/src/main/java/org/zstack/network/l2/L2NetworkExtensionPointEmitter.java +++ b/network/src/main/java/org/zstack/network/l2/L2NetworkExtensionPointEmitter.java @@ -1,10 +1,13 @@ package org.zstack.network.l2; import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.core.asyncbatch.While; import org.zstack.core.componentloader.PluginRegistry; import org.zstack.header.Component; import org.zstack.header.core.Completion; +import org.zstack.header.core.WhileDoneCompletion; import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.errorcode.ErrorCodeList; import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.network.l2.L2NetworkDeleteExtensionPoint; import org.zstack.header.network.l2.L2NetworkException; @@ -74,36 +77,41 @@ 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); - } + new While<>(updateExtensions).each((extension, extensionCompletion) -> { + try { + extension.beforeChangeL2NetworkVlanId(inv, new Completion(extensionCompletion) { + @Override + public void success() { + extensionCompletion.done(); + } - @Override - public void fail(ErrorCode errorCode) { - completion.fail(errorCode); + @Override + public void fail(ErrorCode errorCode) { + extensionCompletion.addError(errorCode); + extensionCompletion.allDone(); + } + }); + } catch (OperationFailureException e) { + extensionCompletion.addError(e.getErrorCode()); + extensionCompletion.allDone(); + } catch (RuntimeException e) { + logger.warn(String.format("exception in L2NetworkUpdateExtensionPoint.beforeChangeL2NetworkVlanId of %s", + extension.getClass().getCanonicalName()), e); + extensionCompletion.addError(inerr(ORG_ZSTACK_NETWORK_L2_10024, + "l2 network update extension[%s] failed before changing vlan id: %s", + extension.getClass().getCanonicalName(), e.getMessage())); + extensionCompletion.allDone(); + } + }).run(new WhileDoneCompletion(completion) { + @Override + public void done(ErrorCodeList errors) { + if (errors.getCauses().isEmpty()) { + completion.success(); + } else { + completion.fail(errors.getCauses().get(0)); } - }); - } 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) { 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 780689fa86a..9a1bfd6f3c1 100755 --- a/network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java +++ b/network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java @@ -40,9 +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.header.network.LocalNetworkConfigChange; +import org.zstack.header.network.NetworkConfigChange; +import org.zstack.header.network.NetworkConfigChangeCoordinator; import org.zstack.network.l3.ServiceTypeExtensionPoint; import org.zstack.utils.Utils; import org.zstack.utils.logging.CLogger; @@ -137,6 +137,12 @@ private void handleLocalMessage(Message msg) { private void handle(UpdateL2NetworkMetadataMsg msg) { MessageReply reply = new MessageReply(); + NetworkCreateContext context = msg.getContext(); + if (context == null || !context.isProjection() || context.getExternalRef() == null) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L2_10030, + "L2 network metadata projection requires an external network reference")); + return; + } if (Objects.equals(self.getName(), msg.getName()) && Objects.equals(self.getDescription(), msg.getDescription())) { bus.reply(msg, reply); @@ -146,17 +152,17 @@ private void handle(UpdateL2NetworkMetadataMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { - return String.format("update-l2-network-%s-metadata", msg.getL2NetworkUuid()); + return getL2NetworkOperationSyncSignature(msg.getL2NetworkUuid()); } @Override public void run(SyncTaskChain chain) { - NetworkConfigMutation mutation = NetworkConfigMutation.metadata( - msg.getL2NetworkUuid(), msg.getContext().getOrigin(), - msg.getContext().getOperationUuid(), - msg.getContext().getExternalRef().getAccountUuid(), + NetworkConfigChange change = NetworkConfigChange.updateL2Metadata( + msg.getL2NetworkUuid(), context.getOrigin(), + context.getOperationUuid(), + context.getExternalRef().getAccountUuid(), msg.getName(), msg.getDescription()); - mutateNetworkConfig(mutation, localCompletion -> { + coordinateNetworkConfigChange(change, localCompletion -> { String originalName = self.getName(); String originalDescription = self.getDescription(); try { @@ -187,7 +193,7 @@ public void fail(ErrorCode errorCode) { @Override public String getName() { - return getSyncSignature(); + return String.format("update-l2-network-%s-metadata", msg.getL2NetworkUuid()); } }); } @@ -523,7 +529,7 @@ private void handle(APIChangeL2NetworkVlanIdMsg msg){ thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { - return String.format("change-l2-network-%s-vlan", msg.getL2NetworkUuid()); + return getL2NetworkOperationSyncSignature(msg.getL2NetworkUuid()); } @Override @@ -531,11 +537,11 @@ public void run(SyncTaskChain chain) { L2NetworkInventory target = getSelfInventory(); target.setType(msg.getType()); target.setVirtualNetworkId(msg.getVlan() == null ? 0 : msg.getVlan()); - NetworkConfigMutation mutation = NetworkConfigMutation.encapsulation( + NetworkConfigChange change = NetworkConfigChange.changeL2Encapsulation( msg.getL2NetworkUuid(), NetworkOperationOrigin.API, msg.getId(), msg.getSession() == null ? null : msg.getSession().getAccountUuid(), target.getType(), target.getVirtualNetworkId()); - mutateNetworkConfig(mutation, localCompletion -> { + coordinateNetworkConfigChange(change, localCompletion -> { try { extpEmitter.beforeUpdate(target); } catch (OperationFailureException e) { @@ -577,29 +583,33 @@ public void fail(ErrorCode errorCode) { @Override public String getName() { - return getSyncSignature(); + return String.format("change-l2-network-%s-vlan", msg.getL2NetworkUuid()); } }); } - protected void mutateNetworkConfig(NetworkConfigMutation mutation, - NetworkConfigLocalContinuation continuation, + protected void coordinateNetworkConfigChange(NetworkConfigChange change, + LocalNetworkConfigChange localChange, Completion completion) { - List providers = pluginRgty - .getExtensionList(NetworkConfigMutationExtensionPoint.class).stream() - .filter(provider -> provider.supports(mutation)) + List coordinators = pluginRgty + .getExtensionList(NetworkConfigChangeCoordinator.class).stream() + .filter(coordinator -> coordinator.isApplicable(change)) .collect(Collectors.toList()); - if (providers.isEmpty()) { - continuation.run(completion); + if (coordinators.isEmpty()) { + localChange.apply(completion); return; } - if (providers.size() != 1) { + if (coordinators.size() != 1) { completion.fail(errf.stringToInternalError(String.format( "network mutation[%s] matched[%s] providers", - mutation.getKind(), providers.size()))); + change.getKind(), coordinators.size()))); return; } - providers.get(0).mutate(mutation, continuation, completion); + coordinators.get(0).coordinate(change, localChange, completion); + } + + protected String getL2NetworkOperationSyncSignature(String l2NetworkUuid) { + return String.format("l2-network-%s", l2NetworkUuid); } private void handle(APIUpdateL2NetworkMsg msg) { @@ -613,16 +623,16 @@ private void handle(APIUpdateL2NetworkMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { - return String.format("update-l2-network-%s-metadata", msg.getL2NetworkUuid()); + return getL2NetworkOperationSyncSignature(msg.getL2NetworkUuid()); } @Override public void run(SyncTaskChain chain) { - NetworkConfigMutation mutation = NetworkConfigMutation.metadata( + NetworkConfigChange change = NetworkConfigChange.updateL2Metadata( msg.getL2NetworkUuid(), NetworkOperationOrigin.API, msg.getId(), msg.getSession() == null ? null : msg.getSession().getAccountUuid(), msg.getName(), msg.getDescription()); - mutateNetworkConfig(mutation, localCompletion -> { + coordinateNetworkConfigChange(change, localCompletion -> { String originalName = self.getName(); String originalDescription = self.getDescription(); try { @@ -658,7 +668,7 @@ public void fail(ErrorCode errorCode) { @Override public String getName() { - return getSyncSignature(); + return String.format("update-l2-network-%s-metadata", msg.getL2NetworkUuid()); } }); } @@ -996,7 +1006,7 @@ private void handle(final AttachL2NetworkToClusterMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { - return String.format("attach-l2-network-%s-to-cluster-%s", msg.getL2NetworkUuid(), msg.getClusterUuid()); + return getL2NetworkOperationSyncSignature(msg.getL2NetworkUuid()); } @Override @@ -1021,7 +1031,8 @@ public void fail(ErrorCode errorCode) { @Override public String getName() { - return getSyncSignature(); + return String.format("attach-l2-network-%s-to-cluster-%s", + msg.getL2NetworkUuid(), msg.getClusterUuid()); } }); } @@ -1097,7 +1108,7 @@ public void handle(Map data) { }).error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { - evt.setError(err(ORG_ZSTACK_NETWORK_L2_10005, SysErrors.DELETE_RESOURCE_ERROR, errCode, errCode.getDetails())); + evt.setError(err(ORG_ZSTACK_NETWORK_L2_10032, SysErrors.DELETE_RESOURCE_ERROR, errCode, errCode.getDetails())); bus.publish(evt); } }).start(); 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 a94e657d056..490408c5c93 100755 --- a/network/src/main/java/org/zstack/network/l3/AbstractIpAllocatorStrategy.java +++ b/network/src/main/java/org/zstack/network/l3/AbstractIpAllocatorStrategy.java @@ -107,8 +107,7 @@ public IpRangeVO call(IpRangeVO arg) { )); } - return l3NwMgr.reserveIp(ipr, msg.getRequiredIp(), msg.isDuplicatedIpAllowed(), - msg.getOperationUuid(), msg.getOperationStep()); + return l3NwMgr.reserveIp(ipr, msg.getRequiredIp(), msg.isDuplicatedIpAllowed()); } protected UsedIpInventory allocateRequiredIpv6(IpAllocateMessage msg) { @@ -139,8 +138,7 @@ public IpRangeVO call(IpRangeVO arg) { )); } - return l3NwMgr.reserveIp(ipr, msg.getRequiredIp(), msg.isDuplicatedIpAllowed(), - msg.getOperationUuid(), msg.getOperationStep()); + return l3NwMgr.reserveIp(ipr, msg.getRequiredIp(), msg.isDuplicatedIpAllowed()); } public static int compareIpv4Range(IpRangeVO r1, IpRangeVO r2) { 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 71ca46140d3..47337b221e3 100755 --- a/network/src/main/java/org/zstack/network/l3/AscDelayRecycleIpAllocatorStrategy.java +++ b/network/src/main/java/org/zstack/network/l3/AscDelayRecycleIpAllocatorStrategy.java @@ -148,8 +148,7 @@ private UsedIpInventory allocateIpByAsc(IpAllocateMessage msg, List r ip = allocateIpByAsc(r, excludeIp); tr = r; if (ip != null) { - UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed(), - msg.getOperationUuid(), msg.getOperationStep()); + UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed()); if (inv != null) { return inv; } 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 7207a229b11..20a9cffc1fb 100755 --- a/network/src/main/java/org/zstack/network/l3/FirstAvailableIpAllocatorStrategy.java +++ b/network/src/main/java/org/zstack/network/l3/FirstAvailableIpAllocatorStrategy.java @@ -66,8 +66,7 @@ public UsedIpInventory allocateIp(IpAllocateMessage msg) { return null; } - UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed(), - msg.getOperationUuid(), msg.getOperationStep()); + UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed()); 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 71bd3b7c659..63723dc7073 100755 --- a/network/src/main/java/org/zstack/network/l3/FirstAvailableIpv6AllocatorStrategy.java +++ b/network/src/main/java/org/zstack/network/l3/FirstAvailableIpv6AllocatorStrategy.java @@ -69,8 +69,7 @@ public UsedIpInventory allocateIp(IpAllocateMessage msg) { return null; } - UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed(), - msg.getOperationUuid(), msg.getOperationStep()); + UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed()); if (inv != null) { return inv; } 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 50c1991685d..d07c2463488 100755 --- a/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java +++ b/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java @@ -1,6 +1,8 @@ package org.zstack.network.l3; import com.googlecode.ipv6.IPv6Address; +import org.apache.commons.net.util.SubnetUtils; +import org.apache.commons.net.util.SubnetUtils.SubnetInfo; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Configurable; @@ -42,9 +44,9 @@ 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.LocalNetworkConfigChange; +import org.zstack.header.network.NetworkConfigChange; +import org.zstack.header.network.NetworkConfigChangeCoordinator; import org.zstack.header.network.l2.NetworkOperationOrigin; import org.zstack.header.network.l3.*; import org.zstack.header.network.sdncontroller.SdnControllerConstant; @@ -299,23 +301,23 @@ private void addIpRanges(List iprs, APICreateMessage msg, .eq(IpRangeVO_.l3NetworkUuid, first.getL3NetworkUuid()) .eq(IpRangeVO_.ipVersion, first.getIpVersion()) .list(); - List ranges = existingRanges.stream() - .map(existing -> new NetworkConfigMutation.IpRangeTarget( + List ranges = existingRanges.stream() + .map(existing -> new NetworkConfigChange.IpRange( existing.getUuid(), existing.getStartIp(), existing.getEndIp())) .collect(Collectors.toList()); - iprs.forEach(ipr -> ranges.add(new NetworkConfigMutation.IpRangeTarget( + iprs.forEach(ipr -> ranges.add(new NetworkConfigChange.IpRange( ipr.getUuid(), ipr.getStartIp(), ipr.getEndIp()))); int prefix = first.getIpVersion() == IPv6Constants.IPv6 ? first.getPrefixLen() : NetworkUtils.getPrefixLengthFromNetmask(first.getNetmask()); - NetworkConfigMutation mutation = NetworkConfigMutation.ipam( + NetworkConfigChange change = NetworkConfigChange.replaceIpRangeConfiguration( 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, + if (coordinateNetworkConfigChange(change, localCompletion -> addIpRanges(iprs, msg, NetworkCreateContext.cloudCommit(msg.getId()), new ReturnValueCompletion>(localCompletion) { @@ -353,24 +355,24 @@ public void fail(ErrorCode errorCode) { }); } - private boolean mutateNetworkConfig(NetworkConfigMutation mutation, - NetworkConfigLocalContinuation continuation, + private boolean coordinateNetworkConfigChange(NetworkConfigChange change, + LocalNetworkConfigChange localChange, java.util.concurrent.atomic.AtomicReference result, ReturnValueCompletion completion) { - List providers = pluginRgty - .getExtensionList(NetworkConfigMutationExtensionPoint.class).stream() - .filter(provider -> provider.supports(mutation)) + List coordinators = pluginRgty + .getExtensionList(NetworkConfigChangeCoordinator.class).stream() + .filter(coordinator -> coordinator.isApplicable(change)) .collect(Collectors.toList()); - if (providers.isEmpty()) { + if (coordinators.isEmpty()) { return false; } - if (providers.size() != 1) { + if (coordinators.size() != 1) { completion.fail(errf.stringToInternalError(String.format( "network mutation[%s] matched[%s] providers", - mutation.getKind(), providers.size()))); + change.getKind(), coordinators.size()))); return true; } - providers.get(0).mutate(mutation, continuation, new Completion(completion) { + coordinators.get(0).coordinate(change, localChange, new Completion(completion) { @Override public void success() { completion.success(result.get()); @@ -574,14 +576,62 @@ public void run(SyncTaskChain chain) { } boolean valid; + int prefixLen = 0; + String networkCidr = null; try { - valid = NetworkUtils.isInRange(msg.getStartIp(), msg.getStartIp(), msg.getEndIp()); + if (range.getIpVersion() == IPv6Constants.IPv6) { + prefixLen = IPv6NetworkUtils.getPrefixLengthFromNetmask(msg.getNetmask()); + valid = IPv6NetworkUtils.isIpv6UnicastAddress(msg.getStartIp()) + && IPv6NetworkUtils.isIpv6UnicastAddress(msg.getEndIp()) + && IPv6NetworkUtils.isIpv6UnicastAddress(msg.getGateway()) + && IPv6NetworkUtils.isIpv6Address(msg.getNetmask()) + && prefixLen >= IPv6Constants.IPV6_PREFIX_LEN_MIN + && prefixLen <= IPv6Constants.IPV6_PREFIX_LEN_MAX + && IPv6NetworkUtils.isValidUnicastIpv6Range(msg.getStartIp(), + msg.getEndIp(), msg.getGateway(), prefixLen); + networkCidr = IPv6NetworkUtils.getNetworkCidrOfIpRange( + msg.getStartIp(), prefixLen); + } else { + prefixLen = NetworkUtils.getPrefixLengthFromNetmask(msg.getNetmask()); + valid = NetworkUtils.isIpv4Address(msg.getStartIp()) + && NetworkUtils.isIpv4Address(msg.getEndIp()) + && NetworkUtils.isIpv4Address(msg.getGateway()) + && NetworkUtils.isNetmaskExcept(msg.getNetmask(), "0.0.0.0"); + SubnetInfo subnet = new SubnetUtils(msg.getStartIp(), msg.getNetmask()).getInfo(); + valid = valid && subnet.isInRange(msg.getEndIp()) + && subnet.isInRange(msg.getGateway()) + && !msg.getStartIp().equals(subnet.getNetworkAddress()) + && !msg.getEndIp().equals(subnet.getBroadcastAddress()); + networkCidr = subnet.getCidrSignature(); + } + valid = 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())); + "projected IP range[start:%s, end:%s, gateway:%s, netmask:%s] is invalid", + msg.getStartIp(), msg.getEndIp(), msg.getGateway(), msg.getNetmask())); + chain.next(); + return; + } + + List candidates = Q.New(IpRangeVO.class) + .eq(IpRangeVO_.l3NetworkUuid, msg.getL3NetworkUuid()) + .eq(IpRangeVO_.ipVersion, range.getIpVersion()) + .notEq(IpRangeVO_.uuid, range.getUuid()).list(); + IpRangeVO overlap = candidates.stream() + .filter(candidate -> range.getIpVersion() == IPv6Constants.IPv6 + ? IPv6NetworkUtils.isIpv6RangeOverlap(msg.getStartIp(), msg.getEndIp(), + candidate.getStartIp(), candidate.getEndIp()) + : NetworkUtils.isIpv4RangeOverlap(msg.getStartIp(), msg.getEndIp(), + candidate.getStartIp(), candidate.getEndIp())) + .findFirst().orElse(null); + if (overlap != null) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L3_10090, + "projected IP range overlaps IP range[uuid:%s] on L3 network[uuid:%s]", + overlap.getUuid(), msg.getL3NetworkUuid())); chain.next(); return; } @@ -603,6 +653,8 @@ public void run(SyncTaskChain chain) { range.setEndIp(msg.getEndIp()); range.setGateway(msg.getGateway()); range.setNetmask(msg.getNetmask()); + range.setPrefixLen(prefixLen); + range.setNetworkCidr(networkCidr); dbf.update(range); bus.reply(msg, new MessageReply()); chain.next(); @@ -673,8 +725,12 @@ public void run(SyncTaskChain chain) { new SQLBatch() { @Override protected void scripts() { - replacements.forEach((usedIp, replacement) -> - usedIp.setIpRangeUuid(replacement.getUuid())); + replacements.forEach((usedIp, replacement) -> { + usedIp.setIpRangeUuid(replacement.getUuid()); + usedIp.setNetmask(replacement.getNetmask()); + usedIp.setGateway(replacement.getGateway()); + usedIp.setPrefixLen(replacement.getPrefixLen()); + }); if (!usedIps.isEmpty()) { dbf.updateCollection(usedIps); } @@ -1541,13 +1597,13 @@ public void fail(ErrorCode errorCode) { }; if (refs.stream().anyMatch(ref -> NetworkServiceType.DHCP.toString() .equals(ref.getNetworkServiceType()))) { - NetworkConfigMutation mutation = NetworkConfigMutation.dhcp( + NetworkConfigChange change = NetworkConfigChange.updateDhcpConfiguration( 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, + if (coordinateNetworkConfigChange(change, localCompletion -> detachNetworkServicesLocally(l3VO, refs, new ReturnValueCompletion(localCompletion) { @Override @@ -1824,14 +1880,14 @@ public void fail(ErrorCode errorCode) { .select(L3NetworkDnsVO_.dns) .eq(L3NetworkDnsVO_.l3NetworkUuid, msg.getL3NetworkUuid()) .notEq(L3NetworkDnsVO_.dns, msg.getDns()).listValues(); - NetworkConfigMutation mutation = NetworkConfigMutation.dhcpDns( + NetworkConfigChange change = NetworkConfigChange.updateDnsConfiguration( 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, + if (coordinateNetworkConfigChange(change, localCompletion -> removeDns(msg, true, new ReturnValueCompletion(localCompletion) { @Override @@ -1983,14 +2039,14 @@ public void fail(ErrorCode errorCode) { if (!desiredDns.contains(msg.getDns())) { desiredDns.add(msg.getDns()); } - NetworkConfigMutation mutation = NetworkConfigMutation.dhcpDns( + NetworkConfigChange change = NetworkConfigChange.updateDnsConfiguration( 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, + if (coordinateNetworkConfigChange(change, localCompletion -> addDns(msg, true, new ReturnValueCompletion(localCompletion) { @Override @@ -2310,13 +2366,13 @@ public void fail(ErrorCode errorCode) { } }; if (containsDhcp(msg.getNetworkServices())) { - NetworkConfigMutation mutation = NetworkConfigMutation.dhcp( + NetworkConfigChange change = NetworkConfigChange.updateDhcpConfiguration( 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, + if (coordinateNetworkConfigChange(change, localCompletion -> attachNetworkServices(msg, NetworkCreateContext.cloudCommit(msg.getId()), new ReturnValueCompletion(localCompletion) { @@ -2457,22 +2513,22 @@ private void deleteIpRangeWithMutation(APIDeleteIpRangeMsg msg, Completion compl .eq(IpRangeVO_.ipVersion, deleting.getIpVersion()) .notEq(IpRangeVO_.uuid, deleting.getUuid()) .list(); - NetworkConfigMutation mutation; + NetworkConfigChange change; if (remaining.isEmpty()) { - mutation = NetworkConfigMutation.deleteIpam( + change = NetworkConfigChange.removeIpRangeConfiguration( 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( + List targets = remaining.stream() + .map(range -> new NetworkConfigChange.IpRange( 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( + change = NetworkConfigChange.replaceIpRangeConfiguration( self.getL2NetworkUuid(), NetworkOperationOrigin.API, msg.getId(), msg.getSession() == null ? null : msg.getSession().getAccountUuid(), deleting.getL3NetworkUuid(), deleting.getIpVersion(), @@ -2480,7 +2536,7 @@ private void deleteIpRangeWithMutation(APIDeleteIpRangeMsg msg, Completion compl } java.util.concurrent.atomic.AtomicReference deleted = new java.util.concurrent.atomic.AtomicReference<>(false); - if (mutateNetworkConfig(mutation, + if (coordinateNetworkConfigChange(change, localCompletion -> doDeleteIpRange(msg, new Completion(localCompletion) { @Override public void success() { 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 9177bbadacb..7a7bec1d834 100755 --- a/network/src/main/java/org/zstack/network/l3/L3NetworkManager.java +++ b/network/src/main/java/org/zstack/network/l3/L3NetworkManager.java @@ -14,9 +14,6 @@ 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); List getUsedIpInRange(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 757a8ab5932..b8168d06d1b 100755 --- a/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java +++ b/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java @@ -21,6 +21,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.errorcode.SysErrors; import org.zstack.header.exception.CloudRuntimeException; import org.zstack.header.host.HostVO; @@ -33,9 +34,9 @@ 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.LocalNetworkConfigChange; +import org.zstack.header.network.NetworkConfigChange; +import org.zstack.header.network.NetworkConfigChangeCoordinator; import org.zstack.header.network.l3.*; import org.zstack.header.network.l3.datatypes.IpCapacityData; import org.zstack.header.network.service.GetSdnControllerExtensionPoint; @@ -600,13 +601,13 @@ private void handle(APICreateL3NetworkMsg msg, NetworkCreateContext context, if (msg.getResourceUuid() == null) { msg.setResourceUuid(Platform.getUuid()); } - NetworkConfigMutation mutation = NetworkConfigMutation.l3Create( + NetworkConfigChange change = NetworkConfigChange.createL3( 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, + if (coordinateNetworkConfigChange(change, localCompletion -> handle(msg, NetworkCreateContext.cloudCommit(msg.getId()), new ReturnValueCompletion(localCompletion) { @@ -733,24 +734,24 @@ public void handle(Map data) { } - private boolean mutateNetworkConfig(NetworkConfigMutation mutation, - NetworkConfigLocalContinuation continuation, + private boolean coordinateNetworkConfigChange(NetworkConfigChange change, + LocalNetworkConfigChange localChange, java.util.concurrent.atomic.AtomicReference result, ReturnValueCompletion completion) { - List providers = pluginRgty - .getExtensionList(NetworkConfigMutationExtensionPoint.class).stream() - .filter(provider -> provider.supports(mutation)) + List coordinators = pluginRgty + .getExtensionList(NetworkConfigChangeCoordinator.class).stream() + .filter(coordinator -> coordinator.isApplicable(change)) .collect(java.util.stream.Collectors.toList()); - if (providers.isEmpty()) { + if (coordinators.isEmpty()) { return false; } - if (providers.size() != 1) { + if (coordinators.size() != 1) { completion.fail(errf.stringToInternalError(String.format( "network mutation[%s] matched[%s] providers", - mutation.getKind(), providers.size()))); + change.getKind(), coordinators.size()))); return true; } - providers.get(0).mutate(mutation, continuation, new Completion(completion) { + coordinators.get(0).coordinate(change, localChange, new Completion(completion) { @Override public void success() { completion.success(result.get()); @@ -949,12 +950,6 @@ public UsedIpInventory reserveIp(IpRangeVO ipRange, String ip) { @Override public UsedIpInventory reserveIp(IpRangeVO ipRange, String ip, boolean allowDuplicatedAddress) { - 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() { @@ -962,7 +957,7 @@ protected UsedIpInventory scripts() { pluginRgty.getExtensionList(NetworkDeleteGuardExtensionPoint.class)) { ErrorCode errorCode = extension.checkL3NetworkWithLock(ipRange.getL3NetworkUuid()); if (errorCode != null) { - throw new CloudRuntimeException(errorCode.getDetails()); + throw new OperationFailureException(errorCode); } } if (NetworkUtils.isIpv4Address(ip)) { 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 d402a22a6d9..e4b8d1562f6 100755 --- a/network/src/main/java/org/zstack/network/l3/RandomIpAllocatorStrategy.java +++ b/network/src/main/java/org/zstack/network/l3/RandomIpAllocatorStrategy.java @@ -51,8 +51,7 @@ public UsedIpInventory allocateIp(IpAllocateMessage msg) { return null; } - UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed(), - msg.getOperationUuid(), msg.getOperationStep()); + UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed()); 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 2b3ead694d8..b9ea4737ec7 100755 --- a/network/src/main/java/org/zstack/network/l3/RandomIpv6AllocatorStrategy.java +++ b/network/src/main/java/org/zstack/network/l3/RandomIpv6AllocatorStrategy.java @@ -50,8 +50,7 @@ public UsedIpInventory allocateIp(IpAllocateMessage msg) { return null; } - UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed(), - msg.getOperationUuid(), msg.getOperationStep()); + UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed()); 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 e480fed38d9..725bf05c4f6 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,8 @@ import org.zstack.header.core.Completion; import org.zstack.header.network.sdncontroller.*; import org.zstack.sdnController.header.*; + +import java.util.List; import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_SDNCONTROLLER_10035; @@ -29,9 +31,10 @@ public interface SdnController { default void changeHost(SdnControllerHostRefVO oldRef, SdnControllerHostRefVO newRef, Completion completion) {completion.success();}; - default void pullResources(PullSdnControllerMsg msg, Completion completion) { + default void pullResources(String sdnControllerUuid, String resourceType, + List resourceUuids, Completion completion) { completion.fail(org.zstack.core.Platform.operr( ORG_ZSTACK_SDNCONTROLLER_10035, - "Resource pull is not supported by sdn controller[%s]", msg.getSdnControllerUuid())); + "Resource pull is not supported by sdn controller[%s]", sdnControllerUuid)); } } 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 a44a2b9d163..8f5c4f733dc 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerApiInterceptor.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerApiInterceptor.java @@ -273,7 +273,8 @@ private void validate(APIPullSdnControllerTenantMsg msg) { } private void validate(APIPullSdnControllerMsg msg) { - if (!Arrays.asList("Segment", "TenantRouter").contains(msg.getResourceType())) { + if (!SdnControllerConstant.ResourceTypes.Segment.name().equals(msg.getResourceType()) + && !SdnControllerConstant.ResourceTypes.TenantRouter.name().equals(msg.getResourceType())) { throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_SDNCONTROLLER_10036, "Unsupported resource type[%s]", msg.getResourceType())); } @@ -299,11 +300,12 @@ static List normalizeResourceUuids(List resourceUuids) { } LinkedHashSet normalized = new LinkedHashSet<>(); for (String resourceUuid : resourceUuids) { - if (!StringDSL.isUuid(resourceUuid) && !StringDSL.isZStackUuid(resourceUuid)) { + String normalizedUuid = resourceUuid == null ? null : resourceUuid.trim(); + if (!StringDSL.isUuid(normalizedUuid) && !StringDSL.isZStackUuid(normalizedUuid)) { throw new IllegalArgumentException(String.format( "Invalid resource uuid[%s]", resourceUuid)); } - normalized.add(resourceUuid); + normalized.add(normalizedUuid); } return new ArrayList<>(normalized); } 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 09a0b4c2b5f..321be4d6e42 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerBase.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerBase.java @@ -1105,8 +1105,8 @@ 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) { + pullResources(amsg.getSdnControllerUuid(), amsg.getResourceType(), amsg.getResourceUuids(), + new Completion(amsg) { @Override public void success() { bus.publish(event); } @Override public void fail(ErrorCode errorCode) { event.setError(errorCode); bus.publish(event); } }); @@ -1137,14 +1137,16 @@ public void fail(ErrorCode errorCode) { private void handle(PullSdnControllerMsg msg) { PullSdnControllerReply reply = new PullSdnControllerReply(); - pullResources(msg, new Completion(msg) { + pullResources(msg.getSdnControllerUuid(), msg.getResourceType(), msg.getResourceUuids(), + 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 pullResources(String sdnControllerUuid, String resourceType, + List resourceUuids, Completion completion) { + getSdnController().pullResources(sdnControllerUuid, resourceType, resourceUuids, completion); } private void pullSdnControllerTenant(PullSdnControllerTenantMsg msg, Completion completion) { 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 b09c8083f60..9e78dd38d62 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerL2.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerL2.java @@ -22,6 +22,12 @@ public interface SdnControllerL2 { void preCreateVxlanNetwork(L2VxlanNetworkInventory vxlan, List systemTags, Completion completion); void createL2Network(L2NetworkInventory inv, APICreateL2NetworkMsg msg, Completion completion); + + /** + * Creates an L2 network with its creation origin and external-resource identity. + * Implementations use the context to distinguish a normal Cloud request from a + * remote system that has already committed the network. + */ default void createL2Network(L2NetworkInventory inv, APICreateL2NetworkMsg msg, NetworkCreateContext context, Completion completion) { createL2Network(inv, msg, completion); } @@ -35,23 +41,50 @@ default void createL2Network(L2NetworkInventory inv, APICreateL2NetworkMsg msg, void deleteSdnController(SdnControllerDeletionMsg msg, SdnControllerInventory sdn, Completion completion); void detachL2NetworkFromCluster(L2VxlanNetworkInventory vxlan, List clusterUuids, Completion completion); void deleteL2Network(L2NetworkInventory inv, Completion completion); + + /** + * Whether this controller requires the confirmed-delete lifecycle before the + * Cloud removes a bound L2 network. + */ default boolean requiresConfirmedDelete() { return false; } + + /** + * Deletes an L2 network using a deletion-operation identity for idempotent + * controller-side handling. + */ default void deleteL2Network(L2NetworkInventory inv, String operationUuid, Completion completion) { deleteL2Network(inv, completion); } + + /** + * Deletes an L2 network with the complete confirmed-delete context, including + * the operation identity and the already-completed remote deletion state. + */ default void deleteL2Network(L2NetworkInventory inv, NetworkDeletionContext context, Completion completion) { deleteL2Network(inv, context == null ? null : context.getOperationUuid(), completion); } + + /** Reserves controller-side deletion work before Cloud cascade deletion begins. */ default void beginConfirmedDelete(L2NetworkInventory inv, NetworkDeletionContext context, Completion completion) { completion.success(); } + + /** Verifies that the bound external network can be deleted after cascade cleanup. */ default void checkConfirmedDelete(L2NetworkInventory inv, NetworkDeletionContext context, Completion completion) { completion.success(); } + + /** Finalizes controller-side deletion after Cloud cascade cleanup succeeds. */ default void completeConfirmedDelete(L2NetworkInventory inv, NetworkDeletionContext context, Completion completion) { completion.success(); } + + /** Cancels a confirmed-delete reservation when Cloud cascade cleanup fails. */ default void cancelConfirmedDelete(L2NetworkInventory inv, NetworkDeletionContext context, Completion completion) { completion.success(); } + + /** Removes controller-local metadata after a confirmed deletion completes. */ default void deleteConfirmedLocalMetadata(L2NetworkInventory inv) { } + + /** Removes controller-local metadata with the confirmed-delete context. */ default void deleteConfirmedLocalMetadata(L2NetworkInventory inv, NetworkDeletionContext context) { deleteConfirmedLocalMetadata(inv); } 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 index 603ec9913e7..93f051f78ea 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/header/PullSdnControllerMsg.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/PullSdnControllerMsg.java @@ -10,14 +10,6 @@ public class PullSdnControllerMsg extends NeedReplyMessage implements SdnControl 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; } diff --git a/plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetwork/VxlanNetwork.java b/plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetwork/VxlanNetwork.java index 0be8bb9aefa..086b8a2c6b5 100755 --- a/plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetwork/VxlanNetwork.java +++ b/plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetwork/VxlanNetwork.java @@ -404,7 +404,7 @@ protected void handle(final APIChangeL2NetworkVlanIdMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { - return String.format("change-l2-network-%s-vlan", msg.getL2NetworkUuid()); + return getL2NetworkOperationSyncSignature(msg.getL2NetworkUuid()); } @Override @@ -431,7 +431,7 @@ public void fail(ErrorCode errorCode) { @Override public String getName() { - return getSyncSignature(); + return String.format("change-l2-network-%s-vni", msg.getL2NetworkUuid()); } }); } 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 8068e40a63f..eb88541dc69 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 @@ -3,6 +3,7 @@ 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.MediaType import org.springframework.http.ResponseEntity import org.springframework.web.client.HttpStatusCodeException import org.zstack.core.rest.RESTFacadeImpl @@ -54,7 +55,7 @@ class RestFacadeCase extends SubCase { void testSyncHandlerPreservesStatusAndBody() { RESTFacadeImpl restf = bean(RESTFacadeImpl.class) String commandPath = "/test-rest-facade-status-body-${UUID.randomUUID()}" - String body = '{"condition":"receiver-overloaded"}' + String body = '{"condition":"接收端过载"}' restf.registerSyncHttpStatusBodyCallHandler(commandPath, Map.class) { Map ignored -> new SyncHttpResponse(429, body) } @@ -68,7 +69,10 @@ class RestFacadeCase extends SubCase { } catch (HttpStatusCodeException e) { assert e.rawStatusCode == 429 assert e.responseBodyAsString == body - assert e.responseHeaders.getFirst(HttpHeaders.CONTENT_TYPE).startsWith("application/json") + MediaType contentType = MediaType.parseMediaType( + e.responseHeaders.getFirst(HttpHeaders.CONTENT_TYPE)) + assert contentType.isCompatibleWith(MediaType.APPLICATION_JSON) + assert contentType.charset.name().equalsIgnoreCase("UTF-8") } } 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 index b9c6e0e62e6..0adbaeb5d7d 100644 --- 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 @@ -10,9 +10,9 @@ 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.LocalNetworkConfigChange +import org.zstack.header.network.NetworkConfigChange +import org.zstack.header.network.NetworkConfigChangeCoordinator import org.zstack.header.network.l2.L2DeleteConfirmExtensionPoint import org.zstack.header.network.l2.L2NetworkInventory import org.zstack.header.network.l2.L2NetworkUpdateExtensionPoint @@ -25,7 +25,6 @@ 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.L2NetworkConfirmedDeleteCoordinator import org.zstack.network.l2.L2NetworkExtensionPointEmitter import org.zstack.sdk.L2NetworkInventory as SdkL2NetworkInventory @@ -132,7 +131,6 @@ class L2NetworkCascadeCase extends SubCase { testRepeatedPrepareReusesOperationContext() testRepeatedPrepareFailureCancelsExistingFence() testDelayedBeginDoesNotBlockCoordinator() - testDeletionCheckHonorsCascadeFilter() testWholeL2ContextPropagatesToChildren() testParentForceChecksBeforeDeletingChildren() testParentForcePreservesProviderError() @@ -333,46 +331,6 @@ class L2NetworkCascadeCase extends SubCase { } } - 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 } - }) - } 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() @@ -537,9 +495,9 @@ class L2NetworkCascadeCase extends SubCase { void testL2MetadataMutationCommitsRemoteBeforeLocal() { SdkL2NetworkInventory l2 = env.inventoryByName("metadata-update-l2") - def extension = new RecordingMetadataMutation(dbf: dbf, l2Uuid: l2.uuid, failRemote: true) + def extension = new RecordingMetadataChange(dbf: dbf, l2Uuid: l2.uuid, failRemote: true) bean(PluginRegistry.class).defineDynamicExtension( - NetworkConfigMutationExtensionPoint.class, extension) + NetworkConfigChangeCoordinator.class, extension) try { expectError { @@ -563,9 +521,9 @@ class L2NetworkCascadeCase extends SubCase { 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 extension.changes*.kind == [ + NetworkConfigChange.Kind.L2_METADATA_CHANGE, + NetworkConfigChange.Kind.L2_METADATA_CHANGE] assert updated.name == "metadata-updated" assert updated.description == "metadata-description" } @@ -652,16 +610,6 @@ class L2NetworkCascadeCase extends SubCase { } } - 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) { @@ -694,28 +642,28 @@ class L2NetworkCascadeCase extends SubCase { } } - private static class RecordingMetadataMutation implements NetworkConfigMutationExtensionPoint { + private static class RecordingMetadataChange implements NetworkConfigChangeCoordinator { DatabaseFacade dbf String l2Uuid boolean failRemote String nameBeforeContinuation - List mutations = [] + List changes = [] @Override - boolean supports(NetworkConfigMutation mutation) { - return mutation.kind == NetworkConfigMutation.Kind.L2_METADATA && mutation.l2Uuid == l2Uuid + boolean isApplicable(NetworkConfigChange change) { + return change.kind == NetworkConfigChange.Kind.L2_METADATA_CHANGE && change.l2Uuid == l2Uuid } @Override - void mutate(NetworkConfigMutation mutation, NetworkConfigLocalContinuation continuation, + void coordinate(NetworkConfigChange change, LocalNetworkConfigChange localChange, Completion completion) { - mutations.add(mutation) + changes.add(change) nameBeforeContinuation = dbf.findByUuid(l2Uuid, L2NetworkVO.class).name if (failRemote) { completion.fail(new ErrorCode("TEST.REMOTE.FAILURE", "simulated remote failure")) return } - continuation.run(completion) + localChange.apply(completion) } } } diff --git a/test/src/test/java/org/zstack/sdnController/SdnControllerApiInterceptorTest.java b/test/src/test/java/org/zstack/sdnController/SdnControllerApiInterceptorTest.java new file mode 100644 index 00000000000..45ad38ef89c --- /dev/null +++ b/test/src/test/java/org/zstack/sdnController/SdnControllerApiInterceptorTest.java @@ -0,0 +1,19 @@ +package org.zstack.sdnController; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.Assert.assertEquals; + +public class SdnControllerApiInterceptorTest { + @Test + public void normalizeResourceUuidsTrimsBeforeValidationAndDeduplication() { + String uuid = "00000000000040008000000000000001"; + + assertEquals(Collections.singletonList(uuid), + SdnControllerApiInterceptor.normalizeResourceUuids( + Arrays.asList(" " + uuid + "\n", 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 fddbb43a10a..926d6c3062e 100644 --- a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java +++ b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java @@ -1074,6 +1074,9 @@ public class CloudOperationsErrorCode { 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_NETWORK_L2_10029 = "ORG_ZSTACK_NETWORK_L2_10029"; + public static final String ORG_ZSTACK_NETWORK_L2_10030 = "ORG_ZSTACK_NETWORK_L2_10030"; + public static final String ORG_ZSTACK_NETWORK_L2_10031 = "ORG_ZSTACK_NETWORK_L2_10031"; + public static final String ORG_ZSTACK_NETWORK_L2_10032 = "ORG_ZSTACK_NETWORK_L2_10032"; public static final String ORG_ZSTACK_CONSOLE_10000 = "ORG_ZSTACK_CONSOLE_10000"; @@ -12457,6 +12460,11 @@ public class CloudOperationsErrorCode { public static final String ORG_ZSTACK_NETWORK_ZNS_10207 = "ORG_ZSTACK_NETWORK_ZNS_10207"; public static final String ORG_ZSTACK_NETWORK_ZNS_10208 = "ORG_ZSTACK_NETWORK_ZNS_10208"; public static final String ORG_ZSTACK_NETWORK_ZNS_10209 = "ORG_ZSTACK_NETWORK_ZNS_10209"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10210 = "ORG_ZSTACK_NETWORK_ZNS_10210"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10211 = "ORG_ZSTACK_NETWORK_ZNS_10211"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10212 = "ORG_ZSTACK_NETWORK_ZNS_10212"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10213 = "ORG_ZSTACK_NETWORK_ZNS_10213"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10214 = "ORG_ZSTACK_NETWORK_ZNS_10214"; public static final String ORG_ZSTACK_PREMIUM_EXTERNALSERVICE_MARKETPLACE_10000 = "ORG_ZSTACK_PREMIUM_EXTERNALSERVICE_MARKETPLACE_10000"; From d2eb76ff79357e9ceee9840d1df76a2eedb2a12e Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Tue, 18 Aug 2026 23:52:05 +0800 Subject: [PATCH 3/8] [sdnController]: use non-throwing SDN factory lookup Resolves: ZCF-5485 Change-Id: Ia5b10e7dfe8e3126326c1f9450698809dc6d47f1 --- .../java/org/zstack/sdnController/SdnControllerManagerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b149f69744b..f4314efb268 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java @@ -493,7 +493,7 @@ private SdnControllerL2 findSdnControllerL2(L2NetworkInventory inv) { if (vSwitchType.getSdnControllerType() == null) { return null; } - SdnControllerFactory factory = getSdnControllerFactory(vSwitchType.getSdnControllerType()); + SdnControllerFactory factory = sdnControllerFactories.get(vSwitchType.getSdnControllerType()); return factory == null ? null : factory.getSdnControllerL2(inv.getUuid()); } From 3c55bea37b620aa0a504253f19c0cec773f5a4bb Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Wed, 19 Aug 2026 08:40:58 +0900 Subject: [PATCH 4/8] [zns]: retain deletion retry state Keep ZNS relation metadata when local L2 deletion fails. Resolves: ZCF-5485 Change-Id: I46df4d50e20dca1dc2fa643b6abfcc2e554953c3 --- .../java/org/zstack/network/l2/L2NetworkCascadeExtension.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d4b830ce975..0897f536749 100755 --- a/network/src/main/java/org/zstack/network/l2/L2NetworkCascadeExtension.java +++ b/network/src/main/java/org/zstack/network/l2/L2NetworkCascadeExtension.java @@ -182,6 +182,7 @@ public void run(List replies) { @Override public void success() { try { + dbf.removeByPrimaryKeys(uuids, L2NetworkVO.class); new SQLBatch() { @Override protected void scripts() { @@ -194,7 +195,6 @@ protected void scripts() { } } }.execute(); - dbf.removeByPrimaryKeys(uuids, L2NetworkVO.class); completion.success(); } catch (RuntimeException e) { completion.fail(inerr(ORG_ZSTACK_NETWORK_L2_10022, From 63c8430986b60c23ee8b84af48566ced3d20c9cc Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Wed, 19 Aug 2026 11:23:12 +0900 Subject: [PATCH 5/8] [core]: isolate zone delete preflight Move the ZNS-specific L2 confirmation behind a targeted extension.\nKeep core test contexts independent from network beans. Change-Id: I137ca36277f3c83a57de268bc9fc78bdc4d59354 --- .../org/zstack/compute/zone/ZoneBase.java | 7 +- .../zone/ZoneExtensionPointEmitter.java | 86 +++++++++++++++++++ conf/springConfigXml/NetworkManager.xml | 8 +- ...BeforeZoneCascadeDeleteExtensionPoint.java | 10 +++ .../L2NetworkZoneCascadeDeleteExtension.java | 22 +++++ .../core/async/AsyncCascadeCase.groovy | 2 +- 6 files changed, 128 insertions(+), 7 deletions(-) create mode 100644 core/src/main/java/org/zstack/core/cascade/BeforeZoneCascadeDeleteExtensionPoint.java create mode 100644 network/src/main/java/org/zstack/network/l2/L2NetworkZoneCascadeDeleteExtension.java 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 568a9f76416..c6c914598f8 100755 --- a/compute/src/main/java/org/zstack/compute/zone/ZoneBase.java +++ b/compute/src/main/java/org/zstack/compute/zone/ZoneBase.java @@ -25,7 +25,6 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.Message; import org.zstack.header.zone.*; -import org.zstack.network.l2.L2NetworkCascadeExtension; import org.zstack.utils.Utils; import org.zstack.utils.logging.CLogger; @@ -57,8 +56,6 @@ public class ZoneBase extends AbstractZone { protected ErrorFacade errf; @Autowired protected ThreadFacade thdf; - @Autowired - protected L2NetworkCascadeExtension l2NetworkCascadeExtension; ZoneBase(ZoneVO self) { this.self = self; @@ -192,7 +189,7 @@ public void run(FlowTrigger trigger, Map data) { cascadeAction.setActionCode(msg.getDeletionMode() == APIDeleteMessage.DeletionMode.Enforcing ? CascadeConstant.DELETION_FORCE_DELETE_CODE : CascadeConstant.DELETION_CHECK_CODE); - l2NetworkCascadeExtension.prepareConfirmedDelete(cascadeAction, new Completion(trigger) { + extpEmitter.prepareCascadeDelete(zinv, cascadeAction, new Completion(trigger) { @Override public void success() { trigger.next(); @@ -217,7 +214,7 @@ public void success() { @Override public void fail(ErrorCode errorCode) { - l2NetworkCascadeExtension.cancelConfirmedDelete(cascadeAction, + extpEmitter.cancelCascadeDelete(zinv, cascadeAction, new Completion(trigger) { @Override public void success() { diff --git a/compute/src/main/java/org/zstack/compute/zone/ZoneExtensionPointEmitter.java b/compute/src/main/java/org/zstack/compute/zone/ZoneExtensionPointEmitter.java index 532b3ac040b..b98cc30e208 100755 --- a/compute/src/main/java/org/zstack/compute/zone/ZoneExtensionPointEmitter.java +++ b/compute/src/main/java/org/zstack/compute/zone/ZoneExtensionPointEmitter.java @@ -1,15 +1,24 @@ package org.zstack.compute.zone; import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.core.asyncbatch.While; +import org.zstack.core.cascade.CascadeAction; +import org.zstack.core.cascade.BeforeZoneCascadeDeleteExtensionPoint; import org.zstack.core.componentloader.PluginExtension; import org.zstack.core.componentloader.PluginRegistry; +import org.zstack.core.errorcode.ErrorFacade; import org.zstack.header.Component; +import org.zstack.header.core.Completion; +import org.zstack.header.core.WhileDoneCompletion; +import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.errorcode.ErrorCodeList; import org.zstack.header.zone.*; import org.zstack.utils.CollectionUtils; import org.zstack.utils.Utils; import org.zstack.utils.function.ForEachFunction; import org.zstack.utils.logging.CLogger; +import java.util.ArrayList; import java.util.List; class ZoneExtensionPointEmitter implements Component { @@ -17,8 +26,11 @@ class ZoneExtensionPointEmitter implements Component { @Autowired private PluginRegistry pluginRgty; + @Autowired + private ErrorFacade errf; private List delExts; + private List cascadeDelExts; private List changeExts; void preDelete(ZoneInventory zinv) throws ZoneException { @@ -52,6 +64,79 @@ public void run(ZoneDeleteExtensionPoint arg) { } }); } + + void prepareCascadeDelete(ZoneInventory inventory, CascadeAction action, Completion completion) { + List prepared = new ArrayList<>(); + new While<>(cascadeDelExts).each((extension, whileCompletion) -> { + try { + extension.beforeDelete(inventory, action, new Completion(whileCompletion) { + @Override + public void success() { + prepared.add(extension); + whileCompletion.done(); + } + + @Override + public void fail(ErrorCode errorCode) { + whileCompletion.addError(errorCode); + whileCompletion.allDone(); + } + }); + } catch (RuntimeException e) { + whileCompletion.addError(errf.throwableToInternalError(e)); + whileCompletion.allDone(); + } + }).run(new WhileDoneCompletion(completion) { + @Override + public void done(ErrorCodeList errors) { + if (errors.getCauses().isEmpty()) { + completion.success(); + return; + } + cancelCascadeDelete(inventory, action, prepared, errors.getCauses().get(0), completion); + } + }); + } + + void cancelCascadeDelete(ZoneInventory inventory, CascadeAction action, Completion completion) { + cancelCascadeDelete(inventory, action, cascadeDelExts, null, completion); + } + + private void cancelCascadeDelete(ZoneInventory inventory, CascadeAction action, + List extensions, + ErrorCode originalError, + Completion completion) { + new While<>(extensions).each((extension, whileCompletion) -> { + try { + extension.cancel(inventory, action, new Completion(whileCompletion) { + @Override + public void success() { + whileCompletion.done(); + } + + @Override + public void fail(ErrorCode errorCode) { + whileCompletion.addError(errorCode); + whileCompletion.allDone(); + } + }); + } catch (RuntimeException e) { + whileCompletion.addError(errf.throwableToInternalError(e)); + whileCompletion.allDone(); + } + }).run(new WhileDoneCompletion(completion) { + @Override + public void done(ErrorCodeList errors) { + if (originalError != null) { + completion.fail(originalError); + } else if (errors.getCauses().isEmpty()) { + completion.success(); + } else { + completion.fail(errors.getCauses().get(0)); + } + } + }); + } void preChange(ZoneVO vo, ZoneStateEvent event) throws ZoneException { ZoneInventory zinv = ZoneInventory.valueOf(vo); @@ -99,6 +184,7 @@ public boolean start() { private void populateExtensions() { delExts = pluginRgty.getExtensionList(ZoneDeleteExtensionPoint.class); + cascadeDelExts = pluginRgty.getExtensionList(BeforeZoneCascadeDeleteExtensionPoint.class); changeExts = pluginRgty.getExtensionList(ZoneChangeStateExtensionPoint.class); } diff --git a/conf/springConfigXml/NetworkManager.xml b/conf/springConfigXml/NetworkManager.xml index b5ddd71ae45..008f64c4a04 100755 --- a/conf/springConfigXml/NetworkManager.xml +++ b/conf/springConfigXml/NetworkManager.xml @@ -40,7 +40,7 @@ - + @@ -136,6 +136,12 @@ + + + + + + diff --git a/core/src/main/java/org/zstack/core/cascade/BeforeZoneCascadeDeleteExtensionPoint.java b/core/src/main/java/org/zstack/core/cascade/BeforeZoneCascadeDeleteExtensionPoint.java new file mode 100644 index 00000000000..8d24a31e9d8 --- /dev/null +++ b/core/src/main/java/org/zstack/core/cascade/BeforeZoneCascadeDeleteExtensionPoint.java @@ -0,0 +1,10 @@ +package org.zstack.core.cascade; + +import org.zstack.header.core.Completion; +import org.zstack.header.zone.ZoneInventory; + +public interface BeforeZoneCascadeDeleteExtensionPoint { + void beforeDelete(ZoneInventory inventory, CascadeAction action, Completion completion); + + void cancel(ZoneInventory inventory, CascadeAction action, Completion completion); +} diff --git a/network/src/main/java/org/zstack/network/l2/L2NetworkZoneCascadeDeleteExtension.java b/network/src/main/java/org/zstack/network/l2/L2NetworkZoneCascadeDeleteExtension.java new file mode 100644 index 00000000000..5ba2ea1f74c --- /dev/null +++ b/network/src/main/java/org/zstack/network/l2/L2NetworkZoneCascadeDeleteExtension.java @@ -0,0 +1,22 @@ +package org.zstack.network.l2; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.core.cascade.CascadeAction; +import org.zstack.core.cascade.BeforeZoneCascadeDeleteExtensionPoint; +import org.zstack.header.core.Completion; +import org.zstack.header.zone.ZoneInventory; + +public class L2NetworkZoneCascadeDeleteExtension implements BeforeZoneCascadeDeleteExtensionPoint { + @Autowired + private L2NetworkCascadeExtension l2NetworkCascadeExtension; + + @Override + public void beforeDelete(ZoneInventory inventory, CascadeAction action, Completion completion) { + l2NetworkCascadeExtension.prepareConfirmedDelete(action, completion); + } + + @Override + public void cancel(ZoneInventory inventory, CascadeAction action, Completion completion) { + l2NetworkCascadeExtension.cancelConfirmedDelete(action, completion); + } +} diff --git a/test/src/test/groovy/org/zstack/test/integration/core/async/AsyncCascadeCase.groovy b/test/src/test/groovy/org/zstack/test/integration/core/async/AsyncCascadeCase.groovy index 260e32a3634..14c3b9f05ff 100644 --- a/test/src/test/groovy/org/zstack/test/integration/core/async/AsyncCascadeCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/core/async/AsyncCascadeCase.groovy @@ -52,7 +52,7 @@ class AsyncCascadeCase extends SubCase{ success5 = false clusterChildActionCount = 0 - casf = bean(CascadeFacade.class) + casf = new CascadeFacadeImpl() asyncCascade() } From 4c0f38d70eeaaf0d03a4bdd73cbec8de5e32c8da Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Wed, 19 Aug 2026 14:36:25 +0900 Subject: [PATCH 6/8] [network]: use internal L3 create requests Map API create requests at the service boundary. Change-Id: I851f12eab1ebb1f2d7d2de8aeb8391a612b4e9af --- .../header/network/l3/AddIpRangeMsg.java | 23 ++++ .../header/network/l3/AllocateIpMsg.java | 1 - .../header/network/l3/CreateL3NetworkMsg.java | 21 ++++ .../header/network/l3/IpRangeFactory.java | 5 +- .../header/network/l3/L3NetworkFactory.java | 4 +- .../network/l3/AddressPoolIpRangeFactory.java | 5 +- .../org/zstack/network/l3/L3BasicNetwork.java | 117 +++++++++--------- .../network/l3/L3BasicNetworkFactory.java | 2 +- .../network/l3/L3NetworkManagerImpl.java | 47 ++----- .../network/l3/NormalIpRangeFactory.java | 5 +- .../network/TfL3NetworkFactory.java | 2 +- 11 files changed, 125 insertions(+), 107 deletions(-) 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 index d1e29605a97..a8e620a3e83 100644 --- a/header/src/main/java/org/zstack/header/network/l3/AddIpRangeMsg.java +++ b/header/src/main/java/org/zstack/header/network/l3/AddIpRangeMsg.java @@ -1,6 +1,8 @@ package org.zstack.header.network.l3; import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.message.APICreateMessage; +import org.zstack.header.identity.SessionInventory; import org.zstack.header.network.l2.NetworkCreateContext; /** Internal typed IP range create request for ZNS projection and recovery. */ @@ -8,8 +10,27 @@ public class AddIpRangeMsg extends NeedReplyMessage implements L3NetworkMessage private String l3NetworkUuid; private String operationUuid; private IpRangeInventory inventory; + private SessionInventory session; private NetworkCreateContext context; + public static AddIpRangeMsg fromApi(APIAddIpRangeMsg api, + NetworkCreateContext context) { + return fromApi(api, IpRangeInventory.fromMessage(api), context); + } + + public static AddIpRangeMsg fromApi(APICreateMessage api, IpRangeInventory inventory, + NetworkCreateContext context) { + AddIpRangeMsg msg = new AddIpRangeMsg(); + msg.setL3NetworkUuid(inventory.getL3NetworkUuid()); + msg.setInventory(inventory); + msg.setSession(api.getSession()); + msg.setSystemTags(api.getSystemTags()); + msg.setUserTags(api.getUserTags()); + msg.setContext(context); + msg.setId(api.getId()); + return msg; + } + @Override public String getL3NetworkUuid() { return l3NetworkUuid; } public void setL3NetworkUuid(String value) { l3NetworkUuid = value; } @@ -17,6 +38,8 @@ public class AddIpRangeMsg extends NeedReplyMessage implements L3NetworkMessage public void setOperationUuid(String value) { operationUuid = value; } public IpRangeInventory getInventory() { return inventory; } public void setInventory(IpRangeInventory value) { inventory = 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/AllocateIpMsg.java b/header/src/main/java/org/zstack/header/network/l3/AllocateIpMsg.java index 750c4dcd304..78af601bea6 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 @@ -114,5 +114,4 @@ public String getIpv6Prefix() { public void setIpv6Prefix(String ipv6Prefix) { this.ipv6Prefix = ipv6Prefix; } - } 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 index 405f65f6b46..a23a11d1a7b 100644 --- a/header/src/main/java/org/zstack/header/network/l3/CreateL3NetworkMsg.java +++ b/header/src/main/java/org/zstack/header/network/l3/CreateL3NetworkMsg.java @@ -19,6 +19,27 @@ public class CreateL3NetworkMsg extends NeedReplyMessage { private SessionInventory session; private NetworkCreateContext context; + public static CreateL3NetworkMsg fromApi(APICreateL3NetworkMsg api, + NetworkCreateContext context) { + CreateL3NetworkMsg msg = new CreateL3NetworkMsg(); + msg.setName(api.getName()); + msg.setDescription(api.getDescription()); + msg.setType(api.getType()); + msg.setL2NetworkUuid(api.getL2NetworkUuid()); + msg.setCategory(api.getCategory()); + msg.setIpVersion(api.getIpVersion()); + msg.setSystem(api.isSystem()); + msg.setDnsDomain(api.getDnsDomain()); + msg.setEnableIPAM(api.getEnableIPAM()); + msg.setResourceUuid(api.getResourceUuid()); + msg.setSession(api.getSession()); + msg.setSystemTags(api.getSystemTags()); + msg.setUserTags(api.getUserTags()); + msg.setContext(context); + msg.setId(api.getId()); + return msg; + } + public String getName() { return name; } public void setName(String value) { name = value; } public String getDescription() { return description; } 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 b227d591926..e3cae8ec3b2 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 @@ -1,7 +1,6 @@ package org.zstack.header.network.l3; import org.zstack.header.core.ReturnValueCompletion; -import org.zstack.header.message.APICreateMessage; import org.zstack.header.network.l2.NetworkCreateContext; import java.util.List; @@ -9,9 +8,9 @@ public interface IpRangeFactory { IpRangeType getType(); - void createIpRange(List iprs, APICreateMessage msg, ReturnValueCompletion> completion); + void createIpRange(List iprs, AddIpRangeMsg msg, ReturnValueCompletion> completion); - default void createIpRange(List iprs, APICreateMessage msg, NetworkCreateContext context, + default void createIpRange(List iprs, AddIpRangeMsg msg, NetworkCreateContext context, ReturnValueCompletion> completion) { createIpRange(iprs, msg, completion); } 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 717f48bf810..9c8c35facef 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 @@ -5,9 +5,9 @@ public interface L3NetworkFactory { L3NetworkType getType(); - L3NetworkInventory createL3Network(L3NetworkVO l3vo, APICreateL3NetworkMsg msg); + L3NetworkInventory createL3Network(L3NetworkVO l3vo, CreateL3NetworkMsg msg); - default L3NetworkInventory createL3Network(L3NetworkVO l3vo, APICreateL3NetworkMsg msg, NetworkCreateContext context) { + default L3NetworkInventory createL3Network(L3NetworkVO l3vo, CreateL3NetworkMsg msg, NetworkCreateContext context) { return createL3Network(l3vo, msg); } 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 51bb42171f8..d7a4ec9e69c 100644 --- a/network/src/main/java/org/zstack/network/l3/AddressPoolIpRangeFactory.java +++ b/network/src/main/java/org/zstack/network/l3/AddressPoolIpRangeFactory.java @@ -6,7 +6,6 @@ import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.SQLBatchWithReturn; 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; @@ -30,12 +29,12 @@ public IpRangeType getType() { } @Override - public void createIpRange(List iprs, APICreateMessage msg, ReturnValueCompletion> completion) { + public void createIpRange(List iprs, AddIpRangeMsg msg, ReturnValueCompletion> completion) { createIpRange(iprs, msg, NetworkCreateContext.api(), completion); } @Override - public void createIpRange(List iprs, APICreateMessage msg, NetworkCreateContext context, + public void createIpRange(List iprs, AddIpRangeMsg msg, NetworkCreateContext context, ReturnValueCompletion> completion) { String resolvedAccountUuid = msg.getSession() == null ? null : msg.getSession().getAccountUuid(); if (resolvedAccountUuid == null && context != null && context.getExternalRef() != null) { 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 d07c2463488..ba6987019a6 100755 --- a/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java +++ b/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java @@ -171,80 +171,50 @@ private void setIpRangeSharedResource(String L3NetworkUuid, String IpRangeUuid) } private void handle(APIAddIpRangeMsg msg) { - thdf.chainSubmit(new ChainTask(msg) { - @Override - public void run(SyncTaskChain 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(); - } - }); - } - + if (msg.getResourceUuid() == null) { + msg.setResourceUuid(Platform.getUuid()); + } + AddIpRangeMsg add = AddIpRangeMsg.fromApi(msg, NetworkCreateContext.api()); + addIpRange(add, apiTagCreator(msg), new ReturnValueCompletion(msg) { @Override - public String getSyncSignature() { - return syncThreadName; + public void success(IpRangeInventory inv) { + APIAddIpRangeEvent evt = new APIAddIpRangeEvent(msg.getId()); + evt.setInventory(inv); + bus.publish(evt); } @Override - public String getName() { - return "add-ip-range"; + public void fail(ErrorCode errorCode) { + APIAddIpRangeEvent evt = new APIAddIpRangeEvent(msg.getId()); + evt.setError(errorCode); + bus.publish(evt); } }); } - private void handle(AddIpRangeMsg msg) { + private void addIpRange(AddIpRangeMsg msg, java.util.function.Consumer tagCreator, + ReturnValueCompletion completion) { 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, + completion.fail(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) { + addIpRange(ipr, msg, msg.getContext(), tagCreator, new ReturnValueCompletion(msg) { @Override public void success(IpRangeInventory inv) { - AddIpRangeReply reply = new AddIpRangeReply(); - reply.setInventory(inv); - bus.reply(msg, reply); + completion.success(inv); chain.next(); } @Override public void fail(ErrorCode errorCode) { - bus.replyErrorByMessageType(msg, errorCode); + completion.fail(errorCode); chain.next(); } }); @@ -257,14 +227,37 @@ public String getSyncSignature() { @Override public String getName() { - return "add-projected-ip-range"; + return "add-ip-range"; } }); } - private void addIpRange(IpRangeInventory ipr, APICreateMessage msg, NetworkCreateContext context, + private void handle(AddIpRangeMsg msg) { + addIpRange(msg, resourceUuid -> tagMgr.createTags(msg.getSystemTags(), msg.getUserTags(), resourceUuid, + L3NetworkVO.class.getSimpleName()), new ReturnValueCompletion(msg) { + @Override + public void success(IpRangeInventory inv) { + AddIpRangeReply reply = new AddIpRangeReply(); + reply.setInventory(inv); + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + bus.replyErrorByMessageType(msg, errorCode); + } + }); + } + + private java.util.function.Consumer apiTagCreator(APICreateMessage msg) { + return resourceUuid -> tagMgr.createTagsFromAPICreateMessage(msg, resourceUuid, + L3NetworkVO.class.getSimpleName()); + } + + private void addIpRange(IpRangeInventory ipr, AddIpRangeMsg msg, NetworkCreateContext context, + java.util.function.Consumer tagCreator, ReturnValueCompletion completion) { - addIpRanges(Collections.singletonList(ipr), msg, context, + addIpRanges(Collections.singletonList(ipr), msg, context, tagCreator, new ReturnValueCompletion>(completion) { @Override public void success(List inventories) { @@ -278,8 +271,8 @@ public void fail(ErrorCode errorCode) { }); } - private void addIpRanges(List iprs, APICreateMessage msg, - NetworkCreateContext context, + private void addIpRanges(List iprs, AddIpRangeMsg msg, NetworkCreateContext context, + java.util.function.Consumer tagCreator, ReturnValueCompletion> completion) { if (context == null) { completion.fail(Platform.inerr(ORG_ZSTACK_NETWORK_L3_10084, @@ -320,6 +313,7 @@ private void addIpRanges(List iprs, APICreateMessage msg, if (coordinateNetworkConfigChange(change, localCompletion -> addIpRanges(iprs, msg, NetworkCreateContext.cloudCommit(msg.getId()), + tagCreator, new ReturnValueCompletion>(localCompletion) { @Override public void success(List inventories) { @@ -341,8 +335,7 @@ public void fail(ErrorCode errorCode) { @Override public void success(List invs) { for (IpRangeInventory inv : invs) { - tagMgr.createTagsFromAPICreateMessage( - msg, inv.getL3NetworkUuid(), L3NetworkVO.class.getSimpleName()); + tagCreator.accept(inv.getL3NetworkUuid()); setIpRangeSharedResource(inv.getL3NetworkUuid(), inv.getUuid()); } completion.success(invs); @@ -1770,8 +1763,10 @@ private void handle(APIAddIpRangeByNetworkCidrMsg msg) { List iprs = IpRangeInventory.fromMessage(msg); assignStableIpRangeUuids(iprs, msg.getResourceUuid()); APIAddIpRangeByNetworkCidrEvent evt = new APIAddIpRangeByNetworkCidrEvent(msg.getId()); + AddIpRangeMsg add = AddIpRangeMsg.fromApi(msg, iprs.get(0), NetworkCreateContext.api()); - addIpRanges(iprs, msg, NetworkCreateContext.api(), new ReturnValueCompletion>(msg) { + addIpRanges(iprs, add, NetworkCreateContext.api(), apiTagCreator(msg), + new ReturnValueCompletion>(msg) { @Override public void success(List invs) { IpRangeInventory inv = invs.get(0); @@ -1794,8 +1789,10 @@ private void handle(APIAddIpv6RangeByNetworkCidrMsg msg) { ipr.setUuid(Platform.getUuid()); } APIAddIpRangeByNetworkCidrEvent evt = new APIAddIpRangeByNetworkCidrEvent(msg.getId()); + AddIpRangeMsg add = AddIpRangeMsg.fromApi(msg, ipr, NetworkCreateContext.api()); - addIpRanges(Collections.singletonList(ipr), msg, NetworkCreateContext.api(), new ReturnValueCompletion>(msg) { + addIpRanges(Collections.singletonList(ipr), add, NetworkCreateContext.api(), apiTagCreator(msg), + new ReturnValueCompletion>(msg) { @Override public void success(List invs) { IpRangeInventory inv = invs.get(0); @@ -1817,8 +1814,10 @@ private void handle(APIAddIpv6RangeMsg msg) { ipr.setUuid(Platform.getUuid()); } APIAddIpRangeEvent evt = new APIAddIpRangeEvent(msg.getId()); + AddIpRangeMsg add = AddIpRangeMsg.fromApi(msg, ipr, NetworkCreateContext.api()); - addIpRanges(Collections.singletonList(ipr), msg, NetworkCreateContext.api(), new ReturnValueCompletion>(msg) { + addIpRanges(Collections.singletonList(ipr), add, NetworkCreateContext.api(), apiTagCreator(msg), + new ReturnValueCompletion>(msg) { @Override public void success(List invs) { IpRangeInventory inv = invs.get(0); diff --git a/network/src/main/java/org/zstack/network/l3/L3BasicNetworkFactory.java b/network/src/main/java/org/zstack/network/l3/L3BasicNetworkFactory.java index 620f2fff436..c1a4e94961f 100755 --- a/network/src/main/java/org/zstack/network/l3/L3BasicNetworkFactory.java +++ b/network/src/main/java/org/zstack/network/l3/L3BasicNetworkFactory.java @@ -18,7 +18,7 @@ public L3NetworkType getType() { @Override @Transactional - public L3NetworkInventory createL3Network(L3NetworkVO l3vo, APICreateL3NetworkMsg msg) { + public L3NetworkInventory createL3Network(L3NetworkVO l3vo, CreateL3NetworkMsg msg) { l3vo.setType(type.toString()); dbf.getEntityManager().persist(l3vo); dbf.getEntityManager().flush(); 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 b8168d06d1b..9ad7b48b179 100755 --- a/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java +++ b/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java @@ -131,21 +131,9 @@ private void handleLocalMessage(Message 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) { + createL3Network(msg, msg.getContext() == null ? NetworkCreateContext.api() : msg.getContext(), + resourceUuid -> tagMgr.createTags(msg.getSystemTags(), msg.getUserTags(), resourceUuid, + L3NetworkVO.class.getSimpleName()), new ReturnValueCompletion(msg) { @Override public void success(L3NetworkInventory inventory) { CreateL3NetworkReply reply = new CreateL3NetworkReply(); @@ -563,7 +551,10 @@ private void syncManagementServiceTypeWhileCreate(ServiceTypeExtensionPoint ext, } private void handle(APICreateL3NetworkMsg msg) { - handle(msg, NetworkCreateContext.api(), new ReturnValueCompletion(msg) { + CreateL3NetworkMsg create = CreateL3NetworkMsg.fromApi(msg, NetworkCreateContext.api()); + createL3Network(create, NetworkCreateContext.api(), resourceUuid -> + tagMgr.createTagsFromAPICreateMessage(msg, resourceUuid, L3NetworkVO.class.getSimpleName()), + new ReturnValueCompletion(msg) { @Override public void success(L3NetworkInventory inventory) { APICreateL3NetworkEvent evt = new APICreateL3NetworkEvent(msg.getId()); @@ -580,22 +571,9 @@ public void fail(ErrorCode errorCode) { }); } - 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) { + private void createL3Network(CreateL3NetworkMsg msg, NetworkCreateContext context, + java.util.function.Consumer tagCreator, + ReturnValueCompletion completion) { if (context.getOrigin() == NetworkOperationOrigin.API) { if (msg.getResourceUuid() == null) { @@ -608,8 +586,9 @@ private void handle(APICreateL3NetworkMsg msg, NetworkCreateContext context, java.util.concurrent.atomic.AtomicReference created = new java.util.concurrent.atomic.AtomicReference<>(); if (coordinateNetworkConfigChange(change, - localCompletion -> handle(msg, + localCompletion -> createL3Network(msg, NetworkCreateContext.cloudCommit(msg.getId()), + tagCreator, new ReturnValueCompletion(localCompletion) { @Override public void success(L3NetworkInventory inventory) { @@ -697,7 +676,7 @@ protected L3NetworkInventory scripts() { } vo.setAccountUuid(accountUuid); L3NetworkInventory inv = factory.createL3Network(vo, msg, context); - tagMgr.createTagsFromAPICreateMessage(msg, vo.getUuid(), L3NetworkVO.class.getSimpleName()); + tagCreator.accept(vo.getUuid()); return inv; } }.execute(); 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 35c20060aa8..2b69a8267d4 100644 --- a/network/src/main/java/org/zstack/network/l3/NormalIpRangeFactory.java +++ b/network/src/main/java/org/zstack/network/l3/NormalIpRangeFactory.java @@ -13,7 +13,6 @@ import org.zstack.header.core.ReturnValueCompletion; import org.zstack.header.core.workflow.*; import org.zstack.header.errorcode.ErrorCode; -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; @@ -50,12 +49,12 @@ public IpRangeType getType() { } @Override - public void createIpRange(List iprs, APICreateMessage msg, ReturnValueCompletion> completion) { + public void createIpRange(List iprs, AddIpRangeMsg msg, ReturnValueCompletion> completion) { createIpRange(iprs, msg, NetworkCreateContext.api(), completion); } @Override - public void createIpRange(List iprs, APICreateMessage msg, NetworkCreateContext context, + public void createIpRange(List iprs, AddIpRangeMsg msg, NetworkCreateContext context, ReturnValueCompletion> completion) { String resolvedAccountUuid = msg.getSession() == null ? null : msg.getSession().getAccountUuid(); if (resolvedAccountUuid == null && context != null && context.getExternalRef() != null) { diff --git a/plugin/sugonSdnController/src/main/java/org/zstack/sugonSdnController/network/TfL3NetworkFactory.java b/plugin/sugonSdnController/src/main/java/org/zstack/sugonSdnController/network/TfL3NetworkFactory.java index be71fded04e..845a4ac4b16 100644 --- a/plugin/sugonSdnController/src/main/java/org/zstack/sugonSdnController/network/TfL3NetworkFactory.java +++ b/plugin/sugonSdnController/src/main/java/org/zstack/sugonSdnController/network/TfL3NetworkFactory.java @@ -26,7 +26,7 @@ public L3NetworkType getType() { } @Override - public L3NetworkInventory createL3Network(L3NetworkVO l3vo, APICreateL3NetworkMsg msg) { + public L3NetworkInventory createL3Network(L3NetworkVO l3vo, CreateL3NetworkMsg msg) { l3vo.setType(type.toString()); dbf.getEntityManager().persist(l3vo); dbf.getEntityManager().flush(); From 4c06c81ea4045959621d09e6f7e677ec36b4f716 Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Wed, 19 Aug 2026 15:09:24 +0900 Subject: [PATCH 7/8] [network]: allocate a dedicated L3 error code Keep the new internal IP range validation distinct from prior errors. Resolves: ZCF-5485 Change-Id: Iea12951febd67542ce37f1479ff971c044ef9cbc --- network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java | 2 +- .../zstack/utils/clouderrorcode/CloudOperationsErrorCode.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) 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 ba6987019a6..d4df1246fa2 100755 --- a/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java +++ b/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java @@ -199,7 +199,7 @@ private void addIpRange(AddIpRangeMsg msg, java.util.function.Consumer t public void run(SyncTaskChain chain) { IpRangeInventory ipr = msg.getInventory(); if (ipr == null || !Objects.equals(msg.getL3NetworkUuid(), ipr.getL3NetworkUuid())) { - completion.fail(argerr(ORG_ZSTACK_NETWORK_L3_10083, + completion.fail(argerr(ORG_ZSTACK_NETWORK_L3_10101, "internal IP range must belong to L3 network[uuid:%s]", msg.getL3NetworkUuid())); chain.next(); return; 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 926d6c3062e..9fbbb804a36 100644 --- a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java +++ b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java @@ -1016,6 +1016,8 @@ public class CloudOperationsErrorCode { 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_NETWORK_L3_10101 = "ORG_ZSTACK_NETWORK_L3_10101"; + public static final String ORG_ZSTACK_SNS_PLATFORM_UNIVERSALSMS_SUPPLIER_EMAY_10000 = "ORG_ZSTACK_SNS_PLATFORM_UNIVERSALSMS_SUPPLIER_EMAY_10000"; public static final String ORG_ZSTACK_CORE_VALIDATION_10000 = "ORG_ZSTACK_CORE_VALIDATION_10000"; From 96cc6507d294c88676eee9db606c45ed162fb792 Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Wed, 19 Aug 2026 16:20:06 +0900 Subject: [PATCH 8/8] [zns]: remove ZNS schema triggers Keep ZNS invariants in service code. Resolves: ZCF-5485 Change-Id: I068061bc455bc38b37cabb50d6b724450bd0bb42 --- conf/db/upgrade/V5.5.38__schema.sql | 54 ----------------------------- 1 file changed, 54 deletions(-) diff --git a/conf/db/upgrade/V5.5.38__schema.sql b/conf/db/upgrade/V5.5.38__schema.sql index 778c753a14b..f56c68348ac 100644 --- a/conf/db/upgrade/V5.5.38__schema.sql +++ b/conf/db/upgrade/V5.5.38__schema.sql @@ -129,57 +129,3 @@ CREATE TABLE IF NOT EXISTS `zstack`.`ZnsSegmentRefVO` ( 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_controller_capability_validate_insert`; -DELIMITER $$ -CREATE TRIGGER `zstack`.`trg_zns_controller_capability_validate_insert` -BEFORE INSERT ON `zstack`.`ZnsControllerCapabilityVO` -FOR EACH ROW -BEGIN - IF NEW.`observedState` NOT IN ('NOT_ACTIVATED', 'MIGRATING_READ_ONLY', 'ACTIVE', 'INCOMPATIBLE_READ_ONLY') - OR NEW.`effectiveState` NOT IN ('NOT_ACTIVATED', 'MIGRATING_READ_ONLY', 'ACTIVE', 'INCOMPATIBLE_READ_ONLY') THEN - SIGNAL SQLSTATE '45000' - SET MESSAGE_TEXT = 'ZnsControllerCapabilityVO has an invalid capability state'; - END IF; -END$$ -DELIMITER ; - -DROP TRIGGER IF EXISTS `zstack`.`trg_zns_controller_capability_validate_update`; -DELIMITER $$ -CREATE TRIGGER `zstack`.`trg_zns_controller_capability_validate_update` -BEFORE UPDATE ON `zstack`.`ZnsControllerCapabilityVO` -FOR EACH ROW -BEGIN - IF NEW.`observedState` NOT IN ('NOT_ACTIVATED', 'MIGRATING_READ_ONLY', 'ACTIVE', 'INCOMPATIBLE_READ_ONLY') - OR NEW.`effectiveState` NOT IN ('NOT_ACTIVATED', 'MIGRATING_READ_ONLY', 'ACTIVE', 'INCOMPATIBLE_READ_ONLY') THEN - SIGNAL SQLSTATE '45000' - SET MESSAGE_TEXT = 'ZnsControllerCapabilityVO has an invalid capability state'; - END IF; -END$$ -DELIMITER ; - -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 ;