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..c6c914598f8 100755 --- a/compute/src/main/java/org/zstack/compute/zone/ZoneBase.java +++ b/compute/src/main/java/org/zstack/compute/zone/ZoneBase.java @@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Configurable; import org.zstack.core.cascade.CascadeConstant; +import org.zstack.core.cascade.CascadeAction; import org.zstack.core.cascade.CascadeFacade; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.db.DatabaseFacade; @@ -178,13 +179,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); + extpEmitter.prepareCascadeDelete(zinv, 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 +214,25 @@ public void success() { @Override public void fail(ErrorCode errorCode) { - trigger.fail(errorCode); + extpEmitter.cancelCascadeDelete(zinv, 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 +249,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/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/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..008f64c4a04 100755 --- a/conf/springConfigXml/NetworkManager.xml +++ b/conf/springConfigXml/NetworkManager.xml @@ -40,7 +40,7 @@ - + @@ -130,11 +130,20 @@ - - - - - + + + + + + + + + + + + + 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/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/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..fa04921d861 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.setContentType(RESTConstant.APP_JSON_UTF8); + 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/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/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/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..a8e620a3e83 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l3/AddIpRangeMsg.java @@ -0,0 +1,45 @@ +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. */ +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; } + 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 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/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/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..a23a11d1a7b --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l3/CreateL3NetworkMsg.java @@ -0,0 +1,67 @@ +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 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; } + 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/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..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,12 +1,17 @@ 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; 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, AddIpRangeMsg 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..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 @@ -1,9 +1,15 @@ package org.zstack.header.network.l3; +import org.zstack.header.network.l2.NetworkCreateContext; + public interface L3NetworkFactory { L3NetworkType getType(); - L3NetworkInventory createL3Network(L3NetworkVO l3vo, APICreateL3NetworkMsg msg); + L3NetworkInventory createL3Network(L3NetworkVO l3vo, CreateL3NetworkMsg msg); + + default L3NetworkInventory createL3Network(L3NetworkVO l3vo, CreateL3NetworkMsg msg, NetworkCreateContext context) { + return createL3Network(l3vo, msg); + } L3Network getL3Network(L3NetworkVO vo); 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/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/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..befd26a69a2 100755 --- a/identity/src/main/java/org/zstack/identity/AccountBase.java +++ b/identity/src/main/java/org/zstack/identity/AccountBase.java @@ -6,7 +6,9 @@ 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; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.EventFacade; @@ -22,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; @@ -48,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; @@ -162,7 +168,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 Deque prepared = new ArrayDeque<>(); List resourceUuids = Q.New(AccountResourceRefVO.class) .select(AccountResourceRefVO_.resourceUuid) .eq(AccountResourceRefVO_.ownerAccountUuid, self.getUuid()) @@ -172,12 +186,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, 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 +250,87 @@ public void handle(ErrorCode errCode, Map data) { }).start(); } + private void prepareAccountDelete(AccountInventory account, CascadeAction action, + List extensions, + Deque prepared, + Completion 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) { + 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; + } + failAccountDeletePreparation(account, action, prepared, + errors.getCauses().get(0), completion); + } + }); + } + + private void failAccountDeletePreparation( + AccountInventory account, CascadeAction action, + Deque prepared, + ErrorCode errorCode, Completion completion) { + cancelAccountDelete(account, action, prepared, + 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, + 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) { + 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) { 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..0897f536749 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,22 +127,26 @@ 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 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()); + 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) { @@ -152,12 +162,51 @@ public void run(List replies) { List uuids = new ArrayList(); for (MessageReply r : replies) { L2NetworkInventory inv = finalL2invs.get(replies.indexOf(r)); + if (!r.isSuccess()) { + if (finalConfirmedInventories.contains(inv)) { + 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(); + if (finalConfirmedInventories.isEmpty()) { + dbf.removeByPrimaryKeys(uuids, L2NetworkVO.class); + completion.success(); + return; + } + + confirmedDeleteCoordinator.complete(action, finalConfirmedInventories, new Completion(completion) { + @Override + public void success() { + try { + dbf.removeByPrimaryKeys(uuids, L2NetworkVO.class); + new SQLBatch() { + @Override + protected void scripts() { + for (L2NetworkInventory inv : finalConfirmedInventories) { + for (L2DeleteConfirmExtensionPoint ext : + confirmedDeleteCoordinator.confirmedExtensions(inv)) { + ext.deleteLocalMetadata(inv, + NetworkDeletionContexts.get(action, inv.getUuid())); + } + } + } + }.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); + } + }); } }); } @@ -173,11 +222,58 @@ private void handleDeletionCheck(CascadeAction action, Completion completion) { for (L2NetworkInventory prinv : l2invs) { extpEmitter.preDelete(prinv); } + } catch (L2NetworkException e) { + completion.fail(inerr(ORG_ZSTACK_NETWORK_L2_10031, e.getMessage())); + return; + } + List confirmedInventories = confirmedInventories(l2invs); + if (confirmedInventories.isEmpty() + || confirmedDeleteCoordinator.isPrepared(action, confirmedInventories)) { + completion.success(); + } else { + confirmedDeleteCoordinator.prepare(action, confirmedInventories, completion); + } + } + public void prepareConfirmedDelete(CascadeAction action, Completion completion) { + List l2invs = l2NetworkFromAction(action); + if (l2invs == null) { completion.success(); - } catch (L2NetworkException e) { - completion.fail(inerr(ORG_ZSTACK_NETWORK_L2_10000, e.getMessage())); + return; + } + confirmedDeleteCoordinator.prepare(action, confirmedInventories(l2invs), completion); + } + + public void cancelConfirmedDelete(CascadeAction action, Completion completion) { + List l2invs = l2NetworkFromAction(action); + if (l2invs == null) { + completion.success(); + return; + } + confirmedDeleteCoordinator.cancel(action, confirmedInventories(l2invs), 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; + } + + private List confirmedInventories(List inventories) { + List result = new ArrayList<>(); + for (L2NetworkInventory inventory : inventories) { + if (!confirmedDeleteCoordinator.confirmedExtensions(inventory).isEmpty()) { + result.add(inventory); + } } + 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..d76d908ef00 --- /dev/null +++ b/network/src/main/java/org/zstack/network/l2/L2NetworkConfirmedDeleteCoordinator.java @@ -0,0 +1,318 @@ +package org.zstack.network.l2; + +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; +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) { + 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, + 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; + } + cancelPrepared(action, prepared, 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, 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 prepareInventory(CascadeAction action, L2NetworkInventory inventory, + List begun, WhileCompletion completion) { + List extensions; + try { + extensions = confirmedExtensions(inventory); + } catch (RuntimeException e) { + completion.addError(toError(e)); + completion.allDone(); + return; + } + if (extensions.isEmpty()) { + completion.done(); + 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(inventory, extensions, finalContext, completion); + return; + } + begin(action, extensions, begun, inventory, finalContext, completion); + } + + private void begin(CascadeAction action, List extensions, + List begun, L2NetworkInventory inventory, + 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 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); + } + }); + } + + 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 done(ErrorCodeList errors) { + if (errors.getCauses().isEmpty()) { + completion.done(); + } else { + completion.addError(errors.getCauses().get(0)); + completion.allDone(); + } + } + }); + } + + private void failPrepared(CascadeAction action, List begun, ErrorCode error, + Completion completion) { + cancelPrepared(action, begun, new Completion(completion) { + @Override + public void success() { + completion.fail(error); + } + + @Override + public void fail(ErrorCode cancelError) { + completion.fail(error); + } + }); + } + + 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 done(ErrorCodeList errors) { + completion.success(); + } + }); + } + + 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)); + } + } + }); + } + + 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..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,8 +1,14 @@ 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; import org.zstack.header.network.l2.L2NetworkInventory; @@ -14,6 +20,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 +76,44 @@ public void beforeUpdate(final L2NetworkInventory inv) { } } + public void beforeUpdate(final L2NetworkInventory inv, Completion 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) { + 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)); + } + } + }); + } + 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/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/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..9a1bfd6f3c1 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.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; @@ -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,76 @@ 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(); + 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); + return; + } + + thdf.chainSubmit(new ChainTask(msg) { + @Override + public String getSyncSignature() { + return getL2NetworkOperationSyncSignature(msg.getL2NetworkUuid()); + } + + @Override + public void run(SyncTaskChain chain) { + NetworkConfigChange change = NetworkConfigChange.updateL2Metadata( + msg.getL2NetworkUuid(), context.getOrigin(), + context.getOperationUuid(), + context.getExternalRef().getAccountUuid(), + msg.getName(), msg.getDescription()); + coordinateNetworkConfigChange(change, 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 String.format("update-l2-network-%s-metadata", msg.getL2NetworkUuid()); + } + }); + } + private void handle(L2NetworkDetachFromClusterMsg msg) { L2NetworkDetachFromClusterReply reply = new L2NetworkDetachFromClusterReply(); @@ -156,13 +227,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 +244,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 +268,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 +426,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()) { @@ -438,17 +529,44 @@ 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 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()); + NetworkConfigChange change = NetworkConfigChange.changeL2Encapsulation( + msg.getL2NetworkUuid(), NetworkOperationOrigin.API, msg.getId(), + msg.getSession() == null ? null : msg.getSession().getAccountUuid(), + target.getType(), target.getVirtualNetworkId()); + coordinateNetworkConfigChange(change, 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(); @@ -465,28 +583,94 @@ public void fail(ErrorCode errorCode) { @Override public String getName() { - return getSyncSignature(); + return String.format("change-l2-network-%s-vlan", msg.getL2NetworkUuid()); } }); } - 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 coordinateNetworkConfigChange(NetworkConfigChange change, + LocalNetworkConfigChange localChange, + Completion completion) { + List coordinators = pluginRgty + .getExtensionList(NetworkConfigChangeCoordinator.class).stream() + .filter(coordinator -> coordinator.isApplicable(change)) + .collect(Collectors.toList()); + if (coordinators.isEmpty()) { + localChange.apply(completion); + return; } - if (update) { - self = dbf.updateAndRefresh(self); + if (coordinators.size() != 1) { + completion.fail(errf.stringToInternalError(String.format( + "network mutation[%s] matched[%s] providers", + change.getKind(), coordinators.size()))); + return; } + coordinators.get(0).coordinate(change, localChange, completion); + } + + protected String getL2NetworkOperationSyncSignature(String l2NetworkUuid) { + return String.format("l2-network-%s", l2NetworkUuid); + } + 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 getL2NetworkOperationSyncSignature(msg.getL2NetworkUuid()); + } + + @Override + public void run(SyncTaskChain chain) { + NetworkConfigChange change = NetworkConfigChange.updateL2Metadata( + msg.getL2NetworkUuid(), NetworkOperationOrigin.API, msg.getId(), + msg.getSession() == null ? null : msg.getSession().getAccountUuid(), + msg.getName(), msg.getDescription()); + coordinateNetworkConfigChange(change, 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 String.format("update-l2-network-%s-metadata", msg.getL2NetworkUuid()); + } + }); } private void handle(final APIDetachL2NetworkFromClusterMsg msg) { @@ -822,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 @@ -847,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()); } }); } @@ -856,13 +1041,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 +1059,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 +1083,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(); @@ -919,12 +1108,50 @@ 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(); } + 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 +1237,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 +1266,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/AddressPoolIpRangeFactory.java b/network/src/main/java/org/zstack/network/l3/AddressPoolIpRangeFactory.java index aae2711248b..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,14 +6,17 @@ 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; 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; @@ -26,7 +29,23 @@ 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, AddIpRangeMsg 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 +61,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 +79,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/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/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..d4df1246fa2 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; @@ -30,6 +32,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 +43,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.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; 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 +69,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.*; @@ -160,10 +171,53 @@ private void setIpRangeSharedResource(String L3NetworkUuid, String IpRangeUuid) } private void handle(APIAddIpRangeMsg msg) { + if (msg.getResourceUuid() == null) { + msg.setResourceUuid(Platform.getUuid()); + } + AddIpRangeMsg add = AddIpRangeMsg.fromApi(msg, NetworkCreateContext.api()); + addIpRange(add, apiTagCreator(msg), new ReturnValueCompletion(msg) { + @Override + public void success(IpRangeInventory inv) { + APIAddIpRangeEvent evt = new APIAddIpRangeEvent(msg.getId()); + evt.setInventory(inv); + bus.publish(evt); + } + + @Override + public void fail(ErrorCode errorCode) { + APIAddIpRangeEvent evt = new APIAddIpRangeEvent(msg.getId()); + evt.setError(errorCode); + bus.publish(evt); + } + }); + } + + private void addIpRange(AddIpRangeMsg msg, java.util.function.Consumer tagCreator, + ReturnValueCompletion completion) { thdf.chainSubmit(new ChainTask(msg) { @Override public void run(SyncTaskChain chain) { - doAddIpRange(msg, chain); + IpRangeInventory ipr = msg.getInventory(); + if (ipr == null || !Objects.equals(msg.getL3NetworkUuid(), ipr.getL3NetworkUuid())) { + completion.fail(argerr(ORG_ZSTACK_NETWORK_L3_10101, + "internal IP range must belong to L3 network[uuid:%s]", msg.getL3NetworkUuid())); + chain.next(); + return; + } + + addIpRange(ipr, msg, msg.getContext(), tagCreator, new ReturnValueCompletion(msg) { + @Override + public void success(IpRangeInventory inv) { + completion.success(inv); + chain.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + chain.next(); + } + }); } @Override @@ -178,36 +232,151 @@ 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) { + 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, tagCreator, + 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, AddIpRangeMsg msg, NetworkCreateContext context, + java.util.function.Consumer tagCreator, + 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 NetworkConfigChange.IpRange( + existing.getUuid(), existing.getStartIp(), existing.getEndIp())) + .collect(Collectors.toList()); + 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()); + 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 (coordinateNetworkConfigChange(change, + localCompletion -> addIpRanges(iprs, msg, + NetworkCreateContext.cloudCommit(msg.getId()), + tagCreator, + 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) { + tagCreator.accept(inv.getL3NetworkUuid()); + 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 coordinateNetworkConfigChange(NetworkConfigChange change, + LocalNetworkConfigChange localChange, + java.util.concurrent.atomic.AtomicReference result, + ReturnValueCompletion completion) { + List coordinators = pluginRgty + .getExtensionList(NetworkConfigChangeCoordinator.class).stream() + .filter(coordinator -> coordinator.isApplicable(change)) + .collect(Collectors.toList()); + if (coordinators.isEmpty()) { + return false; + } + if (coordinators.size() != 1) { + completion.fail(errf.stringToInternalError(String.format( + "network mutation[%s] matched[%s] providers", + change.getKind(), coordinators.size()))); + return true; + } + coordinators.get(0).coordinate(change, localChange, 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) { @@ -234,7 +403,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 +430,323 @@ 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; + int prefixLen = 0; + String networkCidr = null; + try { + 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[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; + } + + 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()); + range.setPrefixLen(prefixLen); + range.setNetworkCidr(networkCidr); + 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()); + usedIp.setNetmask(replacement.getNetmask()); + usedIp.setGateway(replacement.getGateway()); + usedIp.setPrefixLen(replacement.getPrefixLen()); + }); + 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 +792,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 +815,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 +845,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 +870,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 +882,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 +937,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 +946,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 +985,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 +1575,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()))) { + 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 (coordinateNetworkConfigChange(change, + 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 +1761,15 @@ 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()); + AddIpRangeMsg add = AddIpRangeMsg.fromApi(msg, iprs.get(0), NetworkCreateContext.api()); - IpRangeFactory factory = l3NwMgr.getIpRangeFactory(iprs.get(0).getIpRangeType()); - factory.createIpRange(iprs, msg, new ReturnValueCompletion>(msg) { + addIpRanges(iprs, add, NetworkCreateContext.api(), apiTagCreator(msg), + 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 +1785,17 @@ 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()); + AddIpRangeMsg add = AddIpRangeMsg.fromApi(msg, ipr, NetworkCreateContext.api()); - IpRangeFactory factory = l3NwMgr.getIpRangeFactory(ipr.getIpRangeType()); - factory.createIpRange(Collections.singletonList(ipr), msg, 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); - tagMgr.createTagsFromAPICreateMessage(msg, inv.getL3NetworkUuid(), L3NetworkVO.class.getSimpleName()); - setIpRangeSharedResource(msg.getL3NetworkUuid(), inv.getUuid()); evt.setInventory(inv); bus.publish(evt); } @@ -1271,15 +1810,17 @@ 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()); + AddIpRangeMsg add = AddIpRangeMsg.fromApi(msg, ipr, NetworkCreateContext.api()); - IpRangeFactory factory = l3NwMgr.getIpRangeFactory(ipr.getIpRangeType()); - factory.createIpRange(Collections.singletonList(ipr), msg, 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); - tagMgr.createTagsFromAPICreateMessage(msg, inv.getL3NetworkUuid(), L3NetworkVO.class.getSimpleName()); - setIpRangeSharedResource(msg.getL3NetworkUuid(), inv.getUuid()); evt.setInventory(inv); bus.publish(evt); } @@ -1292,6 +1833,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 +1862,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(); + 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 (coordinateNetworkConfigChange(change, + 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 +1936,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 +2003,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 +2019,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()); + } + 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 (coordinateNetworkConfigChange(change, + 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 +2103,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 +2171,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 +2207,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 +2236,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 +2280,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 +2351,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())) { + 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 (coordinateNetworkConfigChange(change, + 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 +2501,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(); + NetworkConfigChange change; + if (remaining.isEmpty()) { + 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 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()); + change = NetworkConfigChange.replaceIpRangeConfiguration( + 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 (coordinateNetworkConfigChange(change, + 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 +2593,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/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/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..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,7 +14,6 @@ public interface L3NetworkManager { UsedIpInventory reserveIp(IpRangeVO ipRange, String ip); UsedIpInventory reserveIp(IpRangeVO ipRange, String ip, boolean allowDuplicatedAddress); - 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 45fecdc0aa1..9ad7b48b179 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; @@ -32,6 +33,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.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; @@ -116,13 +121,33 @@ 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) { + 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(); + 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 +551,59 @@ private void syncManagementServiceTypeWhileCreate(ServiceTypeExtensionPoint ext, } private void handle(APICreateL3NetworkMsg msg) { - APICreateL3NetworkEvent evt = new APICreateL3NetworkEvent(msg.getId()); + 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()); + 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 createL3Network(CreateL3NetworkMsg msg, NetworkCreateContext context, + java.util.function.Consumer tagCreator, + ReturnValueCompletion completion) { + + if (context.getOrigin() == NetworkOperationOrigin.API) { + if (msg.getResourceUuid() == null) { + msg.setResourceUuid(Platform.getUuid()); + } + 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 (coordinateNetworkConfigChange(change, + localCompletion -> createL3Network(msg, + NetworkCreateContext.cloudCommit(msg.getId()), + tagCreator, + 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 +646,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,9 +668,15 @@ 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); - tagMgr.createTagsFromAPICreateMessage(msg, vo.getUuid(), L3NetworkVO.class.getSimpleName()); + 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); + tagCreator.accept(vo.getUuid()); return inv; } }.execute(); @@ -612,26 +695,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 coordinateNetworkConfigChange(NetworkConfigChange change, + LocalNetworkConfigChange localChange, + java.util.concurrent.atomic.AtomicReference result, + ReturnValueCompletion completion) { + List coordinators = pluginRgty + .getExtensionList(NetworkConfigChangeCoordinator.class).stream() + .filter(coordinator -> coordinator.isApplicable(change)) + .collect(java.util.stream.Collectors.toList()); + if (coordinators.isEmpty()) { + return false; + } + if (coordinators.size() != 1) { + completion.fail(errf.stringToInternalError(String.format( + "network mutation[%s] matched[%s] providers", + change.getKind(), coordinators.size()))); + return true; + } + coordinators.get(0).coordinate(change, localChange, 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 +929,24 @@ 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 new SQLBatchWithReturn() { + @Override + protected UsedIpInventory scripts() { + for (NetworkDeleteGuardExtensionPoint extension : + pluginRgty.getExtensionList(NetworkDeleteGuardExtensionPoint.class)) { + ErrorCode errorCode = extension.checkL3NetworkWithLock(ipRange.getL3NetworkUuid()); + if (errorCode != null) { + throw new OperationFailureException(errorCode); + } + } + 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..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,9 +13,9 @@ 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; import org.zstack.header.network.sdncontroller.SdnControllerConstant; import org.zstack.header.network.service.SdnControllerDhcp; import org.zstack.utils.CollectionUtils; @@ -29,6 +29,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; @@ -46,7 +49,23 @@ 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, AddIpRangeMsg 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 +78,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 +116,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 +138,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 +169,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/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnController.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnController.java index 57072cbaa57..725bf05c4f6 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnController.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnController.java @@ -4,6 +4,9 @@ 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; + public interface SdnController { @@ -27,4 +30,11 @@ 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(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]", 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 97840150a1e..8f5c4f733dc 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,44 @@ private void validate(APIPullSdnControllerTenantMsg msg) { } } + private void validate(APIPullSdnControllerMsg msg) { + 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())); + } + 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) { + 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(normalizedUuid); + } + 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..321be4d6e42 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()); + 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); } + }); + } + private void handle(PullSdnControllerTenantMsg msg) { PullSdnControllerTenantReply reply = new PullSdnControllerTenantReply(); @@ -1086,6 +1135,20 @@ public void fail(ErrorCode errorCode) { }); } + private void handle(PullSdnControllerMsg msg) { + PullSdnControllerReply reply = new PullSdnControllerReply(); + 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(String sdnControllerUuid, String resourceType, + List resourceUuids, Completion completion) { + getSdnController().pullResources(sdnControllerUuid, resourceType, resourceUuids, 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..9e78dd38d62 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,15 @@ 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); + } void postCreateVxlanNetwork(L2VxlanNetworkInventory vxlan, List systemTags, Completion completion); void preAttachL2NetworkToCluster(L2VxlanNetworkInventory vxlan, List systemTags, Completion completion); @@ -31,6 +42,53 @@ public interface SdnControllerL2 { 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); + } + 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..f4314efb268 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 = sdnControllerFactories.get(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..93f051f78ea --- /dev/null +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/PullSdnControllerMsg.java @@ -0,0 +1,19 @@ +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; + + @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/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(); 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/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..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 @@ -23,6 +23,7 @@ class AsyncCascadeCase extends SubCase{ boolean success3 boolean success4 boolean success5 + int clusterChildActionCount @Override void setup() { @@ -49,8 +50,9 @@ class AsyncCascadeCase extends SubCase{ success3 = false success4 = false success5 = false + clusterChildActionCount = 0 - casf = bean(CascadeFacade.class) + casf = new CascadeFacadeImpl() 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..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 @@ -1,11 +1,18 @@ 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 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 +47,51 @@ 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":"接收端过载"}' + 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 + MediaType contentType = MediaType.parseMediaType( + e.responseHeaders.getFirst(HttpHeaders.CONTENT_TYPE)) + assert contentType.isCompatibleWith(MediaType.APPLICATION_JSON) + assert contentType.charset.name().equalsIgnoreCase("UTF-8") + } + } + + 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..0adbaeb5d7d --- /dev/null +++ b/test/src/test/groovy/org/zstack/test/integration/network/l2network/L2NetworkCascadeCase.groovy @@ -0,0 +1,669 @@ +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.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 +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.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() + 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 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 RecordingMetadataChange(dbf: dbf, l2Uuid: l2.uuid, failRemote: true) + bean(PluginRegistry.class).defineDynamicExtension( + NetworkConfigChangeCoordinator.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.changes*.kind == [ + NetworkConfigChange.Kind.L2_METADATA_CHANGE, + NetworkConfigChange.Kind.L2_METADATA_CHANGE] + 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 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 RecordingMetadataChange implements NetworkConfigChangeCoordinator { + DatabaseFacade dbf + String l2Uuid + boolean failRemote + String nameBeforeContinuation + List changes = [] + + @Override + boolean isApplicable(NetworkConfigChange change) { + return change.kind == NetworkConfigChange.Kind.L2_METADATA_CHANGE && change.l2Uuid == l2Uuid + } + + @Override + void coordinate(NetworkConfigChange change, LocalNetworkConfigChange localChange, + Completion completion) { + changes.add(change) + nameBeforeContinuation = dbf.findByUuid(l2Uuid, L2NetworkVO.class).name + if (failRemote) { + completion.fail(new ErrorCode("TEST.REMOTE.FAILURE", "simulated remote failure")) + return + } + 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/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..9fbbb804a36 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,28 @@ 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_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"; @@ -1044,6 +1066,20 @@ 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_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"; public static final String ORG_ZSTACK_CONSOLE_10001 = "ORG_ZSTACK_CONSOLE_10001"; @@ -1618,6 +1654,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 +12278,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 +12293,180 @@ 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_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"; @@ -15741,6 +15954,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";