diff --git a/compute/src/main/java/org/zstack/compute/vm/VmAllocateNicFlow.java b/compute/src/main/java/org/zstack/compute/vm/VmAllocateNicFlow.java index 0e307a30b5c..3aa81091713 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmAllocateNicFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmAllocateNicFlow.java @@ -10,6 +10,7 @@ import org.zstack.core.componentloader.PluginRegistry; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.SQLBatch; +import org.zstack.core.db.SQLBatchWithReturn; import org.zstack.core.errorcode.ErrorFacade; import org.zstack.header.core.Completion; import org.zstack.header.core.WhileDoneCompletion; @@ -18,9 +19,11 @@ import org.zstack.header.core.workflow.FlowTrigger; import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.errorcode.ErrorCodeList; +import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.image.ImagePlatform; import org.zstack.header.message.APIMessage; import org.zstack.header.network.l3.*; +import org.zstack.header.network.NetworkDeleteGuardExtensionPoint; import org.zstack.header.vm.*; import org.zstack.network.l3.L3NetworkManager; import org.zstack.resourceconfig.ResourceConfig; @@ -94,7 +97,6 @@ public void run(final FlowTrigger trigger, final Map data) { data.put(VmInstanceConstant.Params.VmAllocateNicFlow_nics.toString(), nics); List errs = new ArrayList<>(); List vmSystemTags = spec.getMessage() instanceof APIMessage ? ((APIMessage) spec.getMessage()).getSystemTags() : null; - new While<>(VmNicSpec.getFirstL3NetworkInventoryOfSpec(spec.getL3Networks())).each((nicSpec, wcomp) -> { L3NetworkInventory nw = nicSpec.getL3Invs().get(0); int deviceId = deviceIdBitmap.nextClearBit(0); @@ -123,20 +125,47 @@ public void run(final FlowTrigger trigger, final Map data) { // Persist VmNicVO first so that ResourceVO entry exists before extensions // (e.g. SDN controllers) attempt to create SystemTags referencing the NIC UUID. - VmNicVO nicVO = vnicFactory.createVmNic(nic, spec); + VmNicVO nicVO; + try { + nicVO = new SQLBatchWithReturn() { + @Override + protected VmNicVO scripts() { + ErrorCode guardError = checkNetworkDeleteGuards(nw.getUuid(), true); + if (guardError != null) { + throw new OperationFailureException(guardError); + } + return vnicFactory.createVmNic(nic, spec); + } + }.execute(); + } catch (OperationFailureException e) { + errs.add(e.getErrorCode()); + wcomp.allDone(); + return; + } callBeforeAllocateVmNicExtensions(nic, spec, new Completion(wcomp) { @Override public void success() { - new SQLBatch() { - @Override - protected void scripts() { - persistStaticIpIfNeeded(nic, nicVO, nw, nicNetworkInfoMap, spec); - nics.add(nic); - VmNicVO updated = dbf.updateAndRefresh(nicVO); - addVmNicConfig(updated, spec, nicSpec); - } - }.execute(); + try { + new SQLBatch() { + @Override + protected void scripts() { + ErrorCode guardError = checkNetworkDeleteGuards(nw.getUuid(), true); + if (guardError != null) { + throw new OperationFailureException(guardError); + } + persistStaticIpIfNeeded(nic, nicVO, nw, nicNetworkInfoMap, spec); + VmNicVO updated = dbf.updateAndRefresh(nicVO); + addVmNicConfig(updated, spec, nicSpec); + } + }.execute(); + } catch (OperationFailureException e) { + dbf.removeByPrimaryKey(nicVO.getUuid(), VmNicVO.class); + errs.add(e.getErrorCode()); + wcomp.allDone(); + return; + } + nics.add(nic); if (customMac != null) { mo.deleteCustomMacSystemTag(spec.getVmInventory().getUuid(), nw.getUuid(), customMac); } @@ -263,6 +292,13 @@ private void addVmNicConfig(VmNicVO vmNicVO, VmInstanceSpec vmSpec, VmNicSpec ni VmNicParam vmNicParm = vmNicParms.get(0); + if (vmNicParm.getInboundBandwidth() != null || vmNicParm.getOutboundBandwidth() != null) { + ErrorCode qosError = validateVmNicQos(vmNicVO.getL3NetworkUuid()); + if (qosError != null) { + throw new OperationFailureException(qosError); + } + } + // add vmnic bandwidth systemtag if (vmNicParm.getInboundBandwidth() != null || vmNicParm.getOutboundBandwidth() != null) { VmNicQosConfigBackend backend = vmMgr.getVmNicQosConfigBackend(vmSpec.getVmInventory().getType()); @@ -277,6 +313,29 @@ private void addVmNicConfig(VmNicVO vmNicVO, VmInstanceSpec vmSpec, VmNicSpec ni } } + private ErrorCode checkNetworkDeleteGuards(String l3NetworkUuid, boolean lock) { + for (NetworkDeleteGuardExtensionPoint extension : + pluginRgty.getExtensionList(NetworkDeleteGuardExtensionPoint.class)) { + ErrorCode errorCode = lock ? extension.checkL3NetworkWithLock(l3NetworkUuid) + : extension.checkL3Network(l3NetworkUuid); + if (errorCode != null) { + return errorCode; + } + } + return null; + } + + private ErrorCode validateVmNicQos(String l3NetworkUuid) { + for (VmNicQosConfigExtensionPoint extension : + pluginRgty.getExtensionList(VmNicQosConfigExtensionPoint.class)) { + ErrorCode errorCode = extension.validateVmNicQos(l3NetworkUuid); + if (errorCode != null) { + return errorCode; + } + } + return null; + } + private void callBeforeAllocateVmNicExtensions(VmNicInventory nic, VmInstanceSpec spec, Completion completion) { List exts = pluginRgty.getExtensionList(BeforeAllocateVmNicExtensionPoint.class); if (exts.isEmpty()) { diff --git a/compute/src/main/java/org/zstack/compute/zone/ZoneBase.java b/compute/src/main/java/org/zstack/compute/zone/ZoneBase.java index ba1f889a46f..fa483aced4e 100755 --- a/compute/src/main/java/org/zstack/compute/zone/ZoneBase.java +++ b/compute/src/main/java/org/zstack/compute/zone/ZoneBase.java @@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Configurable; import org.zstack.core.cascade.CascadeConstant; +import org.zstack.core.cascade.CascadeAction; import org.zstack.core.cascade.CascadeFacade; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.db.DatabaseFacade; @@ -178,13 +179,15 @@ protected void handle(APIDeleteZoneMsg msg) { final String issuer = ZoneVO.class.getSimpleName(); ZoneInventory zinv = ZoneInventory.valueOf(self); final List ctx = Arrays.asList(zinv); + final CascadeAction cascadeAction = new CascadeAction().setRootIssuer(issuer) + .setRootIssuerContext(ctx).setParentIssuer(issuer).setParentIssuerContext(ctx); FlowChain chain = FlowChainBuilder.newSimpleFlowChain(); chain.setName(String.format("delete-zone-%s", msg.getUuid())); if (msg.getDeletionMode() == APIDeleteMessage.DeletionMode.Permissive) { chain.then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_CHECK_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_CHECK_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -199,7 +202,7 @@ public void fail(ErrorCode errorCode) { }).then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_DELETE_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_DELETE_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -216,7 +219,7 @@ public void fail(ErrorCode errorCode) { chain.then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_FORCE_DELETE_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); diff --git a/conf/db/upgrade/V5.5.38__schema.sql b/conf/db/upgrade/V5.5.38__schema.sql index 1b8937fe5e9..c250cdf96b2 100644 --- a/conf/db/upgrade/V5.5.38__schema.sql +++ b/conf/db/upgrade/V5.5.38__schema.sql @@ -20,3 +20,104 @@ CREATE TABLE IF NOT EXISTS `zstack`.`SnmpEngineVO` ( `lastOpDate` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`uuid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- ZNS segment binding state and controller inventory + +CREATE TABLE IF NOT EXISTS `zstack`.`ZnsControllerStateVO` ( + `sdnControllerUuid` varchar(32) NOT NULL, + `activeScanToken` varchar(255) DEFAULT NULL, + `activeScanStartedAt` timestamp NULL DEFAULT NULL, + `migrationState` varchar(32) NOT NULL DEFAULT 'NotStarted', + `compatibilityMode` varchar(32) NOT NULL DEFAULT 'LEGACY', + `compatibilityEpoch` bigint NOT NULL DEFAULT 0, + `remoteCompatibilityEpoch` bigint DEFAULT NULL, + `capabilityDigest` varchar(64) DEFAULT NULL, + `migrationDigest` varchar(64) DEFAULT NULL, + `pendingContractEpoch` bigint DEFAULT NULL, + `pendingContractRequestDigest` varchar(64) DEFAULT NULL, + `lastSuccessfulScanDate` timestamp NULL DEFAULT NULL, + `lastErrorCode` varchar(128) DEFAULT NULL, + `lastErrorDetails` text DEFAULT NULL, + `lastOpDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00' ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`sdnControllerUuid`), + CONSTRAINT `fkZnsControllerStateVOSdnControllerVO` + FOREIGN KEY (`sdnControllerUuid`) REFERENCES `zstack`.`SdnControllerVO` (`uuid`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `zstack`.`ZnsSegmentInventoryVO` ( + `uuid` varchar(32) NOT NULL, + `sdnControllerUuid` varchar(32) NOT NULL, + `znsSegmentUuid` varchar(36) NOT NULL, + `name` varchar(255) NOT NULL, + `transportZoneUuid` varchar(36) NOT NULL, + `availability` varchar(32) NOT NULL, + `reasonCode` varchar(128) DEFAULT NULL, + `lastSeenToken` varchar(36) NOT NULL, + `lastSeenDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', + `createDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', + `lastOpDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00' ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`uuid`), + UNIQUE KEY `uk_zns_seg_inv_controller_segment` (`sdnControllerUuid`, `znsSegmentUuid`), + KEY `idx_zns_seg_inv_candidate` (`sdnControllerUuid`, `transportZoneUuid`, `availability`), + CONSTRAINT `fkZnsSegmentInventoryVOSdnControllerVO` + FOREIGN KEY (`sdnControllerUuid`) REFERENCES `zstack`.`SdnControllerVO` (`uuid`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `zstack`.`ZnsSegmentRefVO` ( + `uuid` varchar(32) NOT NULL, + `sdnControllerUuid` varchar(32) NOT NULL, + `znsSegmentUuid` varchar(36) DEFAULT NULL, + `zoneUuid` varchar(32) NOT NULL, + `l2NetworkUuid` varchar(32) NOT NULL, + `accountUuid` varchar(32) NOT NULL, + `l3NetworkUuid` varchar(32) DEFAULT NULL, + `znsTenantRouterUuid` varchar(36) DEFAULT NULL, + `state` varchar(32) NOT NULL, + `operationUuid` varchar(36) DEFAULT NULL, + `operationStep` varchar(32) DEFAULT NULL, + `operationDigest` varchar(64) DEFAULT NULL, + `currentConfigVersion` bigint DEFAULT NULL, + `appliedConfigVersion` bigint DEFAULT NULL, + `lastErrorCode` varchar(128) DEFAULT NULL, + `lastErrorDetails` text DEFAULT NULL, + `createDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', + `lastOpDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00' ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`uuid`), + UNIQUE KEY `uk_zns_seg_ref_controller_segment` + (`sdnControllerUuid`, `znsSegmentUuid`), + UNIQUE KEY `uk_zns_seg_ref_l2` (`l2NetworkUuid`), + KEY `idx_zns_seg_ref_account` (`accountUuid`), + KEY `idx_zns_seg_ref_state` (`sdnControllerUuid`, `state`), + CONSTRAINT `fkZnsSegmentRefVOSdnControllerVO` + FOREIGN KEY (`sdnControllerUuid`) REFERENCES `zstack`.`SdnControllerVO` (`uuid`) ON DELETE CASCADE, + CONSTRAINT `chk_zns_seg_ref_segment_state` + CHECK (`znsSegmentUuid` IS NOT NULL OR `state` = 'MigrationFailed') +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TRIGGER IF EXISTS `zstack`.`trg_zns_seg_ref_validate_insert`; +DELIMITER $$ +CREATE TRIGGER `zstack`.`trg_zns_seg_ref_validate_insert` +BEFORE INSERT ON `zstack`.`ZnsSegmentRefVO` +FOR EACH ROW +BEGIN + IF NEW.`znsSegmentUuid` IS NULL AND NEW.`state` <> 'MigrationFailed' THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'ZnsSegmentRefVO requires znsSegmentUuid unless migration failed'; + END IF; +END$$ +DELIMITER ; + +DROP TRIGGER IF EXISTS `zstack`.`trg_zns_seg_ref_validate_update`; +DELIMITER $$ +CREATE TRIGGER `zstack`.`trg_zns_seg_ref_validate_update` +BEFORE UPDATE ON `zstack`.`ZnsSegmentRefVO` +FOR EACH ROW +BEGIN + IF NEW.`znsSegmentUuid` IS NULL AND NEW.`state` <> 'MigrationFailed' THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'ZnsSegmentRefVO requires znsSegmentUuid unless migration failed'; + END IF; +END$$ +DELIMITER ; + +CALL ADD_COLUMN('ZnsSegmentRefVO', 'currentConfigVersion', 'BIGINT', 1, NULL); diff --git a/conf/serviceConfig/sdnController.xml b/conf/serviceConfig/sdnController.xml index 2ae849eb987..0f8b7cd4712 100644 --- a/conf/serviceConfig/sdnController.xml +++ b/conf/serviceConfig/sdnController.xml @@ -45,6 +45,10 @@ query + + org.zstack.sdnController.header.APIPullSdnControllerMsg + + org.zstack.network.hostNetworkInterface.APIQueryPhysicalSwitchMsg query diff --git a/conf/springConfigXml/sdnController.xml b/conf/springConfigXml/sdnController.xml index e76be084ac6..0372fc43e06 100644 --- a/conf/springConfigXml/sdnController.xml +++ b/conf/springConfigXml/sdnController.xml @@ -26,6 +26,7 @@ + diff --git a/core/src/main/java/org/zstack/core/cascade/CascadeAction.java b/core/src/main/java/org/zstack/core/cascade/CascadeAction.java index 75722e03661..e6486d66b4c 100755 --- a/core/src/main/java/org/zstack/core/cascade/CascadeAction.java +++ b/core/src/main/java/org/zstack/core/cascade/CascadeAction.java @@ -2,6 +2,9 @@ import org.zstack.header.exception.CloudRuntimeException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + /** */ public class CascadeAction implements Cloneable { @@ -11,6 +14,7 @@ public class CascadeAction implements Cloneable { private Object rootIssuerContext; private String actionCode; private boolean fullTraverse; + private Map contexts = new ConcurrentHashMap<>(); public boolean isFullTraverse() { return fullTraverse; @@ -65,6 +69,15 @@ public CascadeAction copy() { } } + public CascadeAction putContext(String key, Object value) { + contexts.put(key, value); + return this; + } + + public T getContext(String key) { + return (T) contexts.get(key); + } + public String getActionCode() { return actionCode; } diff --git a/core/src/main/java/org/zstack/core/cascade/CascadeFacadeImpl.java b/core/src/main/java/org/zstack/core/cascade/CascadeFacadeImpl.java index f7dad373ce1..19aaec82db1 100755 --- a/core/src/main/java/org/zstack/core/cascade/CascadeFacadeImpl.java +++ b/core/src/main/java/org/zstack/core/cascade/CascadeFacadeImpl.java @@ -212,6 +212,14 @@ public void asyncCascade(CascadeAction action, final Completion completion) { TreeNode root = cascadeTree.get(action.getRootIssuer()); DebugUtils.Assert(root != null, String.format("found no CascadeExtension for %s", action.getRootIssuer())); + List prepared = new ArrayList<>(); + ErrorCode prepareError = prepareAsyncCascade(root, true, action.isFullTraverse(), action, + prepared, new HashSet<>()); + if (prepareError != null) { + rollbackPreparedCascade(prepared); + completion.fail(prepareError); + return; + } List paths = new ArrayList<>(); collectPathsForAsyncCascade(root, true, action.isFullTraverse(), action, paths, new HashSet<>()); FlowChain chain = FlowChainBuilder.newSimpleFlowChain(); @@ -248,11 +256,72 @@ public void handle(Map data) { }).error(new FlowErrorHandler(completion) { @Override public void handle(ErrorCode errCode, Map data) { + rollbackPreparedCascade(prepared); completion.fail(errCode); } }).setName(String.format("Cascade: %s", action.getActionCode())).start(); } + private ErrorCode prepareAsyncCascade(TreeNode treeNode, boolean init, boolean fullTraverse, + CascadeAction action, List prepared, + Set mergePaths) { + Node node = treeNode.node; + String cascadePath = getCascadePath(action.getParentIssuer(), node.getName()); + if (mergePaths.add(cascadePath)) { + CascadeExtensionPoint extension = extensionForAction(node, action); + if (extension instanceof CascadePreExtensionPoint) { + ErrorCode error = ((CascadePreExtensionPoint) extension).beforeCascade(action); + if (error != null) { + return error; + } + prepared.add(Bucket.newBucket(extension, action)); + } + } + + CascadeAction childAction = init ? action : node.getExtension().createActionForChildResource(action); + if (fullTraverse && childAction == null) { + childAction = new CascadeAction(); + childAction.setActionCode(action.getActionCode()); + childAction.setRootIssuer(action.getRootIssuer()); + childAction.setRootIssuerContext(action.getRootIssuerContext()); + childAction.setParentIssuer(node.getName()); + childAction.setParentIssuerContext(null); + } + + if (childAction != null) { + checkForNullElement(node, childAction); + for (TreeNode child : treeNode.leafs) { + ErrorCode error = prepareAsyncCascade(child, false, fullTraverse, childAction, + prepared, mergePaths); + if (error != null) { + return error; + } + } + } + return null; + } + + private CascadeExtensionPoint extensionForAction(Node node, CascadeAction action) { + CascadeExtensionPoint extension = node.getExtension(); + if (extension instanceof CascadeWrapper) { + return ((CascadeWrapper) extension).findExtensionPointByParent(action.getParentIssuer()); + } + return extension; + } + + private void rollbackPreparedCascade(List prepared) { + for (int i = prepared.size() - 1; i >= 0; i--) { + CascadePreExtensionPoint extension = prepared.get(i).get(0); + CascadeAction action = prepared.get(i).get(1); + try { + extension.afterCascadeFailure(action); + } catch (RuntimeException e) { + logger.warn(String.format("failed to roll back cascade preparation[%s]", + extension.getClass().getName()), e); + } + } + } + private void runNode(Node node, CascadeAction caction, Completion completion) { List branches = asyncBranchCascadeExtensionPoints.get(node.getName()); boolean skipNode = false; diff --git a/core/src/main/java/org/zstack/core/cascade/CascadePreExtensionPoint.java b/core/src/main/java/org/zstack/core/cascade/CascadePreExtensionPoint.java new file mode 100644 index 00000000000..c1c547911c2 --- /dev/null +++ b/core/src/main/java/org/zstack/core/cascade/CascadePreExtensionPoint.java @@ -0,0 +1,9 @@ +package org.zstack.core.cascade; + +import org.zstack.header.errorcode.ErrorCode; + +public interface CascadePreExtensionPoint { + ErrorCode beforeCascade(CascadeAction action); + + void afterCascadeFailure(CascadeAction action); +} diff --git a/core/src/main/java/org/zstack/core/rest/RESTFacadeImpl.java b/core/src/main/java/org/zstack/core/rest/RESTFacadeImpl.java index 26dbb7ec675..03e1e940210 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,42 @@ 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) { + SyncHttpResponse response = handler.handleSyncHttpCall(JSONObjectUtil.toObject(entity.getBody(), objectType)); + if (response == null) { + servletResponse.setStatus(HttpStatus.SC_OK); + return; + } + servletResponse.setStatus(response.getStatus()); + if (response.getBody() != null) { + servletResponse.setHeader("Content-Type", "application/json"); + try { + servletResponse.getWriter().write(response.getBody()); + } catch (IOException e) { + throw new CloudRuntimeException(e); + } + } + } + + @Override + public HttpCallHandler getHandler() { return handler; } + }); + } + @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..7dbc0fbe415 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 @@ -99,14 +99,28 @@ 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); + } + + public String submit(String taskId, ReturnValueCompletion completion, + TimeUnit unit, long timeout) { + if (taskId == null || taskId.trim().isEmpty()) { + throw new IllegalArgumentException("taskId cannot be empty"); + } ThreadFacadeImpl.TimeoutTaskReceipt timeoutReceipt = thdf.submitTimeoutTask(() -> { fail(taskId, operr("[Webhook Timeout] callback timed out for taskId[%s], path[%s]", taskId, protocol.getCallbackPath())); }, unit, timeout); - pendingCalls.put(taskId, new PendingEntry<>(completion, timeoutReceipt)); + PendingEntry previous = pendingCalls.putIfAbsent(taskId, + new PendingEntry<>(completion, timeoutReceipt)); + if (previous != null) { + timeoutReceipt.cancel(); + throw new IllegalStateException(String.format( + "webhook taskId[%s] is already pending for path[%s]", + taskId, protocol.getCallbackPath())); + } return taskId; } diff --git a/header/src/main/java/org/zstack/header/network/CompleteZnsSegmentMutationMsg.java b/header/src/main/java/org/zstack/header/network/CompleteZnsSegmentMutationMsg.java new file mode 100644 index 00000000000..0c160f481aa --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/CompleteZnsSegmentMutationMsg.java @@ -0,0 +1,69 @@ +package org.zstack.header.network; + +import org.zstack.header.message.NeedReplyMessage; + +public class CompleteZnsSegmentMutationMsg extends NeedReplyMessage { + private String controllerUuid; + private String relationUuid; + private String operationUuid; + private long acceptedConfigVersion; + private boolean success; + private String errorCode; + private String errorDetails; + + public String getControllerUuid() { + return controllerUuid; + } + + public void setControllerUuid(String controllerUuid) { + this.controllerUuid = controllerUuid; + } + + public String getRelationUuid() { + return relationUuid; + } + + public void setRelationUuid(String relationUuid) { + this.relationUuid = relationUuid; + } + + public String getOperationUuid() { + return operationUuid; + } + + public void setOperationUuid(String operationUuid) { + this.operationUuid = operationUuid; + } + + public long getAcceptedConfigVersion() { + return acceptedConfigVersion; + } + + public void setAcceptedConfigVersion(long acceptedConfigVersion) { + this.acceptedConfigVersion = acceptedConfigVersion; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getErrorCode() { + return errorCode; + } + + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } + + public String getErrorDetails() { + return errorDetails; + } + + public void setErrorDetails(String errorDetails) { + this.errorDetails = errorDetails; + } +} diff --git a/header/src/main/java/org/zstack/header/network/NetworkConfigLocalContinuation.java b/header/src/main/java/org/zstack/header/network/NetworkConfigLocalContinuation.java new file mode 100644 index 00000000000..26b00a0ac58 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/NetworkConfigLocalContinuation.java @@ -0,0 +1,7 @@ +package org.zstack.header.network; + +import org.zstack.header.core.Completion; + +public interface NetworkConfigLocalContinuation { + void run(Completion completion); +} diff --git a/header/src/main/java/org/zstack/header/network/NetworkConfigMutation.java b/header/src/main/java/org/zstack/header/network/NetworkConfigMutation.java new file mode 100644 index 00000000000..64b3e2e6a49 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/NetworkConfigMutation.java @@ -0,0 +1,291 @@ +package org.zstack.header.network; + +import org.zstack.header.network.l2.NetworkOperationOrigin; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class NetworkConfigMutation { + public enum Kind { + ENCAP, + L3_CREATE, + IPAM, + DHCP_DNS + } + + public static final class EncapsulationTarget { + private final String l2Type; + private final int virtualNetworkId; + + private EncapsulationTarget(String l2Type, int virtualNetworkId) { + this.l2Type = l2Type; + this.virtualNetworkId = virtualNetworkId; + } + + public String getL2Type() { + return l2Type; + } + + public int getVirtualNetworkId() { + return virtualNetworkId; + } + } + + public static final class L3CreateTarget { + private final String l3Uuid; + private final String l3Type; + private final List systemTags; + + private L3CreateTarget(String l3Uuid, String l3Type, List systemTags) { + this.l3Uuid = l3Uuid; + this.l3Type = l3Type; + this.systemTags = Collections.unmodifiableList(new ArrayList<>( + systemTags == null ? Collections.emptyList() : systemTags)); + } + + public String getL3Uuid() { + return l3Uuid; + } + + public String getL3Type() { + return l3Type; + } + + public List getSystemTags() { + return systemTags; + } + } + + public static final class IpRangeTarget { + private final String rangeUuid; + private final String startIp; + private final String endIp; + + public IpRangeTarget(String rangeUuid, String startIp, String endIp) { + this.rangeUuid = rangeUuid; + this.startIp = startIp; + this.endIp = endIp; + } + + public String getRangeUuid() { + return rangeUuid; + } + + public String getStartIp() { + return startIp; + } + + public String getEndIp() { + return endIp; + } + } + + public static final class IpamTarget { + private final String l3Uuid; + private final int ipVersion; + private final String gatewayAddress; + private final List ranges; + private final boolean delete; + + private IpamTarget(String l3Uuid, int ipVersion, String gatewayAddress, + List ranges, boolean delete) { + this.l3Uuid = l3Uuid; + this.ipVersion = ipVersion; + this.gatewayAddress = gatewayAddress; + this.ranges = Collections.unmodifiableList(new ArrayList<>(ranges)); + this.delete = delete; + } + + public String getL3Uuid() { + return l3Uuid; + } + + public int getIpVersion() { + return ipVersion; + } + + public String getGatewayAddress() { + return gatewayAddress; + } + + public List getRanges() { + return ranges; + } + + public boolean isDelete() { + return delete; + } + } + + public static final class DhcpTarget { + private final String l3Uuid; + private final boolean enabled; + private final List systemTags; + private final Integer ipVersion; + private final List dnsServers; + + private DhcpTarget(String l3Uuid, boolean enabled, List systemTags, + Integer ipVersion, List dnsServers) { + this.l3Uuid = l3Uuid; + this.enabled = enabled; + this.systemTags = Collections.unmodifiableList(new ArrayList<>( + systemTags == null ? Collections.emptyList() : systemTags)); + this.ipVersion = ipVersion; + this.dnsServers = dnsServers == null ? null + : Collections.unmodifiableList(new ArrayList<>(dnsServers)); + } + + public String getL3Uuid() { + return l3Uuid; + } + + public boolean isEnabled() { + return enabled; + } + + public List getSystemTags() { + return systemTags; + } + + public Integer getIpVersion() { + return ipVersion; + } + + public List getDnsServers() { + return dnsServers; + } + } + + private final Kind kind; + private final String l2Uuid; + private final NetworkOperationOrigin origin; + private final String operationUuid; + private final String accountUuid; + private final EncapsulationTarget encapsulation; + private final L3CreateTarget l3Create; + private final IpamTarget ipam; + private final DhcpTarget dhcp; + + private NetworkConfigMutation(Kind kind, String l2Uuid, NetworkOperationOrigin origin, + String operationUuid, String accountUuid, + EncapsulationTarget encapsulation, + L3CreateTarget l3Create, + IpamTarget ipam, + DhcpTarget dhcp) { + this.kind = kind; + this.l2Uuid = l2Uuid; + this.origin = origin; + this.operationUuid = operationUuid; + this.accountUuid = accountUuid; + this.encapsulation = encapsulation; + this.l3Create = l3Create; + this.ipam = ipam; + this.dhcp = dhcp; + } + + public static NetworkConfigMutation encapsulation(String l2Uuid, + NetworkOperationOrigin origin, + String operationUuid, + String accountUuid, + String l2Type, + int virtualNetworkId) { + return new NetworkConfigMutation(Kind.ENCAP, l2Uuid, origin, operationUuid, + accountUuid, new EncapsulationTarget(l2Type, virtualNetworkId), null, null, null); + } + + public static NetworkConfigMutation l3Create(String l2Uuid, + NetworkOperationOrigin origin, + String operationUuid, + String accountUuid, + String l3Uuid, + String l3Type, + List systemTags) { + return new NetworkConfigMutation(Kind.L3_CREATE, l2Uuid, origin, operationUuid, + accountUuid, null, new L3CreateTarget(l3Uuid, l3Type, systemTags), null, null); + } + + public static NetworkConfigMutation ipam(String l2Uuid, + NetworkOperationOrigin origin, + String operationUuid, + String accountUuid, + String l3Uuid, + int ipVersion, + String gatewayAddress, + List ranges) { + return new NetworkConfigMutation(Kind.IPAM, l2Uuid, origin, operationUuid, + accountUuid, null, null, + new IpamTarget(l3Uuid, ipVersion, gatewayAddress, ranges, false), null); + } + + public static NetworkConfigMutation deleteIpam(String l2Uuid, + NetworkOperationOrigin origin, + String operationUuid, + String accountUuid, + String l3Uuid, + int ipVersion) { + return new NetworkConfigMutation(Kind.IPAM, l2Uuid, origin, operationUuid, + accountUuid, null, null, + new IpamTarget(l3Uuid, ipVersion, null, Collections.emptyList(), true), null); + } + + public static NetworkConfigMutation dhcp(String l2Uuid, + NetworkOperationOrigin origin, + String operationUuid, + String accountUuid, + String l3Uuid, + boolean enabled, + List systemTags) { + return new NetworkConfigMutation(Kind.DHCP_DNS, l2Uuid, origin, operationUuid, + accountUuid, null, null, null, + new DhcpTarget(l3Uuid, enabled, systemTags, null, null)); + } + + public static NetworkConfigMutation dhcpDns(String l2Uuid, + NetworkOperationOrigin origin, + String operationUuid, + String accountUuid, + String l3Uuid, + int ipVersion, + List dnsServers) { + return new NetworkConfigMutation(Kind.DHCP_DNS, l2Uuid, origin, operationUuid, + accountUuid, null, null, null, + new DhcpTarget(l3Uuid, true, Collections.emptyList(), ipVersion, dnsServers)); + } + + public Kind getKind() { + return kind; + } + + public String getL2Uuid() { + return l2Uuid; + } + + public NetworkOperationOrigin getOrigin() { + return origin; + } + + public String getOperationUuid() { + return operationUuid; + } + + public String getAccountUuid() { + return accountUuid; + } + + public EncapsulationTarget getEncapsulation() { + return encapsulation; + } + + public L3CreateTarget getL3Create() { + return l3Create; + } + + public IpamTarget getIpam() { + return ipam; + } + + public DhcpTarget getDhcp() { + return dhcp; + } +} diff --git a/header/src/main/java/org/zstack/header/network/NetworkConfigMutationExtensionPoint.java b/header/src/main/java/org/zstack/header/network/NetworkConfigMutationExtensionPoint.java new file mode 100644 index 00000000000..2bc95a80914 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/NetworkConfigMutationExtensionPoint.java @@ -0,0 +1,11 @@ +package org.zstack.header.network; + +import org.zstack.header.core.Completion; + +public interface NetworkConfigMutationExtensionPoint { + boolean supports(NetworkConfigMutation mutation); + + void mutate(NetworkConfigMutation mutation, + NetworkConfigLocalContinuation continuation, + Completion completion); +} diff --git a/header/src/main/java/org/zstack/header/network/NetworkDeleteGuardExtensionPoint.java b/header/src/main/java/org/zstack/header/network/NetworkDeleteGuardExtensionPoint.java new file mode 100644 index 00000000000..4b599fbc09b --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/NetworkDeleteGuardExtensionPoint.java @@ -0,0 +1,11 @@ +package org.zstack.header.network; + +import org.zstack.header.errorcode.ErrorCode; + +public interface NetworkDeleteGuardExtensionPoint { + ErrorCode checkL3Network(String l3NetworkUuid); + + default ErrorCode checkL3NetworkWithLock(String l3NetworkUuid) { + return checkL3Network(l3NetworkUuid); + } +} diff --git a/header/src/main/java/org/zstack/header/network/PrepareZnsSegmentMutationMsg.java b/header/src/main/java/org/zstack/header/network/PrepareZnsSegmentMutationMsg.java new file mode 100644 index 00000000000..0286c4845e6 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/PrepareZnsSegmentMutationMsg.java @@ -0,0 +1,51 @@ +package org.zstack.header.network; + +import org.zstack.header.message.NeedReplyMessage; + +public class PrepareZnsSegmentMutationMsg extends NeedReplyMessage { + private NetworkConfigMutation mutation; + private String controllerUuid; + private String segmentUuid; + private String relationUuid; + private long expectedConfigVersion; + + public NetworkConfigMutation getMutation() { + return mutation; + } + + public void setMutation(NetworkConfigMutation mutation) { + this.mutation = mutation; + } + + public String getControllerUuid() { + return controllerUuid; + } + + public void setControllerUuid(String controllerUuid) { + this.controllerUuid = controllerUuid; + } + + public String getSegmentUuid() { + return segmentUuid; + } + + public void setSegmentUuid(String segmentUuid) { + this.segmentUuid = segmentUuid; + } + + public String getRelationUuid() { + return relationUuid; + } + + public void setRelationUuid(String relationUuid) { + this.relationUuid = relationUuid; + } + + public long getExpectedConfigVersion() { + return expectedConfigVersion; + } + + public void setExpectedConfigVersion(long expectedConfigVersion) { + this.expectedConfigVersion = expectedConfigVersion; + } +} diff --git a/header/src/main/java/org/zstack/header/network/PrepareZnsSegmentMutationReply.java b/header/src/main/java/org/zstack/header/network/PrepareZnsSegmentMutationReply.java new file mode 100644 index 00000000000..479280f8d14 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/PrepareZnsSegmentMutationReply.java @@ -0,0 +1,15 @@ +package org.zstack.header.network; + +import org.zstack.header.message.MessageReply; + +public class PrepareZnsSegmentMutationReply extends MessageReply { + private long acceptedConfigVersion; + + public long getAcceptedConfigVersion() { + return acceptedConfigVersion; + } + + public void setAcceptedConfigVersion(long acceptedConfigVersion) { + this.acceptedConfigVersion = acceptedConfigVersion; + } +} diff --git a/header/src/main/java/org/zstack/header/network/l2/AttachL2NetworkToClusterMsg.java b/header/src/main/java/org/zstack/header/network/l2/AttachL2NetworkToClusterMsg.java index af5a9290e55..ab310a04ae0 100644 --- a/header/src/main/java/org/zstack/header/network/l2/AttachL2NetworkToClusterMsg.java +++ b/header/src/main/java/org/zstack/header/network/l2/AttachL2NetworkToClusterMsg.java @@ -8,6 +8,8 @@ public class AttachL2NetworkToClusterMsg extends NeedReplyMessage implements L2N private String l2NetworkUuid; private String clusterUuid; private String l2ProviderType; + private String operationUuid; + private String operationStep; public String getL2NetworkUuid() { return l2NetworkUuid; @@ -32,4 +34,20 @@ public String getL2ProviderType() { public void setL2ProviderType(String l2ProviderType) { this.l2ProviderType = l2ProviderType; } + + public String getOperationUuid() { + return operationUuid; + } + + public void setOperationUuid(String operationUuid) { + this.operationUuid = operationUuid; + } + + public String getOperationStep() { + return operationStep; + } + + public void setOperationStep(String operationStep) { + this.operationStep = operationStep; + } } diff --git a/header/src/main/java/org/zstack/header/network/l2/CreateL2NetworkMsg.java b/header/src/main/java/org/zstack/header/network/l2/CreateL2NetworkMsg.java new file mode 100644 index 00000000000..85ce18295f4 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l2/CreateL2NetworkMsg.java @@ -0,0 +1,80 @@ +package org.zstack.header.network.l2; + +import org.zstack.header.identity.SessionInventory; +import org.zstack.header.log.NoLogging; +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.utils.gson.JSONObjectUtil; + +/** Internal, typed local L2 create request used by projection/recovery flows. */ +public class CreateL2NetworkMsg extends NeedReplyMessage { + private String name; + private String description; + private String zoneUuid; + private String physicalInterface; + private String type; + private String vSwitchType = L2NetworkConstant.VSWITCH_TYPE_LINUX_BRIDGE; + private Boolean isolated = Boolean.FALSE; + private String pvlan; + private Integer vlan; + private String resourceUuid; + private String accountUuid; + private NetworkCreateContext context; + private SessionInventory session; + @NoLogging(behavior = NoLogging.Behavior.Auto) + private Object subtypeMessage; + private String subtypeMessageClassName; + + public String getName() { return name; } + public void setName(String value) { name = value; } + public String getDescription() { return description; } + public void setDescription(String value) { description = value; } + public String getZoneUuid() { return zoneUuid; } + public void setZoneUuid(String value) { zoneUuid = value; } + public String getPhysicalInterface() { return physicalInterface; } + public void setPhysicalInterface(String value) { physicalInterface = value; } + public String getType() { return type; } + public void setType(String value) { type = value; } + public String getvSwitchType() { return vSwitchType; } + public void setvSwitchType(String value) { vSwitchType = value; } + public Boolean getIsolated() { return isolated; } + public void setIsolated(Boolean value) { isolated = value; } + public String getPvlan() { return pvlan; } + public void setPvlan(String value) { pvlan = value; } + public Integer getVlan() { return vlan; } + public void setVlan(Integer value) { vlan = value; } + public String getResourceUuid() { return resourceUuid; } + public void setResourceUuid(String value) { resourceUuid = value; } + public String getAccountUuid() { return accountUuid; } + public void setAccountUuid(String value) { accountUuid = value; } + public NetworkCreateContext getContext() { return context; } + public void setContext(NetworkCreateContext value) { context = value; } + public SessionInventory getSession() { return session; } + public void setSession(SessionInventory value) { session = value; } + public APICreateL2NetworkMsg getSubtypeMessage() { + if (subtypeMessage == null || subtypeMessage instanceof APICreateL2NetworkMsg) { + return (APICreateL2NetworkMsg) subtypeMessage; + } + if (subtypeMessageClassName == null) { + throw new IllegalArgumentException("missing l2 subtype message class"); + } + + try { + Class subtypeClass = Class.forName(subtypeMessageClassName, false, + CreateL2NetworkMsg.class.getClassLoader()); + if (!APICreateL2NetworkMsg.class.isAssignableFrom(subtypeClass)) { + throw new IllegalArgumentException(String.format( + "invalid l2 subtype message class[%s]", subtypeMessageClassName)); + } + subtypeMessage = JSONObjectUtil.rehashObject(subtypeMessage, subtypeClass); + return (APICreateL2NetworkMsg) subtypeMessage; + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException(String.format( + "cannot find l2 subtype message class[%s]", subtypeMessageClassName), e); + } + } + + public void setSubtypeMessage(APICreateL2NetworkMsg value) { + subtypeMessage = value; + subtypeMessageClassName = value == null ? null : value.getClass().getName(); + } +} diff --git a/header/src/main/java/org/zstack/header/network/l2/CreateL2NetworkReply.java b/header/src/main/java/org/zstack/header/network/l2/CreateL2NetworkReply.java new file mode 100644 index 00000000000..64fe737e036 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l2/CreateL2NetworkReply.java @@ -0,0 +1,10 @@ +package org.zstack.header.network.l2; + +import org.zstack.header.message.MessageReply; + +public class CreateL2NetworkReply extends MessageReply { + private L2NetworkInventory inventory; + + public L2NetworkInventory getInventory() { return inventory; } + public void setInventory(L2NetworkInventory value) { inventory = value; } +} diff --git a/header/src/main/java/org/zstack/header/network/l2/ExternalNetworkRef.java b/header/src/main/java/org/zstack/header/network/l2/ExternalNetworkRef.java new file mode 100644 index 00000000000..60d74831518 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l2/ExternalNetworkRef.java @@ -0,0 +1,15 @@ +package org.zstack.header.network.l2; + +/** Stable external identity carried by a projection create without changing the public API message. */ +public class ExternalNetworkRef { + private final String resourceUuid; + private final String accountUuid; + + public ExternalNetworkRef(String resourceUuid, String accountUuid) { + this.resourceUuid = resourceUuid; + this.accountUuid = accountUuid; + } + + public String getResourceUuid() { return resourceUuid; } + public String getAccountUuid() { return accountUuid; } +} diff --git a/header/src/main/java/org/zstack/header/network/l2/L2DeleteConfirmExtensionPoint.java b/header/src/main/java/org/zstack/header/network/l2/L2DeleteConfirmExtensionPoint.java new file mode 100644 index 00000000000..b1fa1d36974 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l2/L2DeleteConfirmExtensionPoint.java @@ -0,0 +1,28 @@ +package org.zstack.header.network.l2; + +import org.zstack.header.errorcode.ErrorCode; + +/** Provider hook for confirmed remote-first L2 deletion. */ +public interface L2DeleteConfirmExtensionPoint { + boolean supports(L2NetworkInventory inventory); + ErrorCode begin(L2NetworkInventory inventory); + default ErrorCode begin(L2NetworkInventory inventory, NetworkDeletionContext context) { + return begin(inventory); + } + ErrorCode check(L2NetworkInventory inventory); + default ErrorCode check(L2NetworkInventory inventory, NetworkDeletionContext context) { + return check(inventory); + } + ErrorCode delete(L2NetworkInventory inventory); + default ErrorCode delete(L2NetworkInventory inventory, NetworkDeletionContext context) { + return delete(inventory); + } + ErrorCode cancel(L2NetworkInventory inventory); + default ErrorCode cancel(L2NetworkInventory inventory, NetworkDeletionContext context) { + return cancel(inventory); + } + void deleteLocalMetadata(L2NetworkInventory inventory); + default void deleteLocalMetadata(L2NetworkInventory inventory, NetworkDeletionContext context) { + deleteLocalMetadata(inventory); + } +} diff --git a/header/src/main/java/org/zstack/header/network/l2/L2NetworkCreateExtensionPoint.java b/header/src/main/java/org/zstack/header/network/l2/L2NetworkCreateExtensionPoint.java index d50559128d9..2d38a75bcb5 100755 --- a/header/src/main/java/org/zstack/header/network/l2/L2NetworkCreateExtensionPoint.java +++ b/header/src/main/java/org/zstack/header/network/l2/L2NetworkCreateExtensionPoint.java @@ -6,6 +6,14 @@ public interface L2NetworkCreateExtensionPoint { void beforeCreateL2Network(APICreateL2NetworkMsg msg) throws NetworkException; + default void beforeCreateL2Network(APICreateL2NetworkMsg msg, NetworkCreateContext context) throws NetworkException { + beforeCreateL2Network(msg); + } + default void postCreateL2Network(L2NetworkInventory l2Network, APICreateL2NetworkMsg msg, Completion completion) {completion.success();} + default void postCreateL2Network(L2NetworkInventory l2Network, APICreateL2NetworkMsg msg, + NetworkCreateContext context, Completion completion) { + postCreateL2Network(l2Network, msg, completion); + } void afterCreateL2Network(L2NetworkInventory l2Network); } diff --git a/header/src/main/java/org/zstack/header/network/l2/L2NetworkDeleteExtensionPoint.java b/header/src/main/java/org/zstack/header/network/l2/L2NetworkDeleteExtensionPoint.java index 3e46f74b1e7..8137ac6f61c 100755 --- a/header/src/main/java/org/zstack/header/network/l2/L2NetworkDeleteExtensionPoint.java +++ b/header/src/main/java/org/zstack/header/network/l2/L2NetworkDeleteExtensionPoint.java @@ -10,5 +10,21 @@ public interface L2NetworkDeleteExtensionPoint { default void deleteL2Network(L2NetworkInventory inv, NoErrorCompletion completion) {completion.done();} + default boolean requiresConfirmedDelete(L2NetworkInventory inv) { return false; } + + default void deleteL2Network(L2NetworkInventory inv, String operationUuid, Completion completion) { + deleteL2Network(inv, new NoErrorCompletion(completion) { + @Override + public void done() { + completion.success(); + } + }); + } + + default void deleteL2Network(L2NetworkInventory inv, NetworkDeletionContext context, + Completion completion) { + deleteL2Network(inv, context == null ? null : context.getOperationUuid(), completion); + } + void afterDeleteL2Network(L2NetworkInventory inventory); } diff --git a/header/src/main/java/org/zstack/header/network/l2/L2NetworkDeletionMsg.java b/header/src/main/java/org/zstack/header/network/l2/L2NetworkDeletionMsg.java index 08273df862f..b760f43376d 100755 --- a/header/src/main/java/org/zstack/header/network/l2/L2NetworkDeletionMsg.java +++ b/header/src/main/java/org/zstack/header/network/l2/L2NetworkDeletionMsg.java @@ -6,6 +6,8 @@ */ public class L2NetworkDeletionMsg extends DeletionMessage implements L2NetworkMessage { private String l2NetworkUuid; + private String operationUuid; + private NetworkDeletionContext networkDeletionContext; public void setL2NetworkUuid(String l2NetworkUuid) { this.l2NetworkUuid = l2NetworkUuid; @@ -15,4 +17,20 @@ public void setL2NetworkUuid(String l2NetworkUuid) { public String getL2NetworkUuid() { return l2NetworkUuid; } + + public String getOperationUuid() { + return operationUuid; + } + + public void setOperationUuid(String operationUuid) { + this.operationUuid = operationUuid; + } + + public NetworkDeletionContext getNetworkDeletionContext() { + return networkDeletionContext; + } + + public void setNetworkDeletionContext(NetworkDeletionContext networkDeletionContext) { + this.networkDeletionContext = networkDeletionContext; + } } diff --git a/header/src/main/java/org/zstack/header/network/l2/L2NetworkFactory.java b/header/src/main/java/org/zstack/header/network/l2/L2NetworkFactory.java index c013389967a..97e36c3f6a1 100755 --- a/header/src/main/java/org/zstack/header/network/l2/L2NetworkFactory.java +++ b/header/src/main/java/org/zstack/header/network/l2/L2NetworkFactory.java @@ -7,5 +7,10 @@ public interface L2NetworkFactory { void createL2Network(L2NetworkVO vo, APICreateL2NetworkMsg msg, ReturnValueCompletion completion); + default void createL2Network(L2NetworkVO vo, APICreateL2NetworkMsg msg, NetworkCreateContext context, + ReturnValueCompletion completion) { + createL2Network(vo, msg, completion); + } + L2Network getL2Network(L2NetworkVO vo); } diff --git a/header/src/main/java/org/zstack/header/network/l2/L2NetworkInventoryDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/network/l2/L2NetworkInventoryDoc_zh_cn.groovy index 5ee174aa8e8..1987bdf0174 100644 --- a/header/src/main/java/org/zstack/header/network/l2/L2NetworkInventoryDoc_zh_cn.groovy +++ b/header/src/main/java/org/zstack/header/network/l2/L2NetworkInventoryDoc_zh_cn.groovy @@ -1,5 +1,7 @@ package org.zstack.header.network.l2 +import java.lang.Boolean +import java.lang.Integer import java.sql.Timestamp import java.sql.Timestamp @@ -43,6 +45,30 @@ doc { type "String" since "0.6" } + field { + name "vSwitchType" + desc "二层网络虚拟交换机类型" + type "String" + since "5.3.0" + } + field { + name "virtualNetworkId" + desc "VLAN/VNI 的虚拟网络 ID" + type "Integer" + since "5.3.0" + } + field { + name "isolated" + desc "二层网络是否开启隔离" + type "Boolean" + since "5.3.0" + } + field { + name "pvlan" + desc "二层网络私有 VLAN ID" + type "String" + since "5.3.0" + } field { name "createDate" desc "创建时间" diff --git a/header/src/main/java/org/zstack/header/network/l2/L2NetworkUpdateExtensionPoint.java b/header/src/main/java/org/zstack/header/network/l2/L2NetworkUpdateExtensionPoint.java index 1336bea1907..df817cf8302 100644 --- a/header/src/main/java/org/zstack/header/network/l2/L2NetworkUpdateExtensionPoint.java +++ b/header/src/main/java/org/zstack/header/network/l2/L2NetworkUpdateExtensionPoint.java @@ -1,5 +1,7 @@ package org.zstack.header.network.l2; +import org.zstack.header.core.Completion; + /** * Created by boce.wang on 03/25/2024. */ @@ -7,5 +9,10 @@ public interface L2NetworkUpdateExtensionPoint { void beforeChangeL2NetworkVlanId(L2NetworkInventory l2Inv); + default void beforeChangeL2NetworkVlanId(L2NetworkInventory l2Inv, Completion completion) { + beforeChangeL2NetworkVlanId(l2Inv); + completion.success(); + } + void afterChangeL2NetworkVlanId(L2NetworkInventory l2Inv); } diff --git a/header/src/main/java/org/zstack/header/network/l2/NetworkCreateContext.java b/header/src/main/java/org/zstack/header/network/l2/NetworkCreateContext.java new file mode 100644 index 00000000000..80d419a5699 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l2/NetworkCreateContext.java @@ -0,0 +1,51 @@ +package org.zstack.header.network.l2; + +/** Additive context for local and projection network creation. */ +public class NetworkCreateContext { + private final NetworkOperationOrigin origin; + private final ExternalNetworkRef externalRef; + private final String operationUuid; + private final String operationStep; + + private NetworkCreateContext(NetworkOperationOrigin origin, ExternalNetworkRef externalRef, + String operationUuid, String operationStep) { + this.origin = origin; + this.externalRef = externalRef; + this.operationUuid = operationUuid; + this.operationStep = operationStep; + } + + public static NetworkCreateContext api() { + return new NetworkCreateContext(NetworkOperationOrigin.API, null, null, null); + } + + public static NetworkCreateContext cloudCommit(String operationUuid) { + if (operationUuid == null || operationUuid.isEmpty()) { + throw new IllegalArgumentException("cloud commit context requires an operation UUID"); + } + return new NetworkCreateContext(NetworkOperationOrigin.CLOUD_COMMIT, null, + operationUuid, "APPLY_LOCAL"); + } + + public static NetworkCreateContext projection(NetworkOperationOrigin origin, ExternalNetworkRef ref) { + return projection(origin, ref, null, null); + } + + public static NetworkCreateContext projection(NetworkOperationOrigin origin, ExternalNetworkRef ref, + String operationUuid, String operationStep) { + 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, operationStep); + } + + public NetworkOperationOrigin getOrigin() { return origin; } + public ExternalNetworkRef getExternalRef() { return externalRef; } + public String getOperationUuid() { return operationUuid; } + public String getOperationStep() { return operationStep; } + 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..f1fc362ebfc --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l2/NetworkDeletionContext.java @@ -0,0 +1,77 @@ +package org.zstack.header.network.l2; + +public class NetworkDeletionContext { + public enum Origin { + WHOLE_L2_SEGMENT_DELETE + } + + private Origin origin; + private String operationUuid; + private String l2NetworkUuid; + private String rootIssuer; + private boolean forceDelete; + private boolean sourceUnavailableCleanup; + + 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; + } +} diff --git a/header/src/main/java/org/zstack/header/network/l2/NetworkOperationOrigin.java b/header/src/main/java/org/zstack/header/network/l2/NetworkOperationOrigin.java new file mode 100644 index 00000000000..afa7be382c6 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l2/NetworkOperationOrigin.java @@ -0,0 +1,9 @@ +package org.zstack.header.network.l2; + +/** Identifies whether a local L2 create came from the public API or a typed projection. */ +public enum NetworkOperationOrigin { + API, + CLOUD_COMMIT, + ZNS_PROJECTION, + ZNS_REFRESH +} diff --git a/header/src/main/java/org/zstack/header/network/l3/AddIpRangeMsg.java b/header/src/main/java/org/zstack/header/network/l3/AddIpRangeMsg.java new file mode 100644 index 00000000000..d1e29605a97 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l3/AddIpRangeMsg.java @@ -0,0 +1,22 @@ +package org.zstack.header.network.l3; + +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.network.l2.NetworkCreateContext; + +/** Internal typed IP range create request for ZNS projection and recovery. */ +public class AddIpRangeMsg extends NeedReplyMessage implements L3NetworkMessage { + private String l3NetworkUuid; + private String operationUuid; + private IpRangeInventory inventory; + private NetworkCreateContext context; + + @Override + public String getL3NetworkUuid() { return l3NetworkUuid; } + public void setL3NetworkUuid(String value) { l3NetworkUuid = value; } + public String getOperationUuid() { return operationUuid; } + public void setOperationUuid(String value) { operationUuid = value; } + public IpRangeInventory getInventory() { return inventory; } + public void setInventory(IpRangeInventory value) { inventory = value; } + public NetworkCreateContext getContext() { return context; } + public void setContext(NetworkCreateContext value) { context = value; } +} diff --git a/header/src/main/java/org/zstack/header/network/l3/AddIpRangeReply.java b/header/src/main/java/org/zstack/header/network/l3/AddIpRangeReply.java new file mode 100644 index 00000000000..a65390c371e --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l3/AddIpRangeReply.java @@ -0,0 +1,10 @@ +package org.zstack.header.network.l3; + +import org.zstack.header.message.MessageReply; + +public class AddIpRangeReply extends MessageReply { + private IpRangeInventory inventory; + + public IpRangeInventory getInventory() { return inventory; } + public void setInventory(IpRangeInventory value) { inventory = value; } +} diff --git a/header/src/main/java/org/zstack/header/network/l3/AfterAddIpRangeExtensionPoint.java b/header/src/main/java/org/zstack/header/network/l3/AfterAddIpRangeExtensionPoint.java index 318d27d9e68..c61af9160f6 100755 --- a/header/src/main/java/org/zstack/header/network/l3/AfterAddIpRangeExtensionPoint.java +++ b/header/src/main/java/org/zstack/header/network/l3/AfterAddIpRangeExtensionPoint.java @@ -1,5 +1,7 @@ package org.zstack.header.network.l3; +import org.zstack.header.network.l2.NetworkCreateContext; + import java.util.List; /** @@ -7,4 +9,8 @@ */ public interface AfterAddIpRangeExtensionPoint { void afterAddIpRange(IpRangeInventory ipr, List systemTags); + + default void afterAddIpRange(IpRangeInventory ipr, List systemTags, NetworkCreateContext context) { + afterAddIpRange(ipr, systemTags); + } } diff --git a/header/src/main/java/org/zstack/header/network/l3/AfterDeleteIpRangeExtensionPoint.java b/header/src/main/java/org/zstack/header/network/l3/AfterDeleteIpRangeExtensionPoint.java index 70833859dfc..42fba54ce47 100644 --- a/header/src/main/java/org/zstack/header/network/l3/AfterDeleteIpRangeExtensionPoint.java +++ b/header/src/main/java/org/zstack/header/network/l3/AfterDeleteIpRangeExtensionPoint.java @@ -1,10 +1,14 @@ package org.zstack.header.network.l3; import org.zstack.header.core.Completion; +import org.zstack.header.network.l2.NetworkDeletionContext; /** * Created by boce.wang on 07/01/2025. */ public interface AfterDeleteIpRangeExtensionPoint { void afterDeleteIpRange(IpRangeInventory ipr); + default void afterDeleteIpRange(IpRangeInventory ipr, NetworkDeletionContext context) { + afterDeleteIpRange(ipr); + } } diff --git a/header/src/main/java/org/zstack/header/network/l3/AllocateIpMsg.java b/header/src/main/java/org/zstack/header/network/l3/AllocateIpMsg.java index 78af601bea6..e75bea0b25d 100755 --- a/header/src/main/java/org/zstack/header/network/l3/AllocateIpMsg.java +++ b/header/src/main/java/org/zstack/header/network/l3/AllocateIpMsg.java @@ -16,6 +16,8 @@ public class AllocateIpMsg extends NeedReplyMessage implements L3NetworkMessage, private String gateway; private String ipv6Gateway; private String ipv6Prefix; + private String operationUuid; + private String operationStep; public String getRequiredIp() { return requiredIp; @@ -114,4 +116,22 @@ public String getIpv6Prefix() { public void setIpv6Prefix(String ipv6Prefix) { this.ipv6Prefix = ipv6Prefix; } + + @Override + public String getOperationUuid() { + return operationUuid; + } + + public void setOperationUuid(String operationUuid) { + this.operationUuid = operationUuid; + } + + @Override + public String getOperationStep() { + return operationStep; + } + + public void setOperationStep(String operationStep) { + this.operationStep = operationStep; + } } diff --git a/header/src/main/java/org/zstack/header/network/l3/ConvertL3NetworkTypeMsg.java b/header/src/main/java/org/zstack/header/network/l3/ConvertL3NetworkTypeMsg.java new file mode 100644 index 00000000000..44904ac410e --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l3/ConvertL3NetworkTypeMsg.java @@ -0,0 +1,31 @@ +package org.zstack.header.network.l3; + +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.network.l2.NetworkCreateContext; + +public class ConvertL3NetworkTypeMsg extends NeedReplyMessage implements L3NetworkMessage { + private String l3NetworkUuid; + private String expectedSourceType; + private String expectedSourceCategory; + private String targetType; + private String targetCategory; + private String managedSystemTagPrefix; + private String targetSystemTag; + private NetworkCreateContext context; + public String getL3NetworkUuid() { return l3NetworkUuid; } + public void setL3NetworkUuid(String value) { l3NetworkUuid = value; } + public String getExpectedSourceType() { return expectedSourceType; } + public void setExpectedSourceType(String value) { expectedSourceType = value; } + public String getExpectedSourceCategory() { return expectedSourceCategory; } + public void setExpectedSourceCategory(String value) { expectedSourceCategory = value; } + public String getTargetType() { return targetType; } + public void setTargetType(String value) { targetType = value; } + public String getTargetCategory() { return targetCategory; } + public void setTargetCategory(String value) { targetCategory = value; } + public String getManagedSystemTagPrefix() { return managedSystemTagPrefix; } + public void setManagedSystemTagPrefix(String value) { managedSystemTagPrefix = value; } + public String getTargetSystemTag() { return targetSystemTag; } + public void setTargetSystemTag(String value) { targetSystemTag = value; } + public NetworkCreateContext getContext() { return context; } + public void setContext(NetworkCreateContext value) { context = value; } +} diff --git a/header/src/main/java/org/zstack/header/network/l3/CreateL3NetworkMsg.java b/header/src/main/java/org/zstack/header/network/l3/CreateL3NetworkMsg.java new file mode 100644 index 00000000000..405f65f6b46 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l3/CreateL3NetworkMsg.java @@ -0,0 +1,46 @@ +package org.zstack.header.network.l3; + +import org.zstack.header.identity.SessionInventory; +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.network.l2.NetworkCreateContext; + +/** Internal typed L3 create request for ZNS projection and recovery. */ +public class CreateL3NetworkMsg extends NeedReplyMessage { + private String name; + private String description; + private String type = L3NetworkConstant.L3_BASIC_NETWORK_TYPE; + private String l2NetworkUuid; + private String category = L3NetworkCategory.Private.toString(); + private Integer ipVersion; + private boolean system; + private String dnsDomain; + private Boolean enableIPAM = Boolean.TRUE; + private String resourceUuid; + private SessionInventory session; + private NetworkCreateContext context; + + public String getName() { return name; } + public void setName(String value) { name = value; } + public String getDescription() { return description; } + public void setDescription(String value) { description = value; } + public String getType() { return type; } + public void setType(String value) { type = value; } + public String getL2NetworkUuid() { return l2NetworkUuid; } + public void setL2NetworkUuid(String value) { l2NetworkUuid = value; } + public String getCategory() { return category; } + public void setCategory(String value) { category = value; } + public Integer getIpVersion() { return ipVersion; } + public void setIpVersion(Integer value) { ipVersion = value; } + public boolean isSystem() { return system; } + public void setSystem(boolean value) { system = value; } + public String getDnsDomain() { return dnsDomain; } + public void setDnsDomain(String value) { dnsDomain = value; } + public Boolean getEnableIPAM() { return enableIPAM; } + public void setEnableIPAM(Boolean value) { enableIPAM = value; } + public String getResourceUuid() { return resourceUuid; } + public void setResourceUuid(String value) { resourceUuid = value; } + public SessionInventory getSession() { return session; } + public void setSession(SessionInventory value) { session = value; } + public NetworkCreateContext getContext() { return context; } + public void setContext(NetworkCreateContext value) { context = value; } +} diff --git a/header/src/main/java/org/zstack/header/network/l3/CreateL3NetworkReply.java b/header/src/main/java/org/zstack/header/network/l3/CreateL3NetworkReply.java new file mode 100644 index 00000000000..17b49b1e727 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l3/CreateL3NetworkReply.java @@ -0,0 +1,10 @@ +package org.zstack.header.network.l3; + +import org.zstack.header.message.MessageReply; + +public class CreateL3NetworkReply extends MessageReply { + private L3NetworkInventory inventory; + + public L3NetworkInventory getInventory() { return inventory; } + public void setInventory(L3NetworkInventory value) { inventory = value; } +} diff --git a/header/src/main/java/org/zstack/header/network/l3/DeleteProjectedIpRangeMsg.java b/header/src/main/java/org/zstack/header/network/l3/DeleteProjectedIpRangeMsg.java new file mode 100644 index 00000000000..5c4aed519c1 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l3/DeleteProjectedIpRangeMsg.java @@ -0,0 +1,20 @@ +package org.zstack.header.network.l3; + +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.network.l2.NetworkCreateContext; + +public class DeleteProjectedIpRangeMsg extends NeedReplyMessage implements L3NetworkMessage { + private String l3NetworkUuid; + private String rangeUuid; + private String expectedSourceType; + private NetworkCreateContext context; + @Override + public String getL3NetworkUuid() { return l3NetworkUuid; } + public void setL3NetworkUuid(String value) { l3NetworkUuid = value; } + public String getRangeUuid() { return rangeUuid; } + public void setRangeUuid(String value) { rangeUuid = value; } + public String getExpectedSourceType() { return expectedSourceType; } + public void setExpectedSourceType(String value) { expectedSourceType = value; } + public NetworkCreateContext getContext() { return context; } + public void setContext(NetworkCreateContext value) { context = value; } +} diff --git a/header/src/main/java/org/zstack/header/network/l3/IpAllocateMessage.java b/header/src/main/java/org/zstack/header/network/l3/IpAllocateMessage.java index 0d886bab7b5..2df457e9755 100755 --- a/header/src/main/java/org/zstack/header/network/l3/IpAllocateMessage.java +++ b/header/src/main/java/org/zstack/header/network/l3/IpAllocateMessage.java @@ -31,6 +31,14 @@ default String getIpv6Prefix() { return null; } + default String getOperationUuid() { + return null; + } + + default String getOperationStep() { + return null; + } + void setIpRangeUuid(String ipRangeUuid); void setRequiredIp(String requiredIp); diff --git a/header/src/main/java/org/zstack/header/network/l3/IpRangeDeletionExtensionPoint.java b/header/src/main/java/org/zstack/header/network/l3/IpRangeDeletionExtensionPoint.java index 3761e566dac..6ce6541aee4 100755 --- a/header/src/main/java/org/zstack/header/network/l3/IpRangeDeletionExtensionPoint.java +++ b/header/src/main/java/org/zstack/header/network/l3/IpRangeDeletionExtensionPoint.java @@ -1,16 +1,26 @@ package org.zstack.header.network.l3; import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.network.l2.NetworkDeletionContext; /** * Created by frank on 11/15/2015. */ public interface IpRangeDeletionExtensionPoint { void preDeleteIpRange(IpRangeInventory ipRange); + default void preDeleteIpRange(IpRangeInventory ipRange, NetworkDeletionContext context) { + preDeleteIpRange(ipRange); + } void beforeDeleteIpRange(IpRangeInventory ipRange); + default void beforeDeleteIpRange(IpRangeInventory ipRange, NetworkDeletionContext context) { + beforeDeleteIpRange(ipRange); + } void afterDeleteIpRange(IpRangeInventory ipRange); + default void afterDeleteIpRange(IpRangeInventory ipRange, NetworkDeletionContext context) { + afterDeleteIpRange(ipRange); + } void failedToDeleteIpRange(IpRangeInventory ipRange, ErrorCode errorCode); } diff --git a/header/src/main/java/org/zstack/header/network/l3/IpRangeDeletionMsg.java b/header/src/main/java/org/zstack/header/network/l3/IpRangeDeletionMsg.java index d14bdd3591e..3984455f3ab 100755 --- a/header/src/main/java/org/zstack/header/network/l3/IpRangeDeletionMsg.java +++ b/header/src/main/java/org/zstack/header/network/l3/IpRangeDeletionMsg.java @@ -1,12 +1,14 @@ package org.zstack.header.network.l3; import org.zstack.header.message.DeletionMessage; +import org.zstack.header.network.l2.NetworkDeletionContext; /** */ public class IpRangeDeletionMsg extends DeletionMessage implements L3NetworkMessage { private String l3NetworkUuid; private String ipRangeUuid; + private NetworkDeletionContext networkDeletionContext; @Override public String getL3NetworkUuid() { @@ -24,4 +26,12 @@ public String getIpRangeUuid() { public void setIpRangeUuid(String ipRangeUuid) { this.ipRangeUuid = ipRangeUuid; } + + public NetworkDeletionContext getNetworkDeletionContext() { + return networkDeletionContext; + } + + public void setNetworkDeletionContext(NetworkDeletionContext networkDeletionContext) { + this.networkDeletionContext = networkDeletionContext; + } } diff --git a/header/src/main/java/org/zstack/header/network/l3/IpRangeFactory.java b/header/src/main/java/org/zstack/header/network/l3/IpRangeFactory.java index 6ad1a5507b7..b227d591926 100644 --- a/header/src/main/java/org/zstack/header/network/l3/IpRangeFactory.java +++ b/header/src/main/java/org/zstack/header/network/l3/IpRangeFactory.java @@ -2,6 +2,7 @@ import org.zstack.header.core.ReturnValueCompletion; import org.zstack.header.message.APICreateMessage; +import org.zstack.header.network.l2.NetworkCreateContext; import java.util.List; @@ -9,4 +10,9 @@ public interface IpRangeFactory { IpRangeType getType(); void createIpRange(List iprs, APICreateMessage msg, ReturnValueCompletion> completion); + + default void createIpRange(List iprs, APICreateMessage msg, NetworkCreateContext context, + ReturnValueCompletion> completion) { + createIpRange(iprs, msg, completion); + } } diff --git a/header/src/main/java/org/zstack/header/network/l3/L3NetworkDeleteExtensionPoint.java b/header/src/main/java/org/zstack/header/network/l3/L3NetworkDeleteExtensionPoint.java index 443018bd4fc..3b619ddc7f7 100755 --- a/header/src/main/java/org/zstack/header/network/l3/L3NetworkDeleteExtensionPoint.java +++ b/header/src/main/java/org/zstack/header/network/l3/L3NetworkDeleteExtensionPoint.java @@ -1,9 +1,22 @@ package org.zstack.header.network.l3; +import org.zstack.header.network.l2.NetworkDeletionContext; + public interface L3NetworkDeleteExtensionPoint { String preDeleteL3Network(L3NetworkInventory inventory) throws L3NetworkException; + default String preDeleteL3Network(L3NetworkInventory inventory, NetworkDeletionContext context) + throws L3NetworkException { + return preDeleteL3Network(inventory); + } + void beforeDeleteL3Network(L3NetworkInventory inventory); + default void beforeDeleteL3Network(L3NetworkInventory inventory, NetworkDeletionContext context) { + beforeDeleteL3Network(inventory); + } void afterDeleteL3Network(L3NetworkInventory inventory); + default void afterDeleteL3Network(L3NetworkInventory inventory, NetworkDeletionContext context) { + afterDeleteL3Network(inventory); + } } diff --git a/header/src/main/java/org/zstack/header/network/l3/L3NetworkDeletionMsg.java b/header/src/main/java/org/zstack/header/network/l3/L3NetworkDeletionMsg.java index bb3d0ecf474..67fb0630dc4 100755 --- a/header/src/main/java/org/zstack/header/network/l3/L3NetworkDeletionMsg.java +++ b/header/src/main/java/org/zstack/header/network/l3/L3NetworkDeletionMsg.java @@ -1,11 +1,13 @@ package org.zstack.header.network.l3; import org.zstack.header.message.DeletionMessage; +import org.zstack.header.network.l2.NetworkDeletionContext; /** */ public class L3NetworkDeletionMsg extends DeletionMessage implements L3NetworkMessage { private String l3NetworkUuid; + private NetworkDeletionContext networkDeletionContext; public String getL3NetworkUuid() { return l3NetworkUuid; @@ -14,4 +16,12 @@ public String getL3NetworkUuid() { public void setL3NetworkUuid(String l3NetworkUuid) { this.l3NetworkUuid = l3NetworkUuid; } + + public NetworkDeletionContext getNetworkDeletionContext() { + return networkDeletionContext; + } + + public void setNetworkDeletionContext(NetworkDeletionContext networkDeletionContext) { + this.networkDeletionContext = networkDeletionContext; + } } diff --git a/header/src/main/java/org/zstack/header/network/l3/L3NetworkFactory.java b/header/src/main/java/org/zstack/header/network/l3/L3NetworkFactory.java index 7398f552874..717f48bf810 100755 --- a/header/src/main/java/org/zstack/header/network/l3/L3NetworkFactory.java +++ b/header/src/main/java/org/zstack/header/network/l3/L3NetworkFactory.java @@ -1,10 +1,16 @@ package org.zstack.header.network.l3; +import org.zstack.header.network.l2.NetworkCreateContext; + public interface L3NetworkFactory { L3NetworkType getType(); L3NetworkInventory createL3Network(L3NetworkVO l3vo, APICreateL3NetworkMsg msg); + default L3NetworkInventory createL3Network(L3NetworkVO l3vo, APICreateL3NetworkMsg msg, NetworkCreateContext context) { + return createL3Network(l3vo, msg); + } + L3Network getL3Network(L3NetworkVO vo); boolean applyNetworkServiceWhenVmStateChange(); diff --git a/header/src/main/java/org/zstack/header/network/l3/SdnControllerDisableDHCPMsg.java b/header/src/main/java/org/zstack/header/network/l3/SdnControllerDisableDHCPMsg.java index 7d0e0c9dcb7..d530e62b8bb 100644 --- a/header/src/main/java/org/zstack/header/network/l3/SdnControllerDisableDHCPMsg.java +++ b/header/src/main/java/org/zstack/header/network/l3/SdnControllerDisableDHCPMsg.java @@ -2,6 +2,7 @@ import org.zstack.header.message.NeedReplyMessage; import org.zstack.header.network.sdncontroller.SdnControllerMessage; +import org.zstack.header.network.l2.NetworkDeletionContext; import org.zstack.utils.network.IPv6Constants; public class SdnControllerDisableDHCPMsg extends NeedReplyMessage implements SdnControllerMessage { @@ -9,6 +10,7 @@ public class SdnControllerDisableDHCPMsg extends NeedReplyMessage implements Sdn private Integer ipVersion = IPv6Constants.DUAL_STACK; private String sdnControllerUuid; private boolean checkIpRange = false; + private NetworkDeletionContext networkDeletionContext; public Integer getIpVersion() { return ipVersion; @@ -42,5 +44,12 @@ public boolean isCheckIpRange() { public void setCheckIpRange(boolean checkIpRange) { this.checkIpRange = checkIpRange; } -} + public NetworkDeletionContext getNetworkDeletionContext() { + return networkDeletionContext; + } + + public void setNetworkDeletionContext(NetworkDeletionContext networkDeletionContext) { + this.networkDeletionContext = networkDeletionContext; + } +} diff --git a/header/src/main/java/org/zstack/header/network/l3/SdnControllerL3.java b/header/src/main/java/org/zstack/header/network/l3/SdnControllerL3.java index 228b05aa98d..0d0d46d3396 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.fail(errorCode); + } + }); + } else { + completion.success(); + } + } } diff --git a/header/src/main/java/org/zstack/header/network/l3/UpdateProjectedDnsMsg.java b/header/src/main/java/org/zstack/header/network/l3/UpdateProjectedDnsMsg.java new file mode 100644 index 00000000000..29c1b153413 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l3/UpdateProjectedDnsMsg.java @@ -0,0 +1,22 @@ +package org.zstack.header.network.l3; + +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.network.l2.NetworkCreateContext; + +import java.util.ArrayList; +import java.util.List; + +/** Internal typed DNS projection request. */ +public class UpdateProjectedDnsMsg extends NeedReplyMessage implements L3NetworkMessage { + private String l3NetworkUuid; + private List dns = new ArrayList<>(); + private NetworkCreateContext context; + + @Override + public String getL3NetworkUuid() { return l3NetworkUuid; } + public void setL3NetworkUuid(String value) { l3NetworkUuid = value; } + public List getDns() { return dns; } + public void setDns(List value) { dns = value; } + public NetworkCreateContext getContext() { return context; } + public void setContext(NetworkCreateContext value) { context = value; } +} diff --git a/header/src/main/java/org/zstack/header/network/l3/UpdateProjectedIpRangeMsg.java b/header/src/main/java/org/zstack/header/network/l3/UpdateProjectedIpRangeMsg.java new file mode 100644 index 00000000000..85345401895 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l3/UpdateProjectedIpRangeMsg.java @@ -0,0 +1,32 @@ +package org.zstack.header.network.l3; + +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.network.l2.NetworkCreateContext; + +public class UpdateProjectedIpRangeMsg extends NeedReplyMessage implements L3NetworkMessage { + private String l3NetworkUuid; + private String rangeUuid; + private String startIp; + private String endIp; + private String gateway; + private String netmask; + private String expectedSourceType; + private NetworkCreateContext context; + @Override + public String getL3NetworkUuid() { return l3NetworkUuid; } + public void setL3NetworkUuid(String value) { l3NetworkUuid = value; } + public String getRangeUuid() { return rangeUuid; } + public void setRangeUuid(String value) { rangeUuid = value; } + public String getStartIp() { return startIp; } + public void setStartIp(String value) { startIp = value; } + public String getEndIp() { return endIp; } + public void setEndIp(String value) { endIp = value; } + public String getGateway() { return gateway; } + public void setGateway(String value) { gateway = value; } + public String getNetmask() { return netmask; } + public void setNetmask(String value) { netmask = value; } + public String getExpectedSourceType() { return expectedSourceType; } + public void setExpectedSourceType(String value) { expectedSourceType = value; } + public NetworkCreateContext getContext() { return context; } + public void setContext(NetworkCreateContext value) { context = value; } +} diff --git a/header/src/main/java/org/zstack/header/rest/RESTFacade.java b/header/src/main/java/org/zstack/header/rest/RESTFacade.java index 23fee5208c2..94569183b18 100755 --- a/header/src/main/java/org/zstack/header/rest/RESTFacade.java +++ b/header/src/main/java/org/zstack/header/rest/RESTFacade.java @@ -85,6 +85,9 @@ public interface RESTFacade { void registerSyncHttpCallHandler(String path, Class objectType, SyncHttpCallHandler handler); + void registerSyncHttpStatusBodyCallHandler(String path, Class objectType, + SyncHttpStatusBodyCallHandler handler); + String getBaseUrl(); String getSendCommandUrl(); diff --git a/header/src/main/java/org/zstack/header/rest/SyncHttpResponse.java b/header/src/main/java/org/zstack/header/rest/SyncHttpResponse.java new file mode 100644 index 00000000000..ece0b4f928e --- /dev/null +++ b/header/src/main/java/org/zstack/header/rest/SyncHttpResponse.java @@ -0,0 +1,13 @@ +package org.zstack.header.rest; + +public final class SyncHttpResponse { + private final int status; + private final String body; + + public SyncHttpResponse(int status, String body) { + this.status = status; + this.body = body; + } + public int getStatus() { return status; } + public String getBody() { return body; } +} diff --git a/header/src/main/java/org/zstack/header/rest/SyncHttpStatusBodyCallHandler.java b/header/src/main/java/org/zstack/header/rest/SyncHttpStatusBodyCallHandler.java new file mode 100644 index 00000000000..41e0a9e39a7 --- /dev/null +++ b/header/src/main/java/org/zstack/header/rest/SyncHttpStatusBodyCallHandler.java @@ -0,0 +1,5 @@ +package org.zstack.header.rest; + +public interface SyncHttpStatusBodyCallHandler extends HttpCallHandler { + SyncHttpResponse handleSyncHttpCall(T cmd); +} diff --git a/header/src/main/java/org/zstack/header/vm/VmNicQosConfigExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/VmNicQosConfigExtensionPoint.java new file mode 100644 index 00000000000..c657b19f0d3 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/VmNicQosConfigExtensionPoint.java @@ -0,0 +1,7 @@ +package org.zstack.header.vm; + +import org.zstack.header.errorcode.ErrorCode; + +public interface VmNicQosConfigExtensionPoint { + ErrorCode validateVmNicQos(String l3NetworkUuid); +} diff --git a/network/src/main/java/org/zstack/network/l2/L2NetworkCascadeExtension.java b/network/src/main/java/org/zstack/network/l2/L2NetworkCascadeExtension.java index 1c78cb16f67..035969c8b5f 100755 --- a/network/src/main/java/org/zstack/network/l2/L2NetworkCascadeExtension.java +++ b/network/src/main/java/org/zstack/network/l2/L2NetworkCascadeExtension.java @@ -5,14 +5,17 @@ import org.zstack.core.cascade.AbstractAsyncCascadeExtension; import org.zstack.core.cascade.CascadeAction; import org.zstack.core.cascade.CascadeConstant; +import org.zstack.core.cascade.CascadePreExtensionPoint; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.CloudBusListCallBack; import org.zstack.core.componentloader.PluginRegistry; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.SQL; import org.zstack.core.db.SimpleQuery; +import org.zstack.core.db.SQLBatch; import org.zstack.core.errorcode.ErrorFacade; import org.zstack.header.core.Completion; +import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.identity.AccountInventory; import org.zstack.header.identity.AccountVO; import org.zstack.header.message.MessageReply; @@ -29,15 +32,18 @@ import javax.persistence.TypedQuery; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.Callable; +import java.util.stream.Collectors; import static org.zstack.core.Platform.inerr; import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.*; /** */ -public class L2NetworkCascadeExtension extends AbstractAsyncCascadeExtension { +public class L2NetworkCascadeExtension extends AbstractAsyncCascadeExtension implements CascadePreExtensionPoint { private static final CLogger logger = Utils.getLogger(L2NetworkCascadeExtension.class); @Autowired @@ -53,6 +59,73 @@ public class L2NetworkCascadeExtension extends AbstractAsyncCascadeExtension { private static final String NAME = L2NetworkVO.class.getSimpleName(); + @Override + public ErrorCode beforeCascade(CascadeAction action) { + if (!CascadeConstant.DELETION_CODES.contains(action.getActionCode())) { + return null; + } + List inventories = l2NetworkFromAction(action); + if (inventories == null) { + return null; + } + inventories = filterInventories(inventories, action); + List begun = new ArrayList<>(); + for (L2NetworkInventory inventory : inventories) { + NetworkDeletionContext context = NetworkDeletionContexts.get(action, inventory.getUuid()); + if (context != null) { + continue; + } + context = new NetworkDeletionContext(NetworkDeletionContext.Origin.WHOLE_L2_SEGMENT_DELETE, + org.zstack.core.Platform.getUuid(), inventory.getUuid(), action.getRootIssuer()); + context.setForceDelete(action.isActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE)); + List extensions = confirmedExtensions(inventory); + for (L2DeleteConfirmExtensionPoint extension : extensions) { + ErrorCode error = extension.begin(inventory, context); + if (error != null) { + cancelConfirmedDeletes(begun); + return error; + } + begun.add(new ConfirmedDelete(inventory, extension, context)); + } + if (!extensions.isEmpty()) { + NetworkDeletionContexts.put(action, context); + } + if (action.isActionCode(CascadeConstant.DELETION_DELETE_CODE, + CascadeConstant.DELETION_FORCE_DELETE_CODE)) { + for (L2DeleteConfirmExtensionPoint extension : extensions) { + ErrorCode error = extension.check(inventory, context); + if (error != null) { + cancelConfirmedDeletes(begun); + return error; + } + } + } + } + return null; + } + + @Override + public void afterCascadeFailure(CascadeAction action) { + if (!action.isActionCode(CascadeConstant.DELETION_CHECK_CODE)) { + return; + } + List inventories = l2NetworkFromAction(action); + if (inventories == null) { + return; + } + List begun = new ArrayList<>(); + for (L2NetworkInventory inventory : filterInventories(inventories, action)) { + NetworkDeletionContext context = NetworkDeletionContexts.get(action, inventory.getUuid()); + if (context == null) { + continue; + } + for (L2DeleteConfirmExtensionPoint extension : confirmedExtensions(inventory)) { + begun.add(new ConfirmedDelete(inventory, extension, context)); + } + } + cancelConfirmedDeletes(begun); + } + @Override public void asyncCascade(CascadeAction action, Completion completion) { if (action.isActionCode(CascadeConstant.DELETION_CHECK_CODE)) { @@ -102,7 +175,9 @@ private void handleDeletionCleanup(CascadeAction action, Completion completion) try { final List l2invs = l2NetworkFromAction(action); if (l2invs != null) { - l2invs.forEach(l -> dbf.eoCleanup(L2NetworkVO.class, l.getUuid())); + l2invs.forEach(l -> { + dbf.eoCleanup(L2NetworkVO.class, l.getUuid()); + }); } else { dbf.eoCleanup(L2NetworkVO.class); } @@ -121,10 +196,12 @@ private void handleDeletion(final CascadeAction action, final Completion complet return; } - if (!l2invs.isEmpty()) { - for (L2NetworkCascadeFilterExtensionPoint ext : pluginRgty.getExtensionList(L2NetworkCascadeFilterExtensionPoint.class)) { - l2invs = ext.filterL2NetworkCascade(l2invs, action); - } + l2invs = filterInventories(l2invs, action); + + Map> confirmedExtensions = new HashMap<>(); + for (L2NetworkInventory l2inv : l2invs) { + List extensions = confirmedExtensions(l2inv); + confirmedExtensions.put(l2inv.getUuid(), extensions); } List msgs = new ArrayList(); @@ -132,6 +209,9 @@ private void handleDeletion(final CascadeAction action, final Completion complet L2NetworkDeletionMsg msg = new L2NetworkDeletionMsg(); msg.setForceDelete(action.isActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE)); msg.setL2NetworkUuid(l2inv.getUuid()); + NetworkDeletionContext context = NetworkDeletionContexts.get(action, l2inv.getUuid()); + msg.setNetworkDeletionContext(context); + msg.setOperationUuid(context == null ? org.zstack.core.Platform.getUuid() : context.getOperationUuid()); bus.makeTargetServiceIdByResourceUuid(msg, L2NetworkConstant.SERVICE_ID, l2inv.getUuid()); msgs.add(msg); } @@ -152,12 +232,42 @@ public void run(List replies) { List uuids = new ArrayList(); for (MessageReply r : replies) { L2NetworkInventory inv = finalL2invs.get(replies.indexOf(r)); + if (!r.isSuccess()) { + if (!confirmedExtensions.get(inv.getUuid()).isEmpty()) { + completion.fail(r.getError()); + return; + } + } + for (L2DeleteConfirmExtensionPoint ext : confirmedExtensions.get(inv.getUuid())) { + ErrorCode errorCode = ext.delete(inv, + NetworkDeletionContexts.get(action, inv.getUuid())); + if (errorCode != null) { + completion.fail(errorCode); + return; + } + } uuids.add(inv.getUuid()); logger.debug(String.format("delete l2 network[uuid:%s, name:%s]", inv.getUuid(), inv.getName())); } - dbf.removeByPrimaryKeys(uuids, L2NetworkVO.class); - completion.success(); + try { + new SQLBatch() { + @Override + protected void scripts() { + for (L2NetworkInventory inv : finalL2invs) { + for (L2DeleteConfirmExtensionPoint ext : confirmedExtensions.get(inv.getUuid())) { + ext.deleteLocalMetadata(inv, + NetworkDeletionContexts.get(action, inv.getUuid())); + } + } + databaseFacade.removeByPrimaryKeys(uuids, L2NetworkVO.class); + } + }.execute(); + completion.success(); + } catch (RuntimeException e) { + completion.fail(inerr(ORG_ZSTACK_NETWORK_L2_10022, + "failed to commit confirmed l2 network deletion: %s", e.getMessage())); + } } }); } @@ -172,6 +282,14 @@ private void handleDeletionCheck(CascadeAction action, Completion completion) { try { for (L2NetworkInventory prinv : l2invs) { extpEmitter.preDelete(prinv); + for (L2DeleteConfirmExtensionPoint ext : confirmedExtensions(prinv)) { + ErrorCode errorCode = ext.check(prinv, + NetworkDeletionContexts.get(action, prinv.getUuid())); + if (errorCode != null) { + completion.fail(errorCode); + return; + } + } } completion.success(); @@ -180,6 +298,54 @@ private void handleDeletionCheck(CascadeAction action, Completion completion) { } } + private void cancelConfirmedDeletes(List begun) { + for (int i = begun.size() - 1; i >= 0; i--) { + ConfirmedDelete delete = begun.get(i); + try { + ErrorCode errorCode = delete.extension.cancel(delete.inventory, delete.context); + if (errorCode != null) { + logger.warn(String.format("failed to cancel confirmed l2 network deletion[uuid:%s]: %s", + delete.inventory.getUuid(), errorCode)); + } + } catch (RuntimeException e) { + logger.warn(String.format("failed to cancel confirmed l2 network deletion[uuid:%s]", + delete.inventory.getUuid()), e); + } + } + } + + private static class ConfirmedDelete { + private final L2NetworkInventory inventory; + private final L2DeleteConfirmExtensionPoint extension; + private final NetworkDeletionContext context; + + private ConfirmedDelete(L2NetworkInventory inventory, L2DeleteConfirmExtensionPoint extension, + NetworkDeletionContext context) { + this.inventory = inventory; + this.extension = extension; + this.context = context; + } + } + + private List confirmedExtensions(L2NetworkInventory inventory) { + return pluginRgty.getExtensionList(L2DeleteConfirmExtensionPoint.class).stream() + .filter(ext -> ext.supports(inventory)) + .collect(Collectors.toList()); + } + + private List filterInventories(List inventories, + CascadeAction action) { + if (inventories.isEmpty()) { + return inventories; + } + List result = inventories; + for (L2NetworkCascadeFilterExtensionPoint ext : + pluginRgty.getExtensionList(L2NetworkCascadeFilterExtensionPoint.class)) { + result = ext.filterL2NetworkCascade(result, action); + } + return result; + } + @Override public List getEdgeNames() { return Arrays.asList(ZoneVO.class.getSimpleName(), diff --git a/network/src/main/java/org/zstack/network/l2/L2NetworkExtensionPointEmitter.java b/network/src/main/java/org/zstack/network/l2/L2NetworkExtensionPointEmitter.java index b1eecae4d5e..a866efdce4f 100755 --- a/network/src/main/java/org/zstack/network/l2/L2NetworkExtensionPointEmitter.java +++ b/network/src/main/java/org/zstack/network/l2/L2NetworkExtensionPointEmitter.java @@ -3,6 +3,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.zstack.core.componentloader.PluginRegistry; import org.zstack.header.Component; +import org.zstack.header.core.Completion; import org.zstack.header.network.l2.L2NetworkDeleteExtensionPoint; import org.zstack.header.network.l2.L2NetworkException; import org.zstack.header.network.l2.L2NetworkInventory; @@ -67,6 +68,28 @@ public void beforeUpdate(final L2NetworkInventory inv) { } } + public void beforeUpdate(final L2NetworkInventory inv, Completion completion) { + beforeUpdate(inv, 0, completion); + } + + private void beforeUpdate(final L2NetworkInventory inv, int index, Completion completion) { + if (index >= updateExtensions.size()) { + completion.success(); + return; + } + updateExtensions.get(index).beforeChangeL2NetworkVlanId(inv, new Completion(completion) { + @Override + public void success() { + beforeUpdate(inv, index + 1, completion); + } + + @Override + public void fail(org.zstack.header.errorcode.ErrorCode errorCode) { + completion.fail(errorCode); + } + }); + } + 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..ffd2ccaf2d0 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_10002, 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_10002, 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_10002, 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_10002, SysErrors.INVALID_ARGUMENT_ERROR, + "account uuid is required for l2 create")); + return; + } + vo.setAccountUuid(accountUuid); vo.setIsolated(msg.getIsolated()); vo.setPvlan(msg.getPvlan()); - factory.createL2Network(vo, msg, new ReturnValueCompletion(msg) { + factory.createL2Network(vo, msg, context, new ReturnValueCompletion(msg) { @Override public void success(L2NetworkInventory returnValue) { tagMgr.createTagsFromAPICreateMessage(msg, returnValue.getUuid(), L2NetworkVO.class.getSimpleName()); @@ -460,16 +537,12 @@ public void success(L2NetworkInventory returnValue) { } } - APICreateL2NetworkEvent evt = new APICreateL2NetworkEvent(msg.getId()); - evt.setInventory(returnValue); - bus.publish(evt); + completion.success(returnValue); } @Override public void fail(ErrorCode errorCode) { - APICreateL2NetworkEvent evt = new APICreateL2NetworkEvent(msg.getId()); - evt.setError(errorCode); - bus.publish(evt); + completion.fail(errorCode); } }); diff --git a/network/src/main/java/org/zstack/network/l2/L2NoVlanL2NetworkFactory.java b/network/src/main/java/org/zstack/network/l2/L2NoVlanL2NetworkFactory.java index 3d5720ccd26..207c3ae7e13 100755 --- a/network/src/main/java/org/zstack/network/l2/L2NoVlanL2NetworkFactory.java +++ b/network/src/main/java/org/zstack/network/l2/L2NoVlanL2NetworkFactory.java @@ -43,6 +43,12 @@ public L2NetworkType getType() { @Override public void createL2Network(L2NetworkVO vo, APICreateL2NetworkMsg msg, ReturnValueCompletion completion) { + createL2Network(vo, msg, NetworkCreateContext.api(), completion); + } + + @Override + public void createL2Network(L2NetworkVO vo, APICreateL2NetworkMsg msg, NetworkCreateContext context, + ReturnValueCompletion completion) { FlowChain chain = new SimpleFlowChain(); chain.setName("create-no-vlan-network"); @@ -65,7 +71,7 @@ public void rollback(FlowRollback trigger, Map data) { @Override public void run(FlowTrigger trigger, Map data) { new While<>(pluginRgty.getExtensionList(L2NetworkCreateExtensionPoint.class)) - .each((exp, wcompl) -> exp.postCreateL2Network(L2NetworkInventory.valueOf(vo), msg, new Completion(trigger) { + .each((exp, wcompl) -> exp.postCreateL2Network(L2NetworkInventory.valueOf(vo), msg, context, new Completion(trigger) { @Override public void success() { wcompl.done(); diff --git a/network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java b/network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java index ec9a7a5be0b..4225837cc5b 100755 --- a/network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java +++ b/network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java @@ -5,6 +5,7 @@ import org.springframework.beans.factory.annotation.Configurable; import org.zstack.core.asyncbatch.While; import org.zstack.core.cascade.CascadeConstant; +import org.zstack.core.cascade.CascadeAction; import org.zstack.core.cascade.CascadeFacade; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.CloudBusCallBack; @@ -39,6 +40,9 @@ import org.zstack.header.network.l2.*; import org.zstack.header.network.l3.L3NetworkVO; import org.zstack.header.network.l3.L3NetworkVO_; +import org.zstack.header.network.NetworkConfigLocalContinuation; +import org.zstack.header.network.NetworkConfigMutation; +import org.zstack.header.network.NetworkConfigMutationExtensionPoint; import org.zstack.network.l3.ServiceTypeExtensionPoint; import org.zstack.utils.Utils; import org.zstack.utils.logging.CLogger; @@ -156,13 +160,15 @@ private void handle(DeleteL2NetworkMsg msg) { DeleteL2NetworkReply reply = new DeleteL2NetworkReply(); final String issuer = L2NetworkVO.class.getSimpleName(); final List ctx = L2NetworkInventory.valueOf(Arrays.asList(self)); + final CascadeAction cascadeAction = new CascadeAction().setRootIssuer(issuer) + .setRootIssuerContext(ctx).setParentIssuer(issuer).setParentIssuerContext(ctx); FlowChain chain = FlowChainBuilder.newSimpleFlowChain(); chain.setName(String.format("delete-l2Network-%s", msg.getL2NetworkUuid())); if (msg.isForceDelete() == false) { chain.then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_CHECK_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_CHECK_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -177,7 +183,7 @@ public void fail(ErrorCode errorCode) { }).then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_DELETE_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_DELETE_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -194,7 +200,7 @@ public void fail(ErrorCode errorCode) { chain.then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_FORCE_DELETE_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -352,12 +358,29 @@ private void handle(L2NetworkDeletionMsg msg) { @Override public void run(FlowTrigger trigger, Map data) { new While<>(pluginRgty.getExtensionList(L2NetworkDeleteExtensionPoint.class)) - .each((exp, wcompl) -> exp.deleteL2Network(inv, new NoErrorCompletion(trigger) { - @Override - public void done() { - wcompl.done(); + .each((exp, wcompl) -> { + if (exp.requiresConfirmedDelete(inv)) { + exp.deleteL2Network(inv, msg.getNetworkDeletionContext(), new Completion(trigger) { + @Override + public void success() { + wcompl.done(); + } + + @Override + public void fail(ErrorCode errorCode) { + wcompl.addError(errorCode); + wcompl.allDone(); + } + }); + return; } - })).run(new WhileDoneCompletion(trigger) { + exp.deleteL2Network(inv, new NoErrorCompletion(trigger) { + @Override + public void done() { + wcompl.done(); + } + }); + }).run(new WhileDoneCompletion(trigger) { @Override public void done(ErrorCodeList errorCodeList) { if (errorCodeList.getCauses().isEmpty()) { @@ -443,12 +466,39 @@ public String getSyncSignature() { @Override public void run(SyncTaskChain chain) { - extpEmitter.beforeUpdate(getSelfInventory()); - changeL2NetworkVlanId(msg, new Completion(chain) { + L2NetworkInventory target = getSelfInventory(); + target.setType(msg.getType()); + target.setVirtualNetworkId(msg.getVlan() == null ? 0 : msg.getVlan()); + NetworkConfigMutation mutation = NetworkConfigMutation.encapsulation( + msg.getL2NetworkUuid(), NetworkOperationOrigin.API, msg.getId(), + msg.getSession() == null ? null : msg.getSession().getAccountUuid(), + target.getType(), target.getVirtualNetworkId()); + mutateNetworkConfig(mutation, localCompletion -> { + try { + extpEmitter.beforeUpdate(target); + } catch (OperationFailureException e) { + localCompletion.fail(e.getErrorCode()); + return; + } catch (Exception e) { + localCompletion.fail(errf.throwableToInternalError(e)); + return; + } + changeL2NetworkVlanId(msg, new Completion(localCompletion) { + @Override + public void success() { + changeL2NetworkVlanIdInDb(msg); + extpEmitter.afterUpdate(getSelfInventory()); + localCompletion.success(); + } + + @Override + public void fail(ErrorCode errorCode) { + localCompletion.fail(errorCode); + } + }); + }, new Completion(chain) { @Override public void success() { - changeL2NetworkVlanIdInDb(msg); - extpEmitter.afterUpdate(getSelfInventory()); event.setInventory(getSelfInventory()); bus.publish(event); chain.next(); @@ -470,6 +520,26 @@ public String getName() { }); } + protected void mutateNetworkConfig(NetworkConfigMutation mutation, + NetworkConfigLocalContinuation continuation, + Completion completion) { + List providers = pluginRgty + .getExtensionList(NetworkConfigMutationExtensionPoint.class).stream() + .filter(provider -> provider.supports(mutation)) + .collect(Collectors.toList()); + if (providers.isEmpty()) { + continuation.run(completion); + return; + } + if (providers.size() != 1) { + completion.fail(errf.stringToInternalError(String.format( + "network mutation[%s] matched[%s] providers", + mutation.getKind(), providers.size()))); + return; + } + providers.get(0).mutate(mutation, continuation, completion); + } + private void handle(APIUpdateL2NetworkMsg msg) { boolean update = false; if (msg.getName() != null) { @@ -856,13 +926,15 @@ private void handle(APIDeleteL2NetworkMsg msg) { final APIDeleteL2NetworkEvent evt = new APIDeleteL2NetworkEvent(msg.getId()); final String issuer = L2NetworkVO.class.getSimpleName(); final List ctx = L2NetworkInventory.valueOf(Arrays.asList(self)); + final CascadeAction cascadeAction = new CascadeAction().setRootIssuer(issuer) + .setRootIssuerContext(ctx).setParentIssuer(issuer).setParentIssuerContext(ctx); FlowChain chain = FlowChainBuilder.newSimpleFlowChain(); chain.setName(String.format("delete-l2Network-%s", msg.getL2NetworkUuid())); if (msg.getDeletionMode() == APIDeleteMessage.DeletionMode.Permissive) { chain.then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_CHECK_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_CHECK_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -877,7 +949,7 @@ public void fail(ErrorCode errorCode) { }).then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_DELETE_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_DELETE_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -894,7 +966,7 @@ public void fail(ErrorCode errorCode) { chain.then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_FORCE_DELETE_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -1039,11 +1111,24 @@ protected void scripts() { prepareL2NetworkOnHosts(hvinvs, msg.getL2ProviderType(), new Completion(msg,completion) { @Override public void success() { - L2NetworkClusterRefVO rvo = new L2NetworkClusterRefVO(); - rvo.setClusterUuid(msg.getClusterUuid()); - rvo.setL2NetworkUuid(self.getUuid()); - rvo.setL2ProviderType(msg.getL2ProviderType()); - dbf.persist(rvo); + try { + new SQLBatch() { + @Override + protected void scripts() { + L2NetworkClusterRefVO rvo = new L2NetworkClusterRefVO(); + rvo.setClusterUuid(msg.getClusterUuid()); + rvo.setL2NetworkUuid(self.getUuid()); + rvo.setL2ProviderType(msg.getL2ProviderType()); + persist(rvo); + } + }.execute(); + } catch (OperationFailureException e) { + completion.fail(e.getErrorCode()); + return; + } catch (Exception e) { + completion.fail(errf.throwableToInternalError(e)); + return; + } logger.debug(String.format("successfully attached L2Network[uuid:%s] to cluster [uuid:%s]", self.getUuid(), msg.getClusterUuid())); self = dbf.findByUuid(self.getUuid(), L2NetworkVO.class); completion.success(); diff --git a/network/src/main/java/org/zstack/network/l2/L2VlanNetworkFactory.java b/network/src/main/java/org/zstack/network/l2/L2VlanNetworkFactory.java index 35c34f1799a..6112ccba530 100755 --- a/network/src/main/java/org/zstack/network/l2/L2VlanNetworkFactory.java +++ b/network/src/main/java/org/zstack/network/l2/L2VlanNetworkFactory.java @@ -51,6 +51,12 @@ public L2NetworkType getType() { @Override public void createL2Network(L2NetworkVO ovo, APICreateL2NetworkMsg msg, ReturnValueCompletion completion) { + createL2Network(ovo, msg, NetworkCreateContext.api(), completion); + } + + @Override + public void createL2Network(L2NetworkVO ovo, APICreateL2NetworkMsg msg, NetworkCreateContext context, + ReturnValueCompletion completion) { FlowChain chain = new SimpleFlowChain(); chain.setName("create-no-vlan-network"); chain.then(new Flow() { @@ -78,7 +84,7 @@ public void run(FlowTrigger trigger, Map data) { L2VlanNetworkInventory inv = L2VlanNetworkInventory.valueOf( dbf.findByUuid(ovo.getUuid(), L2VlanNetworkVO.class)); new While<>(pluginRgty.getExtensionList(L2NetworkCreateExtensionPoint.class)) - .each((exp, wcompl) -> exp.postCreateL2Network(inv, msg, new Completion(trigger) { + .each((exp, wcompl) -> exp.postCreateL2Network(inv, msg, context, new Completion(trigger) { @Override public void success() { wcompl.done(); diff --git a/network/src/main/java/org/zstack/network/l2/NetworkDeletionContexts.java b/network/src/main/java/org/zstack/network/l2/NetworkDeletionContexts.java new file mode 100644 index 00000000000..f2dc5c081bc --- /dev/null +++ b/network/src/main/java/org/zstack/network/l2/NetworkDeletionContexts.java @@ -0,0 +1,19 @@ +package org.zstack.network.l2; + +import org.zstack.core.cascade.CascadeAction; +import org.zstack.header.network.l2.NetworkDeletionContext; + +public final class NetworkDeletionContexts { + private static final String KEY_PREFIX = NetworkDeletionContext.class.getName() + ":"; + + private NetworkDeletionContexts() { + } + + public static NetworkDeletionContext get(CascadeAction action, String l2NetworkUuid) { + return action.getContext(KEY_PREFIX + l2NetworkUuid); + } + + public static void put(CascadeAction action, NetworkDeletionContext context) { + action.putContext(KEY_PREFIX + context.getL2NetworkUuid(), context); + } +} diff --git a/network/src/main/java/org/zstack/network/l3/AbstractIpAllocatorStrategy.java b/network/src/main/java/org/zstack/network/l3/AbstractIpAllocatorStrategy.java index 490408c5c93..a94e657d056 100755 --- a/network/src/main/java/org/zstack/network/l3/AbstractIpAllocatorStrategy.java +++ b/network/src/main/java/org/zstack/network/l3/AbstractIpAllocatorStrategy.java @@ -107,7 +107,8 @@ public IpRangeVO call(IpRangeVO arg) { )); } - return l3NwMgr.reserveIp(ipr, msg.getRequiredIp(), msg.isDuplicatedIpAllowed()); + return l3NwMgr.reserveIp(ipr, msg.getRequiredIp(), msg.isDuplicatedIpAllowed(), + msg.getOperationUuid(), msg.getOperationStep()); } protected UsedIpInventory allocateRequiredIpv6(IpAllocateMessage msg) { @@ -138,7 +139,8 @@ public IpRangeVO call(IpRangeVO arg) { )); } - return l3NwMgr.reserveIp(ipr, msg.getRequiredIp(), msg.isDuplicatedIpAllowed()); + return l3NwMgr.reserveIp(ipr, msg.getRequiredIp(), msg.isDuplicatedIpAllowed(), + msg.getOperationUuid(), msg.getOperationStep()); } public static int compareIpv4Range(IpRangeVO r1, IpRangeVO r2) { diff --git a/network/src/main/java/org/zstack/network/l3/AddressPoolIpRangeFactory.java b/network/src/main/java/org/zstack/network/l3/AddressPoolIpRangeFactory.java index aae2711248b..77edf7d3e67 100644 --- a/network/src/main/java/org/zstack/network/l3/AddressPoolIpRangeFactory.java +++ b/network/src/main/java/org/zstack/network/l3/AddressPoolIpRangeFactory.java @@ -8,6 +8,7 @@ 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; @@ -27,6 +28,12 @@ public IpRangeType getType() { @Override public void createIpRange(List iprs, APICreateMessage msg, ReturnValueCompletion> completion) { + createIpRange(iprs, msg, NetworkCreateContext.api(), completion); + } + + @Override + public void createIpRange(List iprs, APICreateMessage msg, NetworkCreateContext context, + ReturnValueCompletion> completion) { List vos = new ArrayList<>(); for (IpRangeInventory ipr : iprs) { AddressPoolVO vo = new SQLBatchWithReturn() { @@ -42,7 +49,8 @@ protected AddressPoolVO scripts() { vo.setNetmask(ipr.getNetmask()); vo.setStartIp(ipr.getStartIp()); vo.setNetworkCidr(ipr.getNetworkCidr()); - vo.setAccountUuid(msg.getSession().getAccountUuid()); + vo.setAccountUuid(msg.getSession() == null && context.getExternalRef() != null + ? context.getExternalRef().getAccountUuid() : msg.getSession().getAccountUuid()); vo.setIpVersion(ipr.getIpVersion()); vo.setAddressMode(ipr.getAddressMode()); vo.setPrefixLen(ipr.getPrefixLen()); @@ -60,7 +68,7 @@ protected AddressPoolVO scripts() { CollectionUtils.safeForEach(pluginRgty.getExtensionList(AfterAddIpRangeExtensionPoint.class), new ForEachFunction() { @Override public void run(AfterAddIpRangeExtensionPoint ext) { - ext.afterAddIpRange(finalIpr, msg.getSystemTags()); + ext.afterAddIpRange(finalIpr, msg.getSystemTags(), context); } }); } diff --git a/network/src/main/java/org/zstack/network/l3/AscDelayRecycleIpAllocatorStrategy.java b/network/src/main/java/org/zstack/network/l3/AscDelayRecycleIpAllocatorStrategy.java index 47337b221e3..71ca46140d3 100755 --- a/network/src/main/java/org/zstack/network/l3/AscDelayRecycleIpAllocatorStrategy.java +++ b/network/src/main/java/org/zstack/network/l3/AscDelayRecycleIpAllocatorStrategy.java @@ -148,7 +148,8 @@ private UsedIpInventory allocateIpByAsc(IpAllocateMessage msg, List r ip = allocateIpByAsc(r, excludeIp); tr = r; if (ip != null) { - UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed()); + UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed(), + msg.getOperationUuid(), msg.getOperationStep()); if (inv != null) { return inv; } diff --git a/network/src/main/java/org/zstack/network/l3/AttachNetworkServiceToL3Msg.java b/network/src/main/java/org/zstack/network/l3/AttachNetworkServiceToL3Msg.java index 24a40b3faf2..e344716d358 100644 --- a/network/src/main/java/org/zstack/network/l3/AttachNetworkServiceToL3Msg.java +++ b/network/src/main/java/org/zstack/network/l3/AttachNetworkServiceToL3Msg.java @@ -1,6 +1,7 @@ package org.zstack.network.l3; import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.network.l2.NetworkCreateContext; import org.zstack.header.network.l3.L3NetworkMessage; import java.util.ArrayList; @@ -16,6 +17,7 @@ public class AttachNetworkServiceToL3Msg extends NeedReplyMessage implements L3N private Map> networkServices; private List apiSystemTags = new ArrayList<>(); + private NetworkCreateContext context; public void setL3NetworkUuid(String l3NetworkUuid) { this.l3NetworkUuid = l3NetworkUuid; @@ -41,4 +43,12 @@ public List getApiSystemTags() { public void setApiSystemTags(List apiSystemTags) { this.apiSystemTags = apiSystemTags; } + + public NetworkCreateContext getContext() { + return context; + } + + public void setContext(NetworkCreateContext context) { + this.context = context; + } } diff --git a/network/src/main/java/org/zstack/network/l3/FirstAvailableIpAllocatorStrategy.java b/network/src/main/java/org/zstack/network/l3/FirstAvailableIpAllocatorStrategy.java index 20a9cffc1fb..7207a229b11 100755 --- a/network/src/main/java/org/zstack/network/l3/FirstAvailableIpAllocatorStrategy.java +++ b/network/src/main/java/org/zstack/network/l3/FirstAvailableIpAllocatorStrategy.java @@ -66,7 +66,8 @@ public UsedIpInventory allocateIp(IpAllocateMessage msg) { return null; } - UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed()); + UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed(), + msg.getOperationUuid(), msg.getOperationStep()); if (inv != null) { return inv; } diff --git a/network/src/main/java/org/zstack/network/l3/FirstAvailableIpv6AllocatorStrategy.java b/network/src/main/java/org/zstack/network/l3/FirstAvailableIpv6AllocatorStrategy.java index 63723dc7073..71bd3b7c659 100755 --- a/network/src/main/java/org/zstack/network/l3/FirstAvailableIpv6AllocatorStrategy.java +++ b/network/src/main/java/org/zstack/network/l3/FirstAvailableIpv6AllocatorStrategy.java @@ -69,7 +69,8 @@ public UsedIpInventory allocateIp(IpAllocateMessage msg) { return null; } - UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed()); + UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed(), + msg.getOperationUuid(), msg.getOperationStep()); if (inv != null) { return inv; } diff --git a/network/src/main/java/org/zstack/network/l3/IpRangeCascadeExtension.java b/network/src/main/java/org/zstack/network/l3/IpRangeCascadeExtension.java index 115dea698ee..977e7659f47 100755 --- a/network/src/main/java/org/zstack/network/l3/IpRangeCascadeExtension.java +++ b/network/src/main/java/org/zstack/network/l3/IpRangeCascadeExtension.java @@ -16,6 +16,7 @@ import org.zstack.header.errorcode.ErrorCodeList; import org.zstack.header.message.MessageReply; import org.zstack.header.network.l3.*; +import org.zstack.network.l2.NetworkDeletionContexts; import org.zstack.utils.CollectionUtils; import org.zstack.utils.Utils; import org.zstack.utils.function.Function; @@ -62,6 +63,10 @@ private void deleteIpRanges(final CascadeAction action,List ip msg.setForceDelete(action.isActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE)); msg.setL3NetworkUuid(iprinv.getL3NetworkUuid()); msg.setIpRangeUuid(iprinv.getUuid()); + String l2NetworkUuid = Q.New(L3NetworkVO.class) + .select(L3NetworkVO_.l2NetworkUuid) + .eq(L3NetworkVO_.uuid, iprinv.getL3NetworkUuid()).findValue(); + msg.setNetworkDeletionContext(NetworkDeletionContexts.get(action, l2NetworkUuid)); bus.makeTargetServiceIdByResourceUuid(msg, L3NetworkConstant.SERVICE_ID, iprinv.getL3NetworkUuid()); msgs.add(msg); } diff --git a/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java b/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java index ae0258de955..6840d2e2867 100755 --- a/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java +++ b/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java @@ -30,6 +30,7 @@ import org.zstack.header.core.workflow.*; import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.errorcode.ErrorCodeList; +import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.host.HostVO; import org.zstack.header.host.HostVO_; import org.zstack.header.identity.SharedResourceVO; @@ -40,9 +41,16 @@ import org.zstack.header.network.l2.L2NetworkClusterRefVO_; import org.zstack.header.network.l2.L2NetworkConstant; import org.zstack.header.network.l2.L2NetworkVO; +import org.zstack.header.network.l2.NetworkCreateContext; +import org.zstack.header.network.NetworkConfigLocalContinuation; +import org.zstack.header.network.NetworkConfigMutation; +import org.zstack.header.network.NetworkConfigMutationExtensionPoint; +import org.zstack.header.network.l2.NetworkOperationOrigin; import org.zstack.header.network.l3.*; import org.zstack.header.network.sdncontroller.SdnControllerConstant; import org.zstack.header.network.service.*; +import org.zstack.header.tag.SystemTagVO; +import org.zstack.header.vm.VmNicVO; import org.zstack.identity.AccountManager; import org.zstack.network.service.NetworkServiceManager; import org.zstack.resourceconfig.ResourceConfigFacade; @@ -59,6 +67,7 @@ import org.zstack.utils.network.NetworkUtils; import org.zstack.utils.stopwatch.StopWatch; +import javax.persistence.LockModeType; import javax.persistence.Tuple; import java.math.BigInteger; import java.util.*; @@ -163,7 +172,27 @@ private void handle(APIAddIpRangeMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public void run(SyncTaskChain chain) { - doAddIpRange(msg, chain); + if (msg.getResourceUuid() == null) { + msg.setResourceUuid(Platform.getUuid()); + } + IpRangeInventory ipr = IpRangeInventory.fromMessage(msg); + addIpRange(ipr, msg, NetworkCreateContext.api(), new ReturnValueCompletion(msg) { + @Override + public void success(IpRangeInventory inv) { + APIAddIpRangeEvent evt = new APIAddIpRangeEvent(msg.getId()); + evt.setInventory(inv); + bus.publish(evt); + chain.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + APIAddIpRangeEvent evt = new APIAddIpRangeEvent(msg.getId()); + evt.setError(errorCode); + bus.publish(evt); + chain.next(); + } + }); } @Override @@ -178,38 +207,183 @@ public String getName() { }); } - private void doAddIpRange(APIAddIpRangeMsg msg, SyncTaskChain chain) { - IpRangeInventory ipr = IpRangeInventory.fromMessage(msg); - APIAddIpRangeEvent evt = new APIAddIpRangeEvent(msg.getId()); - ErrorCode errorCode = validateManagementNetworkIpRangeVersionBeforeCreate(ipr); - if (errorCode != null) { - evt.setError(errorCode); - bus.publish(evt); - chain.next(); + private void handle(AddIpRangeMsg msg) { + thdf.chainSubmit(new ChainTask(msg) { + @Override + public void run(SyncTaskChain chain) { + IpRangeInventory ipr = msg.getInventory(); + if (ipr == null || !Objects.equals(msg.getL3NetworkUuid(), ipr.getL3NetworkUuid())) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L3_10083, + "internal IP range must belong to L3 network[uuid:%s]", msg.getL3NetworkUuid())); + chain.next(); + return; + } + + APIAddIpRangeMsg create = new APIAddIpRangeMsg(); + create.setL3NetworkUuid(msg.getL3NetworkUuid()); + create.setName(ipr.getName()); + create.setDescription(ipr.getDescription()); + create.setStartIp(ipr.getStartIp()); + create.setEndIp(ipr.getEndIp()); + create.setNetmask(ipr.getNetmask()); + create.setGateway(ipr.getGateway()); + create.setIpRangeType(ipr.getIpRangeType().toString()); + create.setResourceUuid(ipr.getUuid()); + create.setSystemTags(msg.getSystemTags()); + create.setUserTags(msg.getUserTags()); + addIpRange(ipr, create, msg.getContext(), new ReturnValueCompletion(msg) { + @Override + public void success(IpRangeInventory inv) { + AddIpRangeReply reply = new AddIpRangeReply(); + reply.setInventory(inv); + bus.reply(msg, reply); + chain.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + bus.replyErrorByMessageType(msg, errorCode); + chain.next(); + } + }); + } + + @Override + public String getSyncSignature() { + return syncThreadName; + } + + @Override + public String getName() { + return "add-projected-ip-range"; + } + }); + } + + private void addIpRange(IpRangeInventory ipr, APICreateMessage msg, NetworkCreateContext context, + ReturnValueCompletion completion) { + addIpRanges(Collections.singletonList(ipr), msg, context, + new ReturnValueCompletion>(completion) { + @Override + public void success(List inventories) { + completion.success(inventories.get(0)); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + }); + } + + private void addIpRanges(List iprs, APICreateMessage msg, + NetworkCreateContext context, + ReturnValueCompletion> completion) { + if (context == null) { + completion.fail(Platform.inerr(ORG_ZSTACK_NETWORK_L3_10084, + "network create context is required for internal IP range creation")); return; } + for (IpRangeInventory ipr : iprs) { + ErrorCode errorCode = validateManagementNetworkIpRangeVersionBeforeCreate(ipr); + if (errorCode != null) { + completion.fail(errorCode); + return; + } + } - IpRangeFactory factory = l3NwMgr.getIpRangeFactory(ipr.getIpRangeType()); - factory.createIpRange(Collections.singletonList(ipr), msg, new ReturnValueCompletion>(msg) { + if (context.getOrigin() == NetworkOperationOrigin.API + && iprs.stream().allMatch(ipr -> ipr.getIpRangeType() == IpRangeType.Normal)) { + IpRangeInventory first = iprs.get(0); + List existingRanges = Q.New(NormalIpRangeVO.class) + .eq(IpRangeVO_.l3NetworkUuid, first.getL3NetworkUuid()) + .eq(IpRangeVO_.ipVersion, first.getIpVersion()) + .list(); + List ranges = existingRanges.stream() + .map(existing -> new NetworkConfigMutation.IpRangeTarget( + existing.getUuid(), existing.getStartIp(), existing.getEndIp())) + .collect(Collectors.toList()); + iprs.forEach(ipr -> ranges.add(new NetworkConfigMutation.IpRangeTarget( + ipr.getUuid(), ipr.getStartIp(), ipr.getEndIp()))); + int prefix = first.getIpVersion() == IPv6Constants.IPv6 + ? first.getPrefixLen() + : NetworkUtils.getPrefixLengthFromNetmask(first.getNetmask()); + NetworkConfigMutation mutation = NetworkConfigMutation.ipam( + self.getL2NetworkUuid(), context.getOrigin(), msg.getId(), + msg.getSession() == null ? null : msg.getSession().getAccountUuid(), + first.getL3NetworkUuid(), + first.getIpVersion(), first.getGateway() + "/" + prefix, ranges); + java.util.concurrent.atomic.AtomicReference> created = + new java.util.concurrent.atomic.AtomicReference<>(); + if (mutateNetworkConfig(mutation, + localCompletion -> addIpRanges(iprs, msg, + NetworkCreateContext.cloudCommit(msg.getId()), + new ReturnValueCompletion>(localCompletion) { + @Override + public void success(List inventories) { + created.set(inventories); + localCompletion.success(); + } + + @Override + public void fail(ErrorCode errorCode) { + localCompletion.fail(errorCode); + } + }), created, completion)) { + return; + } + } + + IpRangeFactory factory = l3NwMgr.getIpRangeFactory(iprs.get(0).getIpRangeType()); + factory.createIpRange(iprs, msg, context, new ReturnValueCompletion>(completion) { @Override public void success(List invs) { - IpRangeInventory inv = invs.get(0); - tagMgr.createTagsFromAPICreateMessage(msg, inv.getL3NetworkUuid(), L3NetworkVO.class.getSimpleName()); - setIpRangeSharedResource(msg.getL3NetworkUuid(), inv.getUuid()); - evt.setInventory(inv); - bus.publish(evt); - chain.next(); + for (IpRangeInventory inv : invs) { + tagMgr.createTagsFromAPICreateMessage( + msg, inv.getL3NetworkUuid(), L3NetworkVO.class.getSimpleName()); + setIpRangeSharedResource(inv.getL3NetworkUuid(), inv.getUuid()); + } + completion.success(invs); } @Override public void fail(ErrorCode errorCode) { - evt.setError(errorCode); - bus.publish(evt); - chain.next(); + completion.fail(errorCode); } }); } + private boolean mutateNetworkConfig(NetworkConfigMutation mutation, + NetworkConfigLocalContinuation continuation, + java.util.concurrent.atomic.AtomicReference result, + ReturnValueCompletion completion) { + List providers = pluginRgty + .getExtensionList(NetworkConfigMutationExtensionPoint.class).stream() + .filter(provider -> provider.supports(mutation)) + .collect(Collectors.toList()); + if (providers.isEmpty()) { + return false; + } + if (providers.size() != 1) { + completion.fail(errf.stringToInternalError(String.format( + "network mutation[%s] matched[%s] providers", + mutation.getKind(), providers.size()))); + return true; + } + providers.get(0).mutate(mutation, continuation, new Completion(completion) { + @Override + public void success() { + completion.success(result.get()); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + }); + return true; + } + private ErrorCode validateManagementNetworkIpRangeVersionBeforeCreate(IpRangeInventory ipr) { if (self.getCategory() != L3NetworkCategory.System) { return null; @@ -234,7 +408,17 @@ private String getIpVersionName(int ipVersion) { } private void handleLocalMessage(Message msg) { - if (msg instanceof AllocateIpMsg) { + if (msg instanceof AddIpRangeMsg) { + handle((AddIpRangeMsg) msg); + } else if (msg instanceof UpdateProjectedIpRangeMsg) { + handle((UpdateProjectedIpRangeMsg) msg); + } else if (msg instanceof DeleteProjectedIpRangeMsg) { + handle((DeleteProjectedIpRangeMsg) msg); + } else if (msg instanceof UpdateProjectedDnsMsg) { + handle((UpdateProjectedDnsMsg) msg); + } else if (msg instanceof ConvertL3NetworkTypeMsg) { + handle((ConvertL3NetworkTypeMsg) msg); + } else if (msg instanceof AllocateIpMsg) { handle((AllocateIpMsg)msg); } else if (msg instanceof ReturnIpMsg) { handle((ReturnIpMsg)msg); @@ -251,6 +435,269 @@ private void handleLocalMessage(Message msg) { } } + private void handle(ConvertL3NetworkTypeMsg msg) { + thdf.chainSubmit(new ChainTask(msg) { + @Override + public void run(SyncTaskChain chain) { + L3NetworkCategory targetCategory; + try { + targetCategory = L3NetworkCategory.valueOf(msg.getTargetCategory()); + } catch (RuntimeException e) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L3_10090, + "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_10090, + "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_10090, + "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_10091, failure)); + chain.next(); + return; + } + + self = dbf.findByUuid(msg.getL3NetworkUuid(), L3NetworkVO.class); + bus.reply(msg, new MessageReply()); + chain.next(); + } + + @Override + public String getSyncSignature() { + return syncThreadName; + } + + @Override + public String getName() { + return String.format("convert-projected-l3-network-%s", msg.getL3NetworkUuid()); + } + }); + } + + private void handle(UpdateProjectedIpRangeMsg msg) { + thdf.chainSubmit(new ChainTask(msg) { + @Override + public void run(SyncTaskChain chain) { + IpRangeVO range = dbf.findByUuid(msg.getRangeUuid(), IpRangeVO.class); + if (msg.getContext() == null || !msg.getContext().isProjection() + || !IpRangeVO.class.getSimpleName().equals(msg.getExpectedSourceType()) + || !Objects.equals(self.getUuid(), msg.getL3NetworkUuid()) + || range == null || !Objects.equals(range.getL3NetworkUuid(), msg.getL3NetworkUuid())) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L3_10087, + "IP range projection update requires the expected range on L3 network[uuid:%s]", + msg.getL3NetworkUuid())); + chain.next(); + return; + } + + boolean valid; + try { + valid = NetworkUtils.isInRange(msg.getStartIp(), msg.getStartIp(), msg.getEndIp()); + } catch (RuntimeException e) { + valid = false; + } + if (!valid) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L3_10088, + "projected IP range[%s, %s] is invalid", msg.getStartIp(), msg.getEndIp())); + chain.next(); + return; + } + + List rangeUsedIps = Q.New(UsedIpVO.class) + .eq(UsedIpVO_.ipRangeUuid, range.getUuid()).list(); + UsedIpVO outside = rangeUsedIps.stream() + .filter(ip -> !NetworkUtils.isInRange(ip.getIp(), msg.getStartIp(), msg.getEndIp())) + .findFirst().orElse(null); + if (outside != null) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L3_10089, + "used IP[%s] is outside projected IP range[uuid:%s]", + outside.getIp(), range.getUuid())); + chain.next(); + return; + } + + range.setStartIp(msg.getStartIp()); + range.setEndIp(msg.getEndIp()); + range.setGateway(msg.getGateway()); + range.setNetmask(msg.getNetmask()); + dbf.update(range); + bus.reply(msg, new MessageReply()); + chain.next(); + } + + @Override + public String getSyncSignature() { + return syncThreadName; + } + + @Override + public String getName() { + return "update-projected-ip-range"; + } + }); + } + + private void handle(DeleteProjectedIpRangeMsg msg) { + thdf.chainSubmit(new ChainTask(msg) { + @Override + public void run(SyncTaskChain chain) { + if (msg.getContext() == null || !msg.getContext().isProjection() + || !IpRangeVO.class.getSimpleName().equals(msg.getExpectedSourceType()) + || !Objects.equals(self.getUuid(), msg.getL3NetworkUuid())) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L3_10090, + "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_10090, + "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_10091, + "used IP[%s] prevents deleting projected IP range[uuid:%s]", + usedIp.getIp(), range.getUuid())); + chain.next(); + return; + } + replacements.put(usedIp, replacement); + } + + new SQLBatch() { + @Override + protected void scripts() { + replacements.forEach((usedIp, replacement) -> + usedIp.setIpRangeUuid(replacement.getUuid())); + if (!usedIps.isEmpty()) { + dbf.updateCollection(usedIps); + } + dbf.remove(range); + IpRangeHelper.updateL3NetworkIpversion(range); + } + }.execute(); + bus.reply(msg, new MessageReply()); + chain.next(); + } + + @Override + public String getSyncSignature() { + return syncThreadName; + } + + @Override + public String getName() { + return "delete-projected-ip-range"; + } + }); + } + private void handle(CheckIpAvailabilityMsg msg) { CheckIpAvailabilityReply reply = checkIpAvailability(msg); bus.reply(msg, reply); @@ -296,13 +743,13 @@ public void run(SyncTaskChain chainTask) { final IpRangeInventory inv = IpRangeInventory.valueOf(iprvo); for (IpRangeDeletionExtensionPoint ext : exts) { - ext.preDeleteIpRange(inv); + ext.preDeleteIpRange(inv, msg.getNetworkDeletionContext()); } CollectionUtils.safeForEach(exts, new ForEachFunction() { @Override public void run(IpRangeDeletionExtensionPoint arg) { - arg.beforeDeleteIpRange(inv); + arg.beforeDeleteIpRange(inv, msg.getNetworkDeletionContext()); } }); @@ -319,13 +766,14 @@ public void run(FlowTrigger trigger, Map data) { return; } - SdnControllerDisableDHCPMsg msg = new SdnControllerDisableDHCPMsg(); - msg.setL3NetworkUuid(self.getUuid()); - msg.setIpVersion(iprvo.getIpVersion()); - msg.setSdnControllerUuid(sdnControllerUuid); - msg.setCheckIpRange(true); - bus.makeTargetServiceIdByResourceUuid(msg, SdnControllerConstant.SERVICE_ID, sdnControllerUuid); - bus.send(msg, new CloudBusCallBack(trigger) { + SdnControllerDisableDHCPMsg dhcpMsg = new SdnControllerDisableDHCPMsg(); + dhcpMsg.setL3NetworkUuid(self.getUuid()); + dhcpMsg.setIpVersion(iprvo.getIpVersion()); + dhcpMsg.setSdnControllerUuid(sdnControllerUuid); + dhcpMsg.setCheckIpRange(true); + dhcpMsg.setNetworkDeletionContext(msg.getNetworkDeletionContext()); + bus.makeTargetServiceIdByResourceUuid(dhcpMsg, SdnControllerConstant.SERVICE_ID, sdnControllerUuid); + bus.send(dhcpMsg, new CloudBusCallBack(trigger) { @Override public void run(MessageReply reply) { if (!reply.isSuccess()) { @@ -348,23 +796,18 @@ public void run(FlowTrigger trigger, Map data) { } boolean isLastIpRange = isLastNormalIpRangeOfVersion(msg.getIpRangeUuid(), inv.getIpVersion()); - if (isLastIpRange) { - sdnL3.deleteIpRange(inv, new Completion(trigger) { - @Override - public void success() { - trigger.next(); - } + sdnL3.deleteIpRange(inv, isLastIpRange, msg.getNetworkDeletionContext(), + new Completion(trigger) { + @Override + public void success() { + trigger.next(); + } - @Override - public void fail(ErrorCode errorCode) { - logger.debug(String.format("delete ip range [uuid:%s] failed because %s", - inv.getUuid(), errorCode.getDetails())); - trigger.next(); - } - }); - } else { - trigger.next(); - } + @Override + public void fail(ErrorCode errorCode) { + trigger.fail(errorCode); + } + }); } }).then(new NoRollbackFlow() { String __name__ = "remove-db"; @@ -378,7 +821,7 @@ public void run(FlowTrigger trigger, Map data) { CollectionUtils.safeForEach(exts, new ForEachFunction() { @Override public void run(IpRangeDeletionExtensionPoint arg) { - arg.afterDeleteIpRange(inv); + arg.afterDeleteIpRange(inv, msg.getNetworkDeletionContext()); } }); trigger.next(); @@ -390,7 +833,7 @@ public void run(IpRangeDeletionExtensionPoint arg) { public void run (FlowTrigger trigger, Map data){ CollectionUtils.safeForEach( pluginRgty.getExtensionList(AfterDeleteIpRangeExtensionPoint.class), - ext -> ext.afterDeleteIpRange(inv) + ext -> ext.afterDeleteIpRange(inv, msg.getNetworkDeletionContext()) ); trigger.next(); } @@ -445,7 +888,8 @@ public void run(FlowTrigger trigger, Map data) { return; } - controllerL3.deleteL3Network(L3NetworkInventory.valueOf(l3NetworkVO), new Completion(trigger) { + controllerL3.deleteL3Network(L3NetworkInventory.valueOf(l3NetworkVO), + msg.getNetworkDeletionContext(), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -453,7 +897,12 @@ public void success() { @Override public void fail(ErrorCode errorCode) { - trigger.next();//ignore error + if (msg.getNetworkDeletionContext() != null && + msg.getNetworkDeletionContext().isWholeL2SegmentDelete()) { + trigger.fail(errorCode); + } else { + trigger.next(); + } } }); } @@ -487,9 +936,9 @@ public void run(FlowTrigger trigger, Map data) { } L3NetworkInventory inv = L3NetworkInventory.valueOf(self); - extpEmitter.beforeDelete(inv); + extpEmitter.beforeDelete(inv, msg.getNetworkDeletionContext()); deleteHook(); - extpEmitter.afterDelete(inv); + extpEmitter.afterDelete(inv, msg.getNetworkDeletionContext()); trigger.next(); } @@ -1077,22 +1526,61 @@ private void handle(APIDetachNetworkServiceFromL3NetworkMsg msg) { return; } - detachNetworkServiceFromL3NetworkMsg(l3VO, refs, new Completion(msg) { + ReturnValueCompletion completion = new ReturnValueCompletion(msg) { + @Override + public void success(L3NetworkInventory inventory) { + evt.setInventory(inventory); + bus.publish(evt); + } + + @Override + public void fail(ErrorCode errorCode) { + evt.setError(errorCode); + bus.publish(evt); + } + }; + if (refs.stream().anyMatch(ref -> NetworkServiceType.DHCP.toString() + .equals(ref.getNetworkServiceType()))) { + NetworkConfigMutation mutation = NetworkConfigMutation.dhcp( + self.getL2NetworkUuid(), NetworkOperationOrigin.API, msg.getId(), + msg.getSession() == null ? null : msg.getSession().getAccountUuid(), + msg.getL3NetworkUuid(), false, msg.getSystemTags()); + java.util.concurrent.atomic.AtomicReference result = + new java.util.concurrent.atomic.AtomicReference<>(); + if (mutateNetworkConfig(mutation, + localCompletion -> detachNetworkServicesLocally(l3VO, refs, + new ReturnValueCompletion(localCompletion) { + @Override + public void success(L3NetworkInventory inventory) { + result.set(inventory); + localCompletion.success(); + } + + @Override + public void fail(ErrorCode errorCode) { + localCompletion.fail(errorCode); + } + }), result, completion)) { + return; + } + } + detachNetworkServicesLocally(l3VO, refs, completion); + } + + private void detachNetworkServicesLocally(L3NetworkVO l3VO, + List refs, + ReturnValueCompletion completion) { + detachNetworkServiceFromL3NetworkMsg(l3VO, refs, new Completion(completion) { @Override public void success() { dbf.removeCollection(refs, NetworkServiceL3NetworkRefVO.class); - logger.debug(String.format("successfully detached network service provider[uuid:%s]", JSONObjectUtil.dumpPretty(refs))); self = dbf.reload(self); - evt.setInventory(L3NetworkInventory.valueOf(self)); - bus.publish(evt); + completion.success(L3NetworkInventory.valueOf(self)); } @Override public void fail(ErrorCode errorCode) { - logger.debug(String.format("detached network service provider[uuid:%s] failed:%s", JSONObjectUtil.dumpPretty(refs), errorCode.getDetails())); - self = dbf.reload(self); - evt.setInventory(L3NetworkInventory.valueOf(self)); - bus.publish(evt); + completion.fail(errorCode); } }); } @@ -1224,15 +1712,13 @@ private void handle(APIUpdateL3NetworkMsg msg) { private void handle(APIAddIpRangeByNetworkCidrMsg msg) { List iprs = IpRangeInventory.fromMessage(msg); + assignStableIpRangeUuids(iprs, msg.getResourceUuid()); APIAddIpRangeByNetworkCidrEvent evt = new APIAddIpRangeByNetworkCidrEvent(msg.getId()); - IpRangeFactory factory = l3NwMgr.getIpRangeFactory(iprs.get(0).getIpRangeType()); - factory.createIpRange(iprs, msg, new ReturnValueCompletion>(msg) { + addIpRanges(iprs, msg, NetworkCreateContext.api(), new ReturnValueCompletion>(msg) { @Override public void success(List invs) { IpRangeInventory inv = invs.get(0); - tagMgr.createTagsFromAPICreateMessage(msg, inv.getL3NetworkUuid(), L3NetworkVO.class.getSimpleName()); - setIpRangeSharedResource(msg.getL3NetworkUuid(), inv.getUuid()); evt.setInventory(inv); evt.setInventories(invs); bus.publish(evt); @@ -1248,15 +1734,15 @@ public void fail(ErrorCode errorCode) { private void handle(APIAddIpv6RangeByNetworkCidrMsg msg) { IpRangeInventory ipr = IpRangeInventory.fromMessage(msg); + if (ipr.getUuid() == null) { + ipr.setUuid(Platform.getUuid()); + } APIAddIpRangeByNetworkCidrEvent evt = new APIAddIpRangeByNetworkCidrEvent(msg.getId()); - IpRangeFactory factory = l3NwMgr.getIpRangeFactory(ipr.getIpRangeType()); - factory.createIpRange(Collections.singletonList(ipr), msg, new ReturnValueCompletion>(msg) { + addIpRanges(Collections.singletonList(ipr), msg, NetworkCreateContext.api(), new ReturnValueCompletion>(msg) { @Override public void success(List invs) { IpRangeInventory inv = invs.get(0); - tagMgr.createTagsFromAPICreateMessage(msg, inv.getL3NetworkUuid(), L3NetworkVO.class.getSimpleName()); - setIpRangeSharedResource(msg.getL3NetworkUuid(), inv.getUuid()); evt.setInventory(inv); bus.publish(evt); } @@ -1271,15 +1757,15 @@ public void fail(ErrorCode errorCode) { private void handle(APIAddIpv6RangeMsg msg) { IpRangeInventory ipr = IpRangeInventory.fromMessage(msg); + if (ipr.getUuid() == null) { + ipr.setUuid(Platform.getUuid()); + } APIAddIpRangeEvent evt = new APIAddIpRangeEvent(msg.getId()); - IpRangeFactory factory = l3NwMgr.getIpRangeFactory(ipr.getIpRangeType()); - factory.createIpRange(Collections.singletonList(ipr), msg, new ReturnValueCompletion>(msg) { + addIpRanges(Collections.singletonList(ipr), msg, NetworkCreateContext.api(), new ReturnValueCompletion>(msg) { @Override public void success(List invs) { IpRangeInventory inv = invs.get(0); - tagMgr.createTagsFromAPICreateMessage(msg, inv.getL3NetworkUuid(), L3NetworkVO.class.getSimpleName()); - setIpRangeSharedResource(msg.getL3NetworkUuid(), inv.getUuid()); evt.setInventory(inv); bus.publish(evt); } @@ -1292,6 +1778,16 @@ public void fail(ErrorCode errorCode) { }); } + private static void assignStableIpRangeUuids(List iprs, + String requestedUuid) { + for (int i = 0; i < iprs.size(); i++) { + if (iprs.get(i).getUuid() == null) { + iprs.get(i).setUuid(i == 0 && requestedUuid != null + ? requestedUuid : Platform.getUuid()); + } + } + } + private void handle(APIChangeL3NetworkStateMsg msg) { if (L3NetworkStateEvent.enable.toString().equals(msg.getStateEvent())) { @@ -1311,6 +1807,52 @@ private void handle(APIChangeL3NetworkStateMsg msg) { private void handle(final APIRemoveDnsFromL3NetworkMsg msg) { final APIRemoveDnsFromL3NetworkEvent evt = new APIRemoveDnsFromL3NetworkEvent(msg.getId()); + ReturnValueCompletion completion = new ReturnValueCompletion(msg) { + @Override + public void success(L3NetworkInventory inventory) { + evt.setInventory(inventory); + bus.publish(evt); + } + + @Override + public void fail(ErrorCode errorCode) { + evt.setError(errorCode); + bus.publish(evt); + } + }; + List desiredDns = Q.New(L3NetworkDnsVO.class) + .select(L3NetworkDnsVO_.dns) + .eq(L3NetworkDnsVO_.l3NetworkUuid, msg.getL3NetworkUuid()) + .notEq(L3NetworkDnsVO_.dns, msg.getDns()).listValues(); + NetworkConfigMutation mutation = NetworkConfigMutation.dhcpDns( + self.getL2NetworkUuid(), NetworkOperationOrigin.API, msg.getId(), + msg.getSession() == null ? null : msg.getSession().getAccountUuid(), + msg.getL3NetworkUuid(), NetworkUtils.isIpv4Address(msg.getDns()) + ? IPv6Constants.IPv4 : IPv6Constants.IPv6, desiredDns); + java.util.concurrent.atomic.AtomicReference result = + new java.util.concurrent.atomic.AtomicReference<>(); + if (mutateNetworkConfig(mutation, + localCompletion -> removeDns(msg, true, + new ReturnValueCompletion(localCompletion) { + @Override + public void success(L3NetworkInventory inventory) { + result.set(inventory); + localCompletion.success(); + } + + @Override + public void fail(ErrorCode errorCode) { + localCompletion.fail(errorCode); + } + }), result, completion)) { + return; + } + removeDns(msg, false, completion); + } + + private void removeDns(final APIRemoveDnsFromL3NetworkMsg msg, + boolean remoteCommitted, + ReturnValueCompletion completion) { SdnControllerDhcp sdnDhcp = l3NwMgr.getSdnControllerDhcp(msg.getL3NetworkUuid()); @@ -1339,6 +1881,11 @@ public void run(FlowTrigger trigger, Map data) { flow(new NoRollbackFlow() { String __name__ = "remove-dns-from-sdn"; + @Override + public boolean skip(Map data) { + return remoteCommitted; + } + @Override public void run(FlowTrigger trigger, Map data) { String sdnControllerUuid = L3NetworkHelper.getSdnControllerUuidFromL3Uuid(self.getUuid()); @@ -1401,16 +1948,14 @@ public void run(MessageReply reply) { done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { - evt.setInventory(L3NetworkInventory.valueOf(dbf.reload(self))); - bus.publish(evt); + completion.success(L3NetworkInventory.valueOf(dbf.reload(self))); } }); error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { - evt.setError(errCode); - bus.publish(evt); + completion.fail(errCode); } }); } @@ -1419,6 +1964,54 @@ public void handle(ErrorCode errCode, Map data) { private void handle(final APIAddDnsToL3NetworkMsg msg) { final APIAddDnsToL3NetworkEvent evt = new APIAddDnsToL3NetworkEvent(msg.getId()); + ReturnValueCompletion completion = new ReturnValueCompletion(msg) { + @Override + public void success(L3NetworkInventory inventory) { + evt.setInventory(inventory); + bus.publish(evt); + } + + @Override + public void fail(ErrorCode errorCode) { + evt.setError(errorCode); + bus.publish(evt); + } + }; + List desiredDns = Q.New(L3NetworkDnsVO.class) + .select(L3NetworkDnsVO_.dns) + .eq(L3NetworkDnsVO_.l3NetworkUuid, msg.getL3NetworkUuid()).listValues(); + if (!desiredDns.contains(msg.getDns())) { + desiredDns.add(msg.getDns()); + } + NetworkConfigMutation mutation = NetworkConfigMutation.dhcpDns( + self.getL2NetworkUuid(), NetworkOperationOrigin.API, msg.getId(), + msg.getSession() == null ? null : msg.getSession().getAccountUuid(), + msg.getL3NetworkUuid(), NetworkUtils.isIpv4Address(msg.getDns()) + ? IPv6Constants.IPv4 : IPv6Constants.IPv6, desiredDns); + java.util.concurrent.atomic.AtomicReference result = + new java.util.concurrent.atomic.AtomicReference<>(); + if (mutateNetworkConfig(mutation, + localCompletion -> addDns(msg, true, + new ReturnValueCompletion(localCompletion) { + @Override + public void success(L3NetworkInventory inventory) { + result.set(inventory); + localCompletion.success(); + } + + @Override + public void fail(ErrorCode errorCode) { + localCompletion.fail(errorCode); + } + }), result, completion)) { + return; + } + addDns(msg, false, completion); + } + + private void addDns(final APIAddDnsToL3NetworkMsg msg, + boolean remoteCommitted, + ReturnValueCompletion completion) { SdnControllerDhcp sdnDhcp = l3NwMgr.getSdnControllerDhcp(msg.getL3NetworkUuid()); @@ -1455,6 +2048,11 @@ public void rollback(FlowRollback trigger, Map data) { flow(new NoRollbackFlow() { String __name__ = "apply-to-sdn"; + @Override + public boolean skip(Map data) { + return remoteCommitted; + } + @Override public void run(FlowTrigger trigger, Map data) { String sdnControllerUuid = L3NetworkHelper.getSdnControllerUuidFromL3Uuid(self.getUuid()); @@ -1518,24 +2116,22 @@ public void run(MessageReply reply) { @Override public void handle(Map data) { self = dbf.reload(self); - evt.setInventory(L3NetworkInventory.valueOf(self)); logger.debug(String.format("successfully added dns[%s] to L3Network[uuid:%s, name:%s]", msg.getDns(), self.getUuid(), self.getName())); - bus.publish(evt); + completion.success(L3NetworkInventory.valueOf(self)); } }); error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { - evt.setError(errCode); - bus.publish(evt); + completion.fail(errCode); } }); } }).start(); } - private void handle(final AttachNetworkServiceToL3Msg msg) { + private void handle(final AttachNetworkServiceToL3Msg msg) { MessageReply reply = new MessageReply(); L3NetworkVO l3VO = dbf.findByUuid(msg.getL3NetworkUuid(), L3NetworkVO.class); List networkServiceProviderVOS = Q.New(NetworkServiceProviderVO.class).list(); @@ -1556,7 +2152,20 @@ private void handle(final AttachNetworkServiceToL3Msg msg) { logger.debug(String.format("successfully attached network service provider[uuid:%s] to l3network[uuid:%s, name:%s] with services%s", e.getKey(), self.getUuid(), self.getName(), e.getValue())); } - dbf.persistCollection(refVOS); + try { + new SQLBatch() { + @Override + protected void scripts() { + for (NetworkServiceL3NetworkRefVO ref : refVOS) { + persist(ref); + } + } + }.execute(); + } catch (OperationFailureException e) { + reply.setError(e.getErrorCode()); + bus.reply(msg, reply); + return; + } new While<>(refVOS).each((ref, wcomp) -> { String provideType = providerUuidTypeMap.get(ref.getNetworkServiceProviderUuid()); @@ -1572,6 +2181,12 @@ private void handle(final AttachNetworkServiceToL3Msg msg) { return; } + if (msg.getContext() != null && msg.getContext().isRemoteWriteSuppressed() + && nsType == NetworkServiceType.DHCP) { + wcomp.done(); + return; + } + nsMgr.enableNetworkService(l3VO, ptype, nsType, msg.getApiSystemTags(), new Completion(wcomp) { @Override public void success() { @@ -1610,6 +2225,68 @@ public void fail(ErrorCode errorCode) { }); } + private void handle(UpdateProjectedDnsMsg msg) { + thdf.chainSubmit(new ChainTask(msg) { + @Override + public void run(SyncTaskChain chain) { + if (msg.getContext() == null || !msg.getContext().isProjection()) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L3_10085, + "DNS projection requires a ZNS projection context")); + chain.next(); + return; + } + + LinkedHashSet desired = new LinkedHashSet<>( + msg.getDns() == null ? Collections.emptyList() : msg.getDns()); + String invalid = desired.stream().filter(dns -> !NetworkUtils.isIpAddress(dns)).findFirst().orElse(null); + if (invalid != null) { + bus.replyErrorByMessageType(msg, argerr(ORG_ZSTACK_NETWORK_L3_10086, + "DNS[%s] is not an IP address", invalid)); + chain.next(); + return; + } + + new SQLBatch() { + @Override + protected void scripts() { + List existing = Q.New(L3NetworkDnsVO.class) + .eq(L3NetworkDnsVO_.l3NetworkUuid, msg.getL3NetworkUuid()).list(); + Set current = existing.stream().map(L3NetworkDnsVO::getDns) + .collect(Collectors.toSet()); + List obsolete = existing.stream() + .filter(vo -> !desired.contains(vo.getDns())).collect(Collectors.toList()); + if (!obsolete.isEmpty()) { + dbf.removeCollection(obsolete, L3NetworkDnsVO.class); + } + List additions = desired.stream() + .filter(dns -> !current.contains(dns)) + .map(dns -> { + L3NetworkDnsVO vo = new L3NetworkDnsVO(); + vo.setL3NetworkUuid(msg.getL3NetworkUuid()); + vo.setDns(dns); + return vo; + }).collect(Collectors.toList()); + if (!additions.isEmpty()) { + dbf.persistCollection(additions); + } + } + }.execute(); + bus.reply(msg, new MessageReply()); + chain.next(); + } + + @Override + public String getSyncSignature() { + return syncThreadName; + } + + @Override + public String getName() { + return "update-projected-dns"; + } + }); + } + private void handle(APIAttachNetworkServiceToL3NetworkMsg msg) { APIAttachNetworkServiceToL3NetworkEvent evt = new APIAttachNetworkServiceToL3NetworkEvent(msg.getId()); if (msg.isSkipAttach()) { @@ -1619,28 +2296,82 @@ private void handle(APIAttachNetworkServiceToL3NetworkMsg msg) { return; } - AttachNetworkServiceToL3Msg amsg = new AttachNetworkServiceToL3Msg(); - amsg.setL3NetworkUuid(msg.getL3NetworkUuid()); - amsg.setNetworkServices(msg.getNetworkServices()); - if (msg.getSystemTags() != null) { - amsg.setApiSystemTags(msg.getSystemTags()); + ReturnValueCompletion completion = new ReturnValueCompletion(msg) { + @Override + public void success(L3NetworkInventory inventory) { + evt.setInventory(inventory); + bus.publish(evt); + } + + @Override + public void fail(ErrorCode errorCode) { + evt.setError(errorCode); + bus.publish(evt); + } + }; + if (containsDhcp(msg.getNetworkServices())) { + NetworkConfigMutation mutation = NetworkConfigMutation.dhcp( + self.getL2NetworkUuid(), NetworkOperationOrigin.API, msg.getId(), + msg.getSession() == null ? null : msg.getSession().getAccountUuid(), + msg.getL3NetworkUuid(), true, msg.getSystemTags()); + java.util.concurrent.atomic.AtomicReference result = + new java.util.concurrent.atomic.AtomicReference<>(); + if (mutateNetworkConfig(mutation, + localCompletion -> attachNetworkServices(msg, + NetworkCreateContext.cloudCommit(msg.getId()), + new ReturnValueCompletion(localCompletion) { + @Override + public void success(L3NetworkInventory inventory) { + result.set(inventory); + localCompletion.success(); + } + + @Override + public void fail(ErrorCode errorCode) { + localCompletion.fail(errorCode); + } + }), result, completion)) { + return; + } + } + attachNetworkServices(msg, NetworkCreateContext.api(), completion); + } + + private boolean containsDhcp(Map> networkServices) { + if (networkServices == null || networkServices.isEmpty()) { + return false; } + List services = networkServices.values().stream() + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + return services.stream().anyMatch(NetworkServiceType.DHCP.toString()::equals); + } - bus.makeTargetServiceIdByResourceUuid(amsg, L3NetworkConstant.SERVICE_ID, msg.getL3NetworkUuid()); - bus.send(amsg, new CloudBusCallBack(msg) { + private void attachNetworkServices(APIAttachNetworkServiceToL3NetworkMsg msg, + NetworkCreateContext context, + ReturnValueCompletion completion) { + AttachNetworkServiceToL3Msg attach = new AttachNetworkServiceToL3Msg(); + attach.setL3NetworkUuid(msg.getL3NetworkUuid()); + attach.setNetworkServices(msg.getNetworkServices()); + attach.setContext(context); + if (msg.getSystemTags() != null) { + attach.setApiSystemTags(msg.getSystemTags()); + } + bus.makeTargetServiceIdByResourceUuid(attach, L3NetworkConstant.SERVICE_ID, + msg.getL3NetworkUuid()); + bus.send(attach, new CloudBusCallBack(completion) { @Override public void run(MessageReply reply) { - if (reply.isSuccess()) { - self = dbf.reload(self); - evt.setInventory(L3NetworkInventory.valueOf(self)); - bus.publish(evt); - } else { - evt.setError(reply.getError()); - bus.publish(evt); + if (!reply.isSuccess()) { + completion.fail(reply.getError()); + return; } + self = dbf.reload(self); + completion.success(L3NetworkInventory.valueOf(self)); } }); - } + } private void doDeleteIpRange(APIDeleteIpRangeMsg msg, Completion completion) { @@ -1714,6 +2445,68 @@ public void handle(ErrorCode errCode, Map data) { }).start(); } + private void deleteIpRangeWithMutation(APIDeleteIpRangeMsg msg, Completion completion) { + IpRangeVO deleting = dbf.findByUuid(msg.getUuid(), IpRangeVO.class); + if (!(deleting instanceof NormalIpRangeVO)) { + doDeleteIpRange(msg, completion); + return; + } + List remaining = Q.New(NormalIpRangeVO.class) + .eq(IpRangeVO_.l3NetworkUuid, deleting.getL3NetworkUuid()) + .eq(IpRangeVO_.ipVersion, deleting.getIpVersion()) + .notEq(IpRangeVO_.uuid, deleting.getUuid()) + .list(); + NetworkConfigMutation mutation; + if (remaining.isEmpty()) { + mutation = NetworkConfigMutation.deleteIpam( + self.getL2NetworkUuid(), NetworkOperationOrigin.API, msg.getId(), + msg.getSession() == null ? null : msg.getSession().getAccountUuid(), + deleting.getL3NetworkUuid(), deleting.getIpVersion()); + } else { + List targets = remaining.stream() + .map(range -> new NetworkConfigMutation.IpRangeTarget( + range.getUuid(), range.getStartIp(), range.getEndIp())) + .collect(Collectors.toList()); + NormalIpRangeVO first = remaining.get(0); + int prefix = first.getIpVersion() == IPv6Constants.IPv6 + ? first.getPrefixLen() + : NetworkUtils.getPrefixLengthFromNetmask(first.getNetmask()); + mutation = NetworkConfigMutation.ipam( + self.getL2NetworkUuid(), NetworkOperationOrigin.API, msg.getId(), + msg.getSession() == null ? null : msg.getSession().getAccountUuid(), + deleting.getL3NetworkUuid(), deleting.getIpVersion(), + first.getGateway() + "/" + prefix, targets); + } + java.util.concurrent.atomic.AtomicReference deleted = + new java.util.concurrent.atomic.AtomicReference<>(false); + if (mutateNetworkConfig(mutation, + localCompletion -> doDeleteIpRange(msg, new Completion(localCompletion) { + @Override + public void success() { + deleted.set(true); + localCompletion.success(); + } + + @Override + public void fail(ErrorCode errorCode) { + localCompletion.fail(errorCode); + } + }), deleted, new ReturnValueCompletion(completion) { + @Override + public void success(Boolean ignored) { + completion.success(); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + })) { + return; + } + doDeleteIpRange(msg, completion); + } + private void syncManagementServiceTypeWhileDelete(ServiceTypeExtensionPoint ext, L2NetworkVO l2NetworkVO, List hostUuids) { String l2NetworkType = l2NetworkVO.getType(); switch (l2NetworkType) { @@ -1744,7 +2537,7 @@ public String getName() { @Override public void run(SyncTaskChain chain) { - doDeleteIpRange(msg, new Completion(chain) { + deleteIpRangeWithMutation(msg, new Completion(chain) { @Override public void success() { bus.publish(evt); diff --git a/network/src/main/java/org/zstack/network/l3/L3NetworkCascadeExtension.java b/network/src/main/java/org/zstack/network/l3/L3NetworkCascadeExtension.java index 1d8a5359327..bde03163291 100755 --- a/network/src/main/java/org/zstack/network/l3/L3NetworkCascadeExtension.java +++ b/network/src/main/java/org/zstack/network/l3/L3NetworkCascadeExtension.java @@ -16,11 +16,13 @@ import org.zstack.header.message.MessageReply; import org.zstack.header.network.l2.L2NetworkInventory; import org.zstack.header.network.l2.L2NetworkVO; +import org.zstack.header.network.l2.NetworkDeletionContext; import org.zstack.header.network.l3.*; import org.zstack.utils.CollectionUtils; import org.zstack.utils.Utils; import org.zstack.utils.function.Function; import org.zstack.utils.logging.CLogger; +import org.zstack.network.l2.NetworkDeletionContexts; import javax.persistence.TypedQuery; import java.util.ArrayList; @@ -87,6 +89,7 @@ private void handleDeletion(final CascadeAction action, final Completion complet L3NetworkDeletionMsg msg = new L3NetworkDeletionMsg(); msg.setL3NetworkUuid(l3inv.getUuid()); msg.setForceDelete(action.isActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE)); + msg.setNetworkDeletionContext(NetworkDeletionContexts.get(action, l3inv.getL2NetworkUuid())); bus.makeTargetServiceIdByResourceUuid(msg, L3NetworkConstant.SERVICE_ID, l3inv.getUuid()); msgs.add(msg); } @@ -125,7 +128,8 @@ private void handleDeletionCheck(CascadeAction action, Completion completion) { try { for (L3NetworkInventory prinv : l3invs) { - extpEmitter.preDelete(prinv); + extpEmitter.preDelete(prinv, + NetworkDeletionContexts.get(action, prinv.getL2NetworkUuid())); } completion.success(); diff --git a/network/src/main/java/org/zstack/network/l3/L3NetworkExtensionPointEmitter.java b/network/src/main/java/org/zstack/network/l3/L3NetworkExtensionPointEmitter.java index 34dc9f35674..9ebb53cb13c 100755 --- a/network/src/main/java/org/zstack/network/l3/L3NetworkExtensionPointEmitter.java +++ b/network/src/main/java/org/zstack/network/l3/L3NetworkExtensionPointEmitter.java @@ -7,6 +7,7 @@ import org.zstack.header.network.l3.L3NetworkDeleteExtensionPoint; import org.zstack.header.network.l3.L3NetworkException; import org.zstack.header.network.l3.L3NetworkInventory; +import org.zstack.header.network.l2.NetworkDeletionContext; import org.zstack.utils.CollectionUtils; import org.zstack.utils.Utils; import org.zstack.utils.function.ForEachFunction; @@ -32,9 +33,14 @@ public void run(L3NetworkCreateExtensionPoint arg) { } public void preDelete(L3NetworkInventory inv) throws L3NetworkException { + preDelete(inv, null); + } + + public void preDelete(L3NetworkInventory inv, NetworkDeletionContext context) + throws L3NetworkException { for (L3NetworkDeleteExtensionPoint ext : deleteExtensions) { try { - ext.preDeleteL3Network(inv); + ext.preDeleteL3Network(inv, context); } catch (L3NetworkException l3e) { throw l3e; } catch (Exception e) { @@ -45,19 +51,27 @@ public void preDelete(L3NetworkInventory inv) throws L3NetworkException { } public void beforeDelete(final L3NetworkInventory inv) { + beforeDelete(inv, null); + } + + public void beforeDelete(final L3NetworkInventory inv, NetworkDeletionContext context) { CollectionUtils.safeForEach(deleteExtensions, new ForEachFunction() { @Override public void run(L3NetworkDeleteExtensionPoint arg) { - arg.beforeDeleteL3Network(inv); + arg.beforeDeleteL3Network(inv, context); } }); } public void afterDelete(final L3NetworkInventory inv) { + afterDelete(inv, null); + } + + public void afterDelete(final L3NetworkInventory inv, NetworkDeletionContext context) { CollectionUtils.safeForEach(deleteExtensions, new ForEachFunction() { @Override public void run(L3NetworkDeleteExtensionPoint arg) { - arg.afterDeleteL3Network(inv); + arg.afterDeleteL3Network(inv, context); } }); } diff --git a/network/src/main/java/org/zstack/network/l3/L3NetworkManager.java b/network/src/main/java/org/zstack/network/l3/L3NetworkManager.java index eebbb11b747..9177bbadacb 100755 --- a/network/src/main/java/org/zstack/network/l3/L3NetworkManager.java +++ b/network/src/main/java/org/zstack/network/l3/L3NetworkManager.java @@ -14,6 +14,8 @@ public interface L3NetworkManager { UsedIpInventory reserveIp(IpRangeVO ipRange, String ip); UsedIpInventory reserveIp(IpRangeVO ipRange, String ip, boolean allowDuplicatedAddress); + UsedIpInventory reserveIp(IpRangeVO ipRange, String ip, boolean allowDuplicatedAddress, + String operationUuid, String operationStep); boolean isIpRangeFull(IpRangeVO vo); diff --git a/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java b/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java index 45fecdc0aa1..757a8ab5932 100755 --- a/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java +++ b/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java @@ -32,6 +32,10 @@ import org.zstack.header.message.Message; import org.zstack.header.message.MessageReply; import org.zstack.header.network.l2.*; +import org.zstack.header.network.NetworkDeleteGuardExtensionPoint; +import org.zstack.header.network.NetworkConfigLocalContinuation; +import org.zstack.header.network.NetworkConfigMutation; +import org.zstack.header.network.NetworkConfigMutationExtensionPoint; import org.zstack.header.network.l3.*; import org.zstack.header.network.l3.datatypes.IpCapacityData; import org.zstack.header.network.service.GetSdnControllerExtensionPoint; @@ -116,13 +120,45 @@ public void handleMessage(Message msg) { } private void handleLocalMessage(Message msg) { - if (msg instanceof L3NetworkMessage) { + if (msg instanceof CreateL3NetworkMsg) { + handle((CreateL3NetworkMsg) msg); + } else if (msg instanceof L3NetworkMessage) { passThrough((L3NetworkMessage) msg); } else { bus.dealWithUnknownMessage(msg); } } + private void handle(CreateL3NetworkMsg msg) { + APICreateL3NetworkMsg api = new APICreateL3NetworkMsg(); + api.setName(msg.getName()); + api.setDescription(msg.getDescription()); + api.setType(msg.getType()); + api.setL2NetworkUuid(msg.getL2NetworkUuid()); + api.setCategory(msg.getCategory()); + api.setIpVersion(msg.getIpVersion()); + api.setSystem(msg.isSystem()); + api.setDnsDomain(msg.getDnsDomain()); + api.setEnableIPAM(msg.getEnableIPAM()); + api.setResourceUuid(msg.getResourceUuid()); + api.setSession(msg.getSession()); + api.setSystemTags(msg.getSystemTags()); + handle(api, msg.getContext() == null ? NetworkCreateContext.api() : msg.getContext(), + new ReturnValueCompletion(msg) { + @Override + public void success(L3NetworkInventory inventory) { + CreateL3NetworkReply reply = new CreateL3NetworkReply(); + reply.setInventory(inventory); + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + bus.replyErrorByMessageType(msg, errorCode); + } + }); + } + private void handleApiMessage(APIMessage msg) { if (msg instanceof APICreateL3NetworkMsg) { @@ -526,7 +562,68 @@ private void syncManagementServiceTypeWhileCreate(ServiceTypeExtensionPoint ext, } private void handle(APICreateL3NetworkMsg msg) { - APICreateL3NetworkEvent evt = new APICreateL3NetworkEvent(msg.getId()); + handle(msg, NetworkCreateContext.api(), new ReturnValueCompletion(msg) { + @Override + public void success(L3NetworkInventory inventory) { + APICreateL3NetworkEvent evt = new APICreateL3NetworkEvent(msg.getId()); + evt.setInventory(inventory); + bus.publish(evt); + } + + @Override + public void fail(ErrorCode errorCode) { + APICreateL3NetworkEvent evt = new APICreateL3NetworkEvent(msg.getId()); + evt.setError(errorCode); + bus.publish(evt); + } + }); + } + + private void handle(APICreateL3NetworkMsg msg, NetworkCreateContext context) { + handle(msg, context, new ReturnValueCompletion(msg) { + @Override + public void success(L3NetworkInventory inventory) { + logger.debug(String.format("Successfully created L3Network[name:%s, uuid:%s]", msg.getName(), inventory.getUuid())); + } + + @Override + public void fail(ErrorCode errorCode) { + logger.warn(String.format("Failed to create L3Network[name:%s]: %s", msg.getName(), errorCode)); + } + }); + } + + private void handle(APICreateL3NetworkMsg msg, NetworkCreateContext context, + ReturnValueCompletion completion) { + + if (context.getOrigin() == NetworkOperationOrigin.API) { + if (msg.getResourceUuid() == null) { + msg.setResourceUuid(Platform.getUuid()); + } + NetworkConfigMutation mutation = NetworkConfigMutation.l3Create( + msg.getL2NetworkUuid(), context.getOrigin(), msg.getId(), + msg.getSession() == null ? null : msg.getSession().getAccountUuid(), + msg.getResourceUuid(), msg.getType(), msg.getSystemTags()); + java.util.concurrent.atomic.AtomicReference created = + new java.util.concurrent.atomic.AtomicReference<>(); + if (mutateNetworkConfig(mutation, + localCompletion -> handle(msg, + NetworkCreateContext.cloudCommit(msg.getId()), + new ReturnValueCompletion(localCompletion) { + @Override + public void success(L3NetworkInventory inventory) { + created.set(inventory); + localCompletion.success(); + } + + @Override + public void fail(ErrorCode errorCode) { + localCompletion.fail(errorCode); + } + }), created, completion)) { + return; + } + } L2NetworkVO l2Vo = Q.New(L2NetworkVO.class).eq(L2NetworkVO_.uuid, msg.getL2NetworkUuid()).find(); assert l2Vo.getZoneUuid() != null; @@ -569,7 +666,7 @@ public void run(FlowTrigger trigger, Map data) { return; } - controllerL3.createL3Network(L3NetworkInventory.valueOf(vo), msg.getSystemTags(), + controllerL3.createL3Network(L3NetworkInventory.valueOf(vo), msg.getSystemTags(), context, new Completion(trigger) { @Override public void success() { @@ -591,8 +688,14 @@ public void run(FlowTrigger trigger, Map data) { L3NetworkInventory inv = new SQLBatchWithReturn() { @Override protected L3NetworkInventory scripts() { - vo.setAccountUuid(msg.getSession().getAccountUuid()); - L3NetworkInventory inv = factory.createL3Network(vo, msg); + String accountUuid = msg.getSession() == null && context.getExternalRef() != null + ? context.getExternalRef().getAccountUuid() + : msg.getSession() == null ? null : msg.getSession().getAccountUuid(); + if (accountUuid == null) { + throw new CloudRuntimeException("account uuid is required for l3 create"); + } + vo.setAccountUuid(accountUuid); + L3NetworkInventory inv = factory.createL3Network(vo, msg, context); tagMgr.createTagsFromAPICreateMessage(msg, vo.getUuid(), L3NetworkVO.class.getSimpleName()); return inv; } @@ -612,26 +715,55 @@ protected L3NetworkInventory scripts() { } extpEmitter.afterCreate(inv); - evt.setInventory(inv); + data.put("L3NetworkInventory", inv); trigger.next(); } }).error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { - evt.setError(errCode); - bus.publish(evt); + completion.fail(errCode); } }).done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { - logger.debug(String.format("Successfully created L3Network[name:%s, uuid:%s]", vo.getName(), vo.getUuid())); - bus.publish(evt); + completion.success((L3NetworkInventory) data.get("L3NetworkInventory")); } }).start(); } + private boolean mutateNetworkConfig(NetworkConfigMutation mutation, + NetworkConfigLocalContinuation continuation, + java.util.concurrent.atomic.AtomicReference result, + ReturnValueCompletion completion) { + List providers = pluginRgty + .getExtensionList(NetworkConfigMutationExtensionPoint.class).stream() + .filter(provider -> provider.supports(mutation)) + .collect(java.util.stream.Collectors.toList()); + if (providers.isEmpty()) { + return false; + } + if (providers.size() != 1) { + completion.fail(errf.stringToInternalError(String.format( + "network mutation[%s] matched[%s] providers", + mutation.getKind(), providers.size()))); + return true; + } + providers.get(0).mutate(mutation, continuation, new Completion(completion) { + @Override + public void success() { + completion.success(result.get()); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + }); + return true; + } + @Override public String getId() { return bus.makeLocalServiceId(L3NetworkConstant.SERVICE_ID); @@ -817,13 +949,30 @@ public UsedIpInventory reserveIp(IpRangeVO ipRange, String ip) { @Override public UsedIpInventory reserveIp(IpRangeVO ipRange, String ip, boolean allowDuplicatedAddress) { - if (NetworkUtils.isIpv4Address(ip)) { - return reserveIpv4(ipRange, ip, allowDuplicatedAddress); - } else if (IPv6NetworkUtils.isIpv6Address(ip)) { - return reserveIpv6(ipRange, ip, allowDuplicatedAddress); - } else { - return null; - } + return reserveIp(ipRange, ip, allowDuplicatedAddress, null, null); + } + + @Override + public UsedIpInventory reserveIp(IpRangeVO ipRange, String ip, boolean allowDuplicatedAddress, + String operationUuid, String operationStep) { + return new SQLBatchWithReturn() { + @Override + protected UsedIpInventory scripts() { + for (NetworkDeleteGuardExtensionPoint extension : + pluginRgty.getExtensionList(NetworkDeleteGuardExtensionPoint.class)) { + ErrorCode errorCode = extension.checkL3NetworkWithLock(ipRange.getL3NetworkUuid()); + if (errorCode != null) { + throw new CloudRuntimeException(errorCode.getDetails()); + } + } + if (NetworkUtils.isIpv4Address(ip)) { + return reserveIpv4(ipRange, ip, allowDuplicatedAddress); + } else if (IPv6NetworkUtils.isIpv6Address(ip)) { + return reserveIpv6(ipRange, ip, allowDuplicatedAddress); + } + return null; + } + }.execute(); } @Override diff --git a/network/src/main/java/org/zstack/network/l3/NormalIpRangeFactory.java b/network/src/main/java/org/zstack/network/l3/NormalIpRangeFactory.java index c052d0a1d69..6cb9aed8c5d 100644 --- a/network/src/main/java/org/zstack/network/l3/NormalIpRangeFactory.java +++ b/network/src/main/java/org/zstack/network/l3/NormalIpRangeFactory.java @@ -16,6 +16,7 @@ import org.zstack.header.message.APICreateMessage; import org.zstack.header.message.MessageReply; import org.zstack.header.network.l3.*; +import org.zstack.header.network.l2.NetworkCreateContext; import org.zstack.header.network.sdncontroller.SdnControllerConstant; import org.zstack.header.network.service.SdnControllerDhcp; import org.zstack.utils.CollectionUtils; @@ -47,6 +48,12 @@ public IpRangeType getType() { @Override public void createIpRange(List iprs, APICreateMessage msg, ReturnValueCompletion> completion) { + createIpRange(iprs, msg, NetworkCreateContext.api(), completion); + } + + @Override + public void createIpRange(List iprs, APICreateMessage msg, NetworkCreateContext context, + ReturnValueCompletion> completion) { FlowChain chain = new SimpleFlowChain(); chain.setName(String.format("add-iprange-to-l3-%s", iprs.get(0).getL3NetworkUuid())); chain.then(new Flow() { @@ -59,7 +66,8 @@ public void run(FlowTrigger trigger, Map data) { @Override protected NormalIpRangeVO scripts() { NormalIpRangeVO vo = (NormalIpRangeVO) IpRangeHelper - .fromIpRangeInventory(ipr, msg.getSession().getAccountUuid()); + .fromIpRangeInventory(ipr, msg.getSession() == null && context.getExternalRef() != null + ? context.getExternalRef().getAccountUuid() : msg.getSession().getAccountUuid()); dbf.getEntityManager().persist(vo); dbf.getEntityManager().flush(); dbf.getEntityManager().refresh(vo); @@ -97,7 +105,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 +127,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 +158,10 @@ public void run(MessageReply reply) { @Override public void run(FlowTrigger trigger, Map data) { + if (context.isRemoteWriteSuppressed()) { + trigger.next(); + return; + } L3NetworkVO l3vo = dbf.findByUuid(iprs.get(0).getL3NetworkUuid(), L3NetworkVO.class); SdnControllerL3 sdnL3 = l3Mgr.getSdnControllerL3(l3vo.getL2NetworkUuid()); if (sdnL3 == null) { diff --git a/network/src/main/java/org/zstack/network/l3/RandomIpAllocatorStrategy.java b/network/src/main/java/org/zstack/network/l3/RandomIpAllocatorStrategy.java index e4b8d1562f6..d402a22a6d9 100755 --- a/network/src/main/java/org/zstack/network/l3/RandomIpAllocatorStrategy.java +++ b/network/src/main/java/org/zstack/network/l3/RandomIpAllocatorStrategy.java @@ -51,7 +51,8 @@ public UsedIpInventory allocateIp(IpAllocateMessage msg) { return null; } - UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed()); + UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed(), + msg.getOperationUuid(), msg.getOperationStep()); if (inv != null) { return inv; } diff --git a/network/src/main/java/org/zstack/network/l3/RandomIpv6AllocatorStrategy.java b/network/src/main/java/org/zstack/network/l3/RandomIpv6AllocatorStrategy.java index b9ea4737ec7..2b3ead694d8 100755 --- a/network/src/main/java/org/zstack/network/l3/RandomIpv6AllocatorStrategy.java +++ b/network/src/main/java/org/zstack/network/l3/RandomIpv6AllocatorStrategy.java @@ -50,7 +50,8 @@ public UsedIpInventory allocateIp(IpAllocateMessage msg) { return null; } - UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed()); + UsedIpInventory inv = l3NwMgr.reserveIp(tr, ip, msg.isDuplicatedIpAllowed(), + msg.getOperationUuid(), msg.getOperationStep()); if (inv != null) { return inv; } diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnController.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnController.java index 57072cbaa57..80d4f917eee 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnController.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnController.java @@ -27,4 +27,10 @@ public interface SdnController { default void removeHost(SdnControllerRemoveHostMsg msg, Completion completion) {completion.success();}; default void changeHost(SdnControllerHostRefVO oldRef, SdnControllerHostRefVO newRef, Completion completion) {completion.success();}; + + default void pullResources(PullSdnControllerMsg msg, Completion completion) { + completion.fail(org.zstack.core.Platform.operr( + org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_SDNCONTROLLER_10012, + "Resource pull is not supported by sdn controller[%s]", msg.getSdnControllerUuid())); + } } diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerApiInterceptor.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerApiInterceptor.java index 97840150a1e..b1f38ea9be6 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerApiInterceptor.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerApiInterceptor.java @@ -60,6 +60,7 @@ public List getMessageClassToIntercept() { ret.add(APISetVmNicSecurityGroupMsg.class); ret.add(APIAddSecurityGroupRuleMsg.class); ret.add(APIPullSdnControllerTenantMsg.class); + ret.add(APIPullSdnControllerMsg.class); return ret; } @@ -85,6 +86,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 +270,21 @@ private void validate(APIPullSdnControllerTenantMsg msg) { } } + private void validate(APIPullSdnControllerMsg msg) { + if (!Arrays.asList("Segment", "TenantRouter").contains(msg.getResourceType())) { + throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_SDNCONTROLLER_10030, + "Unsupported resource type[%s]", msg.getResourceType())); + } + if (msg.getResourceUuids() != null && msg.getResourceUuids().size() > 100) { + throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_SDNCONTROLLER_10031, + "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_10032, + "SDN controller[uuid:%s] not found", msg.getSdnControllerUuid())); + } + } + private void validateVlanRanges(List ranges) { List sdnVlanRanges = new ArrayList<>(); for (String range : ranges) { diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerBase.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerBase.java index 848c962b2e3..c256f2c6947 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerBase.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerBase.java @@ -8,6 +8,7 @@ import org.zstack.core.Platform; import org.zstack.core.asyncbatch.While; import org.zstack.core.cascade.CascadeConstant; +import org.zstack.core.cascade.CascadeAction; import org.zstack.core.cascade.CascadeFacade; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.CloudBusCallBack; @@ -120,12 +121,16 @@ public void handleMessage(SdnControllerMessage msg) { handle((APISdnControllerChangeHostMsg) msg); } else if (msg instanceof APIPullSdnControllerTenantMsg) { handle((APIPullSdnControllerTenantMsg) msg); + } else if (msg instanceof APIPullSdnControllerMsg) { + handle((APIPullSdnControllerMsg) msg); } else if (msg instanceof APIChangeSdnControllerMsg) { handle((APIChangeSdnControllerMsg) msg); } else if (msg instanceof SdnControllerRemoveHostMsg) { handle((SdnControllerRemoveHostMsg) msg); } else if (msg instanceof PullSdnControllerTenantMsg) { handle((PullSdnControllerTenantMsg) msg); + } else if (msg instanceof PullSdnControllerMsg) { + handle((PullSdnControllerMsg) msg); } else if (msg instanceof ReconnectSdnControllerMsg) { handle((ReconnectSdnControllerMsg) msg); } else if (msg instanceof SyncSdnControllerDataMsg) { @@ -948,13 +953,15 @@ private void handle(APIRemoveSdnControllerMsg msg) { final String issuer = SdnControllerVO.class.getSimpleName(); SdnControllerVO vo = dbf.findByUuid(msg.getUuid(), SdnControllerVO.class); final List ctx = asList(SdnControllerInventory.valueOf(vo)); + final CascadeAction cascadeAction = new CascadeAction().setRootIssuer(issuer) + .setRootIssuerContext(ctx).setParentIssuer(issuer).setParentIssuerContext(ctx); FlowChain chain = FlowChainBuilder.newSimpleFlowChain(); chain.setName(String.format("delete-sdn-controller-%s-name-%s", msg.getUuid(), vo.getName())); if (msg.getDeletionMode() == APIDeleteMessage.DeletionMode.Permissive) { chain.then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_CHECK_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_CHECK_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -969,7 +976,7 @@ public void fail(ErrorCode errorCode) { }).then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_DELETE_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_DELETE_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -986,7 +993,7 @@ public void fail(ErrorCode errorCode) { chain.then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { - casf.asyncCascade(CascadeConstant.DELETION_FORCE_DELETE_CODE, issuer, ctx, new Completion(trigger) { + casf.asyncCascade(cascadeAction.setActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE), new Completion(trigger) { @Override public void success() { trigger.next(); @@ -1063,6 +1070,15 @@ public void fail(ErrorCode errorCode) { }); } + private void handle(APIPullSdnControllerMsg amsg) { + APIPullSdnControllerEvent event = new APIPullSdnControllerEvent(amsg.getId()); + PullSdnControllerMsg msg = PullSdnControllerMsg.fromApi(amsg); + pullResources(msg, new Completion(msg) { + @Override public void success() { bus.publish(event); } + @Override public void fail(ErrorCode errorCode) { event.setError(errorCode); bus.publish(event); } + }); + } + private void handle(PullSdnControllerTenantMsg msg) { PullSdnControllerTenantReply reply = new PullSdnControllerTenantReply(); @@ -1086,6 +1102,18 @@ public void fail(ErrorCode errorCode) { }); } + private void handle(PullSdnControllerMsg msg) { + PullSdnControllerReply reply = new PullSdnControllerReply(); + pullResources(msg, new Completion(msg) { + @Override public void success() { bus.reply(msg, reply); } + @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } + }); + } + + private void pullResources(PullSdnControllerMsg msg, Completion completion) { + getSdnController().pullResources(msg, completion); + } + private void pullSdnControllerTenant(PullSdnControllerTenantMsg msg, Completion completion) { // Only H3C VCFC V2 controllers support this operation, already validated in interceptor // But confirm again here to ensure type safety diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerL2.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerL2.java index 4a63f3c5c2b..fb348ef4b23 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerL2.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerL2.java @@ -1,9 +1,12 @@ package org.zstack.sdnController; import org.zstack.header.core.Completion; +import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.host.HostInventory; import org.zstack.header.network.l2.APICreateL2NetworkMsg; import org.zstack.header.network.l2.L2NetworkInventory; +import org.zstack.header.network.l2.NetworkCreateContext; +import org.zstack.header.network.l2.NetworkDeletionContext; import org.zstack.header.network.l3.IpRangeInventory; import org.zstack.header.network.l3.L3NetworkInventory; import org.zstack.header.vm.VmNicInventory; @@ -20,6 +23,9 @@ public interface SdnControllerL2 { void preCreateVxlanNetwork(L2VxlanNetworkInventory vxlan, List systemTags, Completion completion); void createL2Network(L2NetworkInventory inv, APICreateL2NetworkMsg msg, Completion completion); + default void createL2Network(L2NetworkInventory inv, APICreateL2NetworkMsg msg, NetworkCreateContext context, Completion completion) { + createL2Network(inv, msg, completion); + } void postCreateVxlanNetwork(L2VxlanNetworkInventory vxlan, List systemTags, Completion completion); void preAttachL2NetworkToCluster(L2VxlanNetworkInventory vxlan, List systemTags, Completion completion); @@ -30,6 +36,34 @@ public interface SdnControllerL2 { void deleteSdnController(SdnControllerDeletionMsg msg, SdnControllerInventory sdn, Completion completion); void detachL2NetworkFromCluster(L2VxlanNetworkInventory vxlan, List clusterUuids, Completion completion); void deleteL2Network(L2NetworkInventory inv, Completion completion); + default boolean requiresConfirmedDelete() { return false; } + default void deleteL2Network(L2NetworkInventory inv, String operationUuid, Completion completion) { + deleteL2Network(inv, completion); + } + default void deleteL2Network(L2NetworkInventory inv, NetworkDeletionContext context, + Completion completion) { + deleteL2Network(inv, context == null ? null : context.getOperationUuid(), completion); + } + default ErrorCode beginConfirmedDelete(L2NetworkInventory inv) { return null; } + default ErrorCode beginConfirmedDelete(L2NetworkInventory inv, NetworkDeletionContext context) { + return beginConfirmedDelete(inv); + } + default ErrorCode checkConfirmedDelete(L2NetworkInventory inv) { return null; } + default ErrorCode checkConfirmedDelete(L2NetworkInventory inv, NetworkDeletionContext context) { + return checkConfirmedDelete(inv); + } + default ErrorCode completeConfirmedDelete(L2NetworkInventory inv) { return null; } + default ErrorCode completeConfirmedDelete(L2NetworkInventory inv, NetworkDeletionContext context) { + return completeConfirmedDelete(inv); + } + default ErrorCode cancelConfirmedDelete(L2NetworkInventory inv) { return null; } + default ErrorCode cancelConfirmedDelete(L2NetworkInventory inv, NetworkDeletionContext context) { + return cancelConfirmedDelete(inv); + } + default void deleteConfirmedLocalMetadata(L2NetworkInventory inv) { } + default void deleteConfirmedLocalMetadata(L2NetworkInventory inv, NetworkDeletionContext context) { + deleteConfirmedLocalMetadata(inv); + } List getVniRange(SdnControllerInventory controller); List getVlanRange(SdnControllerInventory controller); diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java index e832f146738..d2c116681b9 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java @@ -47,7 +47,7 @@ import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.*; public class SdnControllerManagerImpl extends AbstractService implements SdnControllerManager, - L2NetworkCreateExtensionPoint, L2NetworkDeleteExtensionPoint, + L2NetworkCreateExtensionPoint, L2NetworkDeleteExtensionPoint, L2DeleteConfirmExtensionPoint, SecurityGroupGetSdnBackendExtensionPoint, AfterAddIpRangeExtensionPoint, IpRangeDeletionExtensionPoint, GetSdnControllerExtensionPoint, AfterAllocateSdnNicExtensionPoint { @@ -292,6 +292,12 @@ public void beforeCreateL2Network(APICreateL2NetworkMsg msg) throws NetworkExcep @Override public void postCreateL2Network(L2NetworkInventory l2Network, APICreateL2NetworkMsg msg, Completion completion) { + postCreateL2Network(l2Network, msg, NetworkCreateContext.api(), completion); + } + + @Override + public void postCreateL2Network(L2NetworkInventory l2Network, APICreateL2NetworkMsg msg, + NetworkCreateContext context, Completion completion) { VSwitchType vSwitchType = VSwitchType.valueOf(l2Network.getvSwitchType()); if (vSwitchType.getSdnControllerType() == null) { completion.success(); @@ -326,7 +332,7 @@ public void postCreateL2Network(L2NetworkInventory l2Network, APICreateL2Network SdnControllerFactory factory = getSdnControllerFactory(sdnControllerVO.getVendorType()); SdnControllerL2 controller = factory.getSdnControllerL2(sdnControllerVO); - controller.createL2Network(l2Network, msg, completion); + controller.createL2Network(l2Network, msg, context, completion); } @Override @@ -346,16 +352,7 @@ public void beforeDeleteL2Network(L2NetworkInventory inventory) { @Override public void deleteL2Network(L2NetworkInventory inv, NoErrorCompletion completion) { - VSwitchType vSwitchType = VSwitchType.valueOf(inv.getvSwitchType()); - if (vSwitchType.getSdnControllerType() == null) { - //hardware vxlan will go this path - completion.done(); - return; - } - - /* vswitch type: OvnDpdk will go here */ - SdnControllerFactory factory = getSdnControllerFactory(vSwitchType.getSdnControllerType()); - SdnControllerL2 controllerL2 = factory.getSdnControllerL2(inv.getUuid()); + SdnControllerL2 controllerL2 = findSdnControllerL2(inv); if (controllerL2 == null) { logger.warn(String.format("can not found sdn controller for l2 network[uuid:%s, vswitchType:%s]", inv.getUuid(), inv.getvSwitchType())); @@ -378,6 +375,129 @@ public void fail(ErrorCode errorCode) { }); } + @Override + public boolean requiresConfirmedDelete(L2NetworkInventory inv) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inv); + return controllerL2 != null && controllerL2.requiresConfirmedDelete(); + } + + @Override + public void deleteL2Network(L2NetworkInventory inv, String operationUuid, Completion completion) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inv); + if (controllerL2 == null) { + completion.fail(confirmedDeleteControllerMissing(inv)); + return; + } + controllerL2.deleteL2Network(inv, operationUuid, completion); + } + + @Override + public void deleteL2Network(L2NetworkInventory inv, NetworkDeletionContext context, + Completion completion) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inv); + if (controllerL2 == null) { + completion.fail(confirmedDeleteControllerMissing(inv)); + return; + } + controllerL2.deleteL2Network(inv, context, completion); + } + + @Override + public boolean supports(L2NetworkInventory inventory) { + return requiresConfirmedDelete(inventory); + } + + @Override + public ErrorCode begin(L2NetworkInventory inventory) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + return controllerL2 == null ? confirmedDeleteControllerMissing(inventory) + : controllerL2.beginConfirmedDelete(inventory); + } + + @Override + public ErrorCode begin(L2NetworkInventory inventory, NetworkDeletionContext context) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + return controllerL2 == null ? confirmedDeleteControllerMissing(inventory) + : controllerL2.beginConfirmedDelete(inventory, context); + } + + @Override + public ErrorCode check(L2NetworkInventory inventory) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + return controllerL2 == null ? confirmedDeleteControllerMissing(inventory) + : controllerL2.checkConfirmedDelete(inventory); + } + + @Override + public ErrorCode check(L2NetworkInventory inventory, NetworkDeletionContext context) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + return controllerL2 == null ? confirmedDeleteControllerMissing(inventory) + : controllerL2.checkConfirmedDelete(inventory, context); + } + + @Override + public ErrorCode delete(L2NetworkInventory inventory) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + return controllerL2 == null ? confirmedDeleteControllerMissing(inventory) + : controllerL2.completeConfirmedDelete(inventory); + } + + @Override + public ErrorCode delete(L2NetworkInventory inventory, NetworkDeletionContext context) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + return controllerL2 == null ? confirmedDeleteControllerMissing(inventory) + : controllerL2.completeConfirmedDelete(inventory, context); + } + + @Override + public ErrorCode cancel(L2NetworkInventory inventory) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + return controllerL2 == null ? confirmedDeleteControllerMissing(inventory) + : controllerL2.cancelConfirmedDelete(inventory); + } + + @Override + public ErrorCode cancel(L2NetworkInventory inventory, NetworkDeletionContext context) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + return controllerL2 == null ? confirmedDeleteControllerMissing(inventory) + : controllerL2.cancelConfirmedDelete(inventory, context); + } + + private ErrorCode confirmedDeleteControllerMissing(L2NetworkInventory inventory) { + return operr(ORG_ZSTACK_SDNCONTROLLER_10034, + "cannot find sdn controller for confirmed L2Network deletion[uuid:%s, vswitchType:%s]", + inventory.getUuid(), inventory.getvSwitchType()); + } + + @Override + public void deleteLocalMetadata(L2NetworkInventory inventory) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + if (controllerL2 == null) { + throw new CloudRuntimeException(String.format( + "cannot find sdn controller for confirmed l2 network deletion[uuid:%s]", inventory.getUuid())); + } + controllerL2.deleteConfirmedLocalMetadata(inventory); + } + + @Override + public void deleteLocalMetadata(L2NetworkInventory inventory, NetworkDeletionContext context) { + SdnControllerL2 controllerL2 = findSdnControllerL2(inventory); + if (controllerL2 == null) { + throw new CloudRuntimeException(String.format( + "cannot find sdn controller for confirmed l2 network deletion[uuid:%s]", inventory.getUuid())); + } + controllerL2.deleteConfirmedLocalMetadata(inventory, context); + } + + private SdnControllerL2 findSdnControllerL2(L2NetworkInventory inv) { + VSwitchType vSwitchType = VSwitchType.valueOf(inv.getvSwitchType()); + if (vSwitchType.getSdnControllerType() == null) { + return null; + } + SdnControllerFactory factory = getSdnControllerFactory(vSwitchType.getSdnControllerType()); + return factory == null ? null : factory.getSdnControllerL2(inv.getUuid()); + } + @Override public void afterDeleteL2Network(L2NetworkInventory inventory) { diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerEvent.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerEvent.java new file mode 100644 index 00000000000..f905b260bff --- /dev/null +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerEvent.java @@ -0,0 +1,10 @@ +package org.zstack.sdnController.header; + +import org.zstack.header.message.APIEvent; +import org.zstack.header.rest.RestResponse; + +@RestResponse +public class APIPullSdnControllerEvent extends APIEvent { + public APIPullSdnControllerEvent() { } + public APIPullSdnControllerEvent(String apiId) { super(apiId); } +} diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerEventDoc_zh_cn.groovy b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIPullSdnControllerEventDoc_zh_cn.groovy new file mode 100644 index 00000000000..3713f6e9f73 --- /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 "在这里输入结构的名称" + + 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..41c4bc28ab8 --- /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 "未知类别" + + desc """在这里填写API描述""" + + rest { + request { + url "PUT /v1/sdn-controllers/{uuid}/resources/actions" + + header (Authorization: 'OAuth the-session-uuid') + + clz APIPullSdnControllerMsg.class + + desc """""" + + params { + + column { + name "uuid" + enclosedIn "pullSdnController" + desc "资源的UUID,唯一标示该资源" + location "url" + type "String" + optional false + since "5.5.38" + } + column { + name "resourceType" + enclosedIn "pullSdnController" + desc "" + location "body" + type "String" + optional false + since "5.5.38" + values ("Segment","TenantRouter") + } + column { + name "resourceUuids" + enclosedIn "pullSdnController" + desc "" + location "body" + type "List" + optional true + since "5.5.38" + } + column { + name "systemTags" + enclosedIn "" + desc "系统标签" + location "body" + type "List" + optional true + since "5.5.38" + } + column { + name "userTags" + enclosedIn "" + desc "用户标签" + location "body" + type "List" + optional true + since "5.5.38" + } + } + } + + response { + clz APIPullSdnControllerEvent.class + } + } +} \ No newline at end of file diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/header/PullSdnControllerMsg.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/PullSdnControllerMsg.java new file mode 100644 index 00000000000..603ec9913e7 --- /dev/null +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/PullSdnControllerMsg.java @@ -0,0 +1,27 @@ +package org.zstack.sdnController.header; + +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.network.sdncontroller.SdnControllerMessage; + +import java.util.List; + +public class PullSdnControllerMsg extends NeedReplyMessage implements SdnControllerMessage { + private String sdnControllerUuid; + private String resourceType; + private List resourceUuids; + + public static PullSdnControllerMsg fromApi(APIPullSdnControllerMsg api) { + PullSdnControllerMsg msg = new PullSdnControllerMsg(); + msg.setSdnControllerUuid(api.getSdnControllerUuid()); + msg.setResourceType(api.getResourceType()); + msg.setResourceUuids(api.getResourceUuids()); + return msg; + } + + @Override public String getSdnControllerUuid() { return sdnControllerUuid; } + public void setSdnControllerUuid(String value) { sdnControllerUuid = value; } + public String getResourceType() { return resourceType; } + public void setResourceType(String value) { resourceType = value; } + public List getResourceUuids() { return resourceUuids; } + public void setResourceUuids(List value) { resourceUuids = value; } +} diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/header/PullSdnControllerReply.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/PullSdnControllerReply.java new file mode 100644 index 00000000000..6dc07d6e0c9 --- /dev/null +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/header/PullSdnControllerReply.java @@ -0,0 +1,6 @@ +package org.zstack.sdnController.header; + +import org.zstack.header.message.MessageReply; + +public class PullSdnControllerReply extends MessageReply { +} diff --git a/sdk/src/main/java/SourceClassMap.java b/sdk/src/main/java/SourceClassMap.java index 29c9197c37d..0d503dedd9a 100644 --- a/sdk/src/main/java/SourceClassMap.java +++ b/sdk/src/main/java/SourceClassMap.java @@ -636,6 +636,8 @@ public class SourceClassMap { put("org.zstack.network.service.virtualrouter.VirtualRouterVmInventory", "org.zstack.sdk.VirtualRouterVmInventory"); put("org.zstack.network.zns.L2GeneveNetworkInventory", "org.zstack.sdk.network.zns.L2GeneveNetworkInventory"); put("org.zstack.network.zns.ZnsControllerInventory", "org.zstack.sdk.network.zns.ZnsControllerInventory"); + put("org.zstack.network.zns.ZnsSegmentCloudProjectionInventory", "org.zstack.sdk.network.zns.ZnsSegmentCloudProjectionInventory"); + put("org.zstack.network.zns.ZnsSegmentSyncOperationInventory", "org.zstack.sdk.network.zns.ZnsSegmentSyncOperationInventory"); put("org.zstack.network.zns.ZnsTenantInventory", "org.zstack.sdk.network.zns.ZnsTenantInventory"); put("org.zstack.network.zns.ZnsTenantRouterInventory", "org.zstack.sdk.network.zns.ZnsTenantRouterInventory"); put("org.zstack.network.zns.ZnsTransportZoneInventory", "org.zstack.sdk.network.zns.ZnsTransportZoneInventory"); @@ -1746,6 +1748,8 @@ public class SourceClassMap { put("org.zstack.sdk.license.header.server.TotalLicenseAuthorizedCapacityView", "org.zstack.license.header.server.TotalLicenseAuthorizedCapacityView"); put("org.zstack.sdk.network.zns.L2GeneveNetworkInventory", "org.zstack.network.zns.L2GeneveNetworkInventory"); put("org.zstack.sdk.network.zns.ZnsControllerInventory", "org.zstack.network.zns.ZnsControllerInventory"); + put("org.zstack.sdk.network.zns.ZnsSegmentCloudProjectionInventory", "org.zstack.network.zns.ZnsSegmentCloudProjectionInventory"); + put("org.zstack.sdk.network.zns.ZnsSegmentSyncOperationInventory", "org.zstack.network.zns.ZnsSegmentSyncOperationInventory"); put("org.zstack.sdk.network.zns.ZnsTenantInventory", "org.zstack.network.zns.ZnsTenantInventory"); put("org.zstack.sdk.network.zns.ZnsTenantRouterInventory", "org.zstack.network.zns.ZnsTenantRouterInventory"); put("org.zstack.sdk.network.zns.ZnsTransportZoneInventory", "org.zstack.network.zns.ZnsTransportZoneInventory"); diff --git a/sdk/src/main/java/org/zstack/sdk/PullSdnControllerAction.java b/sdk/src/main/java/org/zstack/sdk/PullSdnControllerAction.java new file mode 100644 index 00000000000..b82f48c573a --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/PullSdnControllerAction.java @@ -0,0 +1,107 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class PullSdnControllerAction extends AbstractAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.PullSdnControllerResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + return this; + } + } + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String uuid; + + @Param(required = true, validValues = {"Segment","TenantRouter"}, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String resourceType; + + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.util.List resourceUuids; + + @Param(required = false) + public java.util.List systemTags; + + @Param(required = false) + public java.util.List userTags; + + @Param(required = false) + public String sessionId; + + @Param(required = false) + public String accessKeyId; + + @Param(required = false) + public String accessKeySecret; + + @Param(required = false) + public String requestIp; + + @NonAPIParam + public long timeout = -1; + + @NonAPIParam + public long pollingInterval = -1; + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.PullSdnControllerResult value = res.getResult(org.zstack.sdk.PullSdnControllerResult.class); + ret.value = value == null ? new org.zstack.sdk.PullSdnControllerResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "PUT"; + info.path = "/sdn-controllers/{uuid}/resources/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "pullSdnController"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/PullSdnControllerResult.java b/sdk/src/main/java/org/zstack/sdk/PullSdnControllerResult.java new file mode 100644 index 00000000000..7048752ca90 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/PullSdnControllerResult.java @@ -0,0 +1,7 @@ +package org.zstack.sdk; + + + +public class PullSdnControllerResult { + +} diff --git a/sdk/src/main/java/org/zstack/sdk/network/zns/QueryZnsSegmentCloudProjectionAction.java b/sdk/src/main/java/org/zstack/sdk/network/zns/QueryZnsSegmentCloudProjectionAction.java new file mode 100644 index 00000000000..2a63d4c13d6 --- /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/rest/RestFacadeCase.groovy b/test/src/test/groovy/org/zstack/test/integration/core/rest/RestFacadeCase.groovy index 1e0f335f146..6af93cfba86 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,15 @@ package org.zstack.test.integration.core.rest import org.springframework.http.HttpEntity +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpMethod +import org.springframework.http.ResponseEntity import org.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.test.core.rest.RESTBeanForTest import org.zstack.test.integration.ZStackTest import org.zstack.testlib.EnvSpec @@ -40,9 +44,26 @@ class RestFacadeCase extends SubCase { void test() { env.create { testRestFacadeFailureWillBeRecorded() + testSyncHandlerWritesResponseBody() } } + void testSyncHandlerWritesResponseBody() { + RESTFacadeImpl restf = bean(RESTFacadeImpl.class) + String commandPath = "/test-rest-facade-response-body" + String body = '{"result":"ok"}' + restf.registerSyncHttpCallHandler(commandPath, Map.class) { Map ignored -> body } + + HttpHeaders headers = new HttpHeaders() + headers.set(RESTConstant.COMMAND_PATH, commandPath) + ResponseEntity response = restf.getRESTTemplate().exchange(restf.getSendCommandUrl(), + HttpMethod.POST, new HttpEntity("{}", headers), String.class) + + assert response.statusCodeValue == 200 + assert response.body == body + assert response.headers.getFirst(HttpHeaders.CONTENT_TYPE).startsWith("application/json") + } + void testRestFacadeFailureWillBeRecorded() { RESTFacadeImpl restf = bean(RESTFacadeImpl.class) diff --git a/test/src/test/groovy/org/zstack/test/integration/network/l2network/AttachL2NetworkCase.groovy b/test/src/test/groovy/org/zstack/test/integration/network/l2network/AttachL2NetworkCase.groovy index 7d5e8bbfa1d..4c6637d5592 100644 --- a/test/src/test/groovy/org/zstack/test/integration/network/l2network/AttachL2NetworkCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/network/l2network/AttachL2NetworkCase.groovy @@ -97,6 +97,7 @@ public class AttachL2NetworkCase extends SubCase{ testCreateL2NetworkWithoutPhysicalInterface() testAttachL2NoVlanNetwork() testAttachL2ValnNetwork() + testAttachL2NetworkReturnsFailureWhenAdmissionThrows() testAttachL2NoVlanNetworkSynchronously() testAttachL2VlanNetworkSynchronously() testAddHost() diff --git a/test/src/test/groovy/org/zstack/test/integration/network/l2network/L2NetworkCascadeCase.groovy b/test/src/test/groovy/org/zstack/test/integration/network/l2network/L2NetworkCascadeCase.groovy new file mode 100644 index 00000000000..fe5e780e636 --- /dev/null +++ b/test/src/test/groovy/org/zstack/test/integration/network/l2network/L2NetworkCascadeCase.groovy @@ -0,0 +1,361 @@ +package org.zstack.test.integration.network.l2network + +import org.zstack.core.componentloader.PluginRegistry +import org.zstack.core.cloudbus.CloudBus +import org.zstack.core.db.DatabaseFacade +import org.zstack.header.errorcode.ErrorCode +import org.zstack.header.message.AbstractBeforeDeliveryMessageInterceptor +import org.zstack.header.message.Message +import org.zstack.header.network.l2.L2DeleteConfirmExtensionPoint +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.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.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 + +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" + } + } + } + } + + @Override + void test() { + env.create { + dbf = bean(DatabaseFacade.class) + testFenceExistsBeforeChildCheck() + testDeleteZoneCancelsEveryConfirmedDeleteOnCheckFailure() + testWholeL2ContextPropagatesToChildren() + testParentForceChecksBeforeDeletingChildren() + testParentForcePreservesProviderError() + testUnsupportedProviderKeepsExistingDeleteBehavior() + testDeleteL2NetworkRemovesConfirmedMetadataOnce() + } + } + + void testFenceExistsBeforeChildCheck() { + ZoneInventory zone = env.inventoryByName("check-zone") + List events = [] + def confirmation = new RecordingDeleteConfirmation(events: events) + def childCheck = new RecordingL3DeleteCheck(events: events, failCheck: true) + List confirmations = bean(PluginRegistry.class) + .getExtensionList(L2DeleteConfirmExtensionPoint.class) + List childChecks = bean(PluginRegistry.class) + .getExtensionList(L3NetworkDeleteExtensionPoint.class) + confirmations.add(confirmation) + childChecks.add(childCheck) + + try { + expectError { + deleteZone { + uuid = zone.uuid + } + } + } finally { + childChecks.remove(childCheck) + confirmations.remove(confirmation) + } + + assert events.findIndexOf { it.startsWith("child-check:") } > + events.findLastIndexOf { it.startsWith("begin:") } + } + + void testDeleteZoneCancelsEveryConfirmedDeleteOnCheckFailure() { + ZoneInventory zone = env.inventoryByName("check-zone") + def extension = new RecordingDeleteConfirmation(failOnSecondCheck: true) + List extensions = bean(PluginRegistry.class) + .getExtensionList(L2DeleteConfirmExtensionPoint.class) + extensions.add(extension) + + try { + expectError { + deleteZone { + uuid = zone.uuid + } + } + } finally { + extensions.remove(extension) + } + + assert extension.begun.size() == 2 + assert extension.cancelled == extension.begun.toList().reverse() + assert dbf.isExist(zone.uuid, org.zstack.header.zone.ZoneVO.class) + assert extension.begun.every { dbf.isExist(it, L2NetworkVO.class) } + } + + void 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] + } + + 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() + } + + @Override + void clean() { + env.delete() + } + + private static class RecordingDeleteConfirmation implements L2DeleteConfirmExtensionPoint { + boolean supported = true + boolean failOnSecondCheck + boolean failCheck + boolean failDelete + int checkCount + List events = [] + List begun = [] + List cancelled = [] + List localMetadataDeletes = [] + List operationUuids = [] + List forceDeleteFlags = [] + + @Override + boolean supports(L2NetworkInventory inventory) { + return supported + } + + @Override + ErrorCode begin(L2NetworkInventory inventory) { + begun.add(inventory.uuid) + events.add("begin:${inventory.uuid}") + return null + } + + @Override + ErrorCode begin(L2NetworkInventory inventory, NetworkDeletionContext context) { + operationUuids.add(context.operationUuid) + forceDeleteFlags.add(context.forceDelete) + return begin(inventory) + } + + @Override + ErrorCode check(L2NetworkInventory inventory) { + checkCount++ + return failCheck || failOnSecondCheck && checkCount == 2 ? + new ErrorCode("TEST", "simulated confirmation failure") : null + } + + @Override + ErrorCode delete(L2NetworkInventory inventory) { + return failDelete ? new ErrorCode("TEST", "simulated provider delete failure") : null + } + + @Override + ErrorCode cancel(L2NetworkInventory inventory) { + cancelled.add(inventory.uuid) + return null + } + + @Override + void deleteLocalMetadata(L2NetworkInventory inventory) { + localMetadataDeletes.add(inventory.uuid) + } + } + + private static class 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) { + } + } +} diff --git a/test/src/test/java/org/zstack/test/network/TestSdnControllerL3.java b/test/src/test/java/org/zstack/test/network/TestSdnControllerL3.java new file mode 100644 index 00000000000..add09019ca8 --- /dev/null +++ b/test/src/test/java/org/zstack/test/network/TestSdnControllerL3.java @@ -0,0 +1,52 @@ +package org.zstack.test.network; + +import org.junit.Assert; +import org.junit.Test; +import org.zstack.header.core.Completion; +import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.network.l3.IpRangeInventory; +import org.zstack.header.network.l3.L3NetworkInventory; +import org.zstack.header.network.l3.SdnControllerL3; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +public class TestSdnControllerL3 { + @Test + public void testDeleteLastIpRangePropagatesControllerFailure() { + ErrorCode expected = new ErrorCode("SYS.1006", "Operation Error", "controller failed"); + AtomicReference actual = new AtomicReference<>(); + SdnControllerL3 controller = new SdnControllerL3() { + @Override + public void createL3Network(L3NetworkInventory inv, List systemTags, Completion completion) { + } + + @Override + public void deleteL3Network(L3NetworkInventory inv, Completion completion) { + } + + @Override + public void createIpRange(IpRangeInventory inv, Completion completion) { + } + + @Override + public void deleteIpRange(IpRangeInventory inv, Completion completion) { + completion.fail(expected); + } + }; + + controller.deleteIpRange(null, true, new Completion(null) { + @Override + public void success() { + Assert.fail("controller failure must not be converted to success"); + } + + @Override + public void fail(ErrorCode errorCode) { + actual.set(errorCode); + } + }); + + Assert.assertSame(expected, actual.get()); + } +} diff --git a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy index 74c3ac573dc..c90481f499e 100644 --- a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy +++ b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy @@ -48,20 +48,20 @@ abstract class ApiHelper { 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()) @@ -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 @@ -25430,20 +25457,20 @@ abstract class ApiHelper { 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()) @@ -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 @@ -48552,6 +48606,7 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() + if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -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 @@ -54914,19 +55025,20 @@ abstract class ApiHelper { 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()) @@ -56233,7 +56345,7 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + a.conditions = a.conditions.collect { it.toString() } @@ -56241,14 +56353,14 @@ abstract class ApiHelper { 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()) 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..73e02694bd8 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,15 @@ 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_SNS_PLATFORM_UNIVERSALSMS_SUPPLIER_EMAY_10000 = "ORG_ZSTACK_SNS_PLATFORM_UNIVERSALSMS_SUPPLIER_EMAY_10000"; @@ -1044,6 +1053,8 @@ 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_CONSOLE_10000 = "ORG_ZSTACK_CONSOLE_10000"; public static final String ORG_ZSTACK_CONSOLE_10001 = "ORG_ZSTACK_CONSOLE_10001"; @@ -12240,6 +12251,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 +12266,70 @@ 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_PREMIUM_EXTERNALSERVICE_MARKETPLACE_10000 = "ORG_ZSTACK_PREMIUM_EXTERNALSERVICE_MARKETPLACE_10000"; @@ -15741,6 +15817,8 @@ 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_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";