diff --git a/conf/db/upgrade/V5.5.12__schema.sql b/conf/db/upgrade/V5.5.12__schema.sql index 6c6168445f..c0bd4cc0eb 100644 --- a/conf/db/upgrade/V5.5.12__schema.sql +++ b/conf/db/upgrade/V5.5.12__schema.sql @@ -177,4 +177,21 @@ END $$ DELIMITER ; CALL ModifyUsedIpVOForeignKey(); -DROP PROCEDURE IF EXISTS ModifyUsedIpVOForeignKey; \ No newline at end of file +DROP PROCEDURE IF EXISTS ModifyUsedIpVOForeignKey; + +-- ZSTAC-83157: Add VM model mount feature +CREATE TABLE IF NOT EXISTS `zstack`.`VmModelMountVO` ( + `uuid` VARCHAR(32) NOT NULL, + `vmInstanceUuid` VARCHAR(32) NOT NULL, + `modelUuid` VARCHAR(32) NOT NULL, + `modelName` VARCHAR(256) NOT NULL, + `juicefsSubdir` VARCHAR(512) NOT NULL, + `mountPath` VARCHAR(512) NOT NULL, + `status` VARCHAR(32) NOT NULL, + `createDate` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', + `lastOpDate` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`uuid`), + KEY `idx_vm` (`vmInstanceUuid`), + CONSTRAINT `fk_VmModelMount_vm` FOREIGN KEY (`vmInstanceUuid`) + REFERENCES `VmInstanceEO`(`uuid`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; \ No newline at end of file diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java index 2a36bb5aba..699c8bb77d 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java @@ -5205,4 +5205,96 @@ public void setMemoryUsage(long memoryUsage) { } } + public static class KvmAttachModelCmd extends AgentCommand { + @GrayVersion(value = "5.5.12") + private String vmInstanceUuid; + @GrayVersion(value = "5.5.12") + private String zdfsUrl; + @GrayVersion(value = "5.5.12") + private String juicefsSubdir; + @GrayVersion(value = "5.5.12") + private String mountPath; + + public String getVmInstanceUuid() { + return vmInstanceUuid; + } + + public void setVmInstanceUuid(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + + public String getZdfsUrl() { + return zdfsUrl; + } + + public void setZdfsUrl(String zdfsUrl) { + this.zdfsUrl = zdfsUrl; + } + + public String getJuicefsSubdir() { + return juicefsSubdir; + } + + public void setJuicefsSubdir(String juicefsSubdir) { + this.juicefsSubdir = juicefsSubdir; + } + + public String getMountPath() { + return mountPath; + } + + public void setMountPath(String mountPath) { + this.mountPath = mountPath; + } + } + + public static class KvmAttachModelResponse extends AgentResponse { + @GrayVersion(value = "5.5.12") + private String status; + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + } + + public static class KvmDetachModelCmd extends AgentCommand { + @GrayVersion(value = "5.5.12") + private String vmInstanceUuid; + @GrayVersion(value = "5.5.12") + private String mountPath; + + public String getVmInstanceUuid() { + return vmInstanceUuid; + } + + public void setVmInstanceUuid(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + + public String getMountPath() { + return mountPath; + } + + public void setMountPath(String mountPath) { + this.mountPath = mountPath; + } + } + + public static class KvmDetachModelResponse extends AgentResponse { + @GrayVersion(value = "5.5.12") + private String status; + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + } + } diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java index 1b2df9f8f2..7c6b676e84 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java @@ -95,6 +95,8 @@ public interface KVMConstant { String KVM_REGISTER_PRIMARY_VM_HEARTBEAT = "/register/primary/vm/heartbeat"; String CLEAN_FIRMWARE_FLASH = "/clean/firmware/flash"; String FSTRIM_VM_PATH = "/vm/fstrim"; + String KVM_ATTACH_AI_MODEL_PATH = "/aimodel/attach"; + String KVM_DETACH_AI_MODEL_PATH = "/aimodel/detach"; String ISO_TO = "kvm.isoto"; String ANSIBLE_PLAYBOOK_NAME = "kvm.py"; diff --git a/sdk/src/main/java/SourceClassMap.java b/sdk/src/main/java/SourceClassMap.java index 614350ecd6..b6bc4a611a 100644 --- a/sdk/src/main/java/SourceClassMap.java +++ b/sdk/src/main/java/SourceClassMap.java @@ -25,6 +25,7 @@ public class SourceClassMap { put("org.zstack.ai.entity.ModelServiceRefInventory", "org.zstack.sdk.ModelServiceRefInventory"); put("org.zstack.ai.entity.ModelServiceTemplateInventory", "org.zstack.sdk.ModelServiceTemplateInventory"); put("org.zstack.ai.entity.TrainedModelRecordInventory", "org.zstack.sdk.TrainedModelRecordInventory"); + put("org.zstack.ai.entity.VmModelMountInventory", "org.zstack.sdk.VmModelMountInventory"); put("org.zstack.ai.message.ArchitectureImageMapping", "org.zstack.sdk.ArchitectureImageMapping"); put("org.zstack.ai.message.MaaSUsage", "org.zstack.sdk.MaaSUsage"); put("org.zstack.ai.message.ModelCenterServiceInventory", "org.zstack.sdk.ModelCenterServiceInventory"); @@ -1549,6 +1550,7 @@ public class SourceClassMap { put("org.zstack.sdk.VmInstancePciDeviceSpecRefInventory", "org.zstack.pciDevice.specification.pci.VmInstancePciDeviceSpecRefInventory"); put("org.zstack.sdk.VmMemoryBillingInventory", "org.zstack.billing.generator.vm.memory.VmMemoryBillingInventory"); put("org.zstack.sdk.VmMemorySpendingDetails", "org.zstack.billing.spendingcalculator.vm.VmMemorySpendingDetails"); + put("org.zstack.sdk.VmModelMountInventory", "org.zstack.ai.entity.VmModelMountInventory"); put("org.zstack.sdk.VmNicBandwidthSpendingDetails", "org.zstack.billing.spendingcalculator.vmnic.VmNicBandwidthSpendingDetails"); put("org.zstack.sdk.VmNicInventory", "org.zstack.header.vm.VmNicInventory"); put("org.zstack.sdk.VmNicSecurityGroupRefInventory", "org.zstack.network.securitygroup.VmNicSecurityGroupRefInventory"); diff --git a/sdk/src/main/java/org/zstack/sdk/MountModelToVmInstanceAction.java b/sdk/src/main/java/org/zstack/sdk/MountModelToVmInstanceAction.java new file mode 100644 index 0000000000..6fc0a8eff4 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/MountModelToVmInstanceAction.java @@ -0,0 +1,107 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class MountModelToVmInstanceAction 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.MountModelToVmInstanceResult 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 vmInstanceUuid; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String modelUuid; + + @Param(required = false, maxLength = 512, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String mountPath; + + @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.MountModelToVmInstanceResult value = res.getResult(org.zstack.sdk.MountModelToVmInstanceResult.class); + ret.value = value == null ? new org.zstack.sdk.MountModelToVmInstanceResult() : 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 = "/ai/models/{modelUuid}/vm-instances/{vmInstanceUuid}/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "mountModelToVmInstance"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/MountModelToVmInstanceResult.java b/sdk/src/main/java/org/zstack/sdk/MountModelToVmInstanceResult.java new file mode 100644 index 0000000000..0feb080e65 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/MountModelToVmInstanceResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + +import org.zstack.sdk.VmModelMountInventory; + +public class MountModelToVmInstanceResult { + public VmModelMountInventory inventory; + public void setInventory(VmModelMountInventory inventory) { + this.inventory = inventory; + } + public VmModelMountInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/QueryVmModelMountAction.java b/sdk/src/main/java/org/zstack/sdk/QueryVmModelMountAction.java new file mode 100644 index 0000000000..e7460bafa8 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/QueryVmModelMountAction.java @@ -0,0 +1,75 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class QueryVmModelMountAction 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.QueryVmModelMountResult 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.QueryVmModelMountResult value = res.getResult(org.zstack.sdk.QueryVmModelMountResult.class); + ret.value = value == null ? new org.zstack.sdk.QueryVmModelMountResult() : 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 = "/ai/vm-model-mounts"; + info.needSession = true; + info.needPoll = false; + info.parameterName = ""; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/QueryVmModelMountResult.java b/sdk/src/main/java/org/zstack/sdk/QueryVmModelMountResult.java new file mode 100644 index 0000000000..de488a4a8a --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/QueryVmModelMountResult.java @@ -0,0 +1,22 @@ +package org.zstack.sdk; + + + +public class QueryVmModelMountResult { + 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/UnmountModelFromVmInstanceAction.java b/sdk/src/main/java/org/zstack/sdk/UnmountModelFromVmInstanceAction.java new file mode 100644 index 0000000000..ef85a9921a --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/UnmountModelFromVmInstanceAction.java @@ -0,0 +1,104 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class UnmountModelFromVmInstanceAction 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.UnmountModelFromVmInstanceResult 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 vmInstanceUuid; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String modelUuid; + + @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.UnmountModelFromVmInstanceResult value = res.getResult(org.zstack.sdk.UnmountModelFromVmInstanceResult.class); + ret.value = value == null ? new org.zstack.sdk.UnmountModelFromVmInstanceResult() : 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 = "/ai/models/{modelUuid}/vm-instances/{vmInstanceUuid}/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "unmountModelFromVmInstance"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/UnmountModelFromVmInstanceResult.java b/sdk/src/main/java/org/zstack/sdk/UnmountModelFromVmInstanceResult.java new file mode 100644 index 0000000000..736cdec965 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/UnmountModelFromVmInstanceResult.java @@ -0,0 +1,7 @@ +package org.zstack.sdk; + + + +public class UnmountModelFromVmInstanceResult { + +} diff --git a/sdk/src/main/java/org/zstack/sdk/VmModelMountInventory.java b/sdk/src/main/java/org/zstack/sdk/VmModelMountInventory.java new file mode 100644 index 0000000000..aec3d82b0d --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/VmModelMountInventory.java @@ -0,0 +1,79 @@ +package org.zstack.sdk; + + + +public class VmModelMountInventory { + + 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 vmInstanceUuid; + public void setVmInstanceUuid(java.lang.String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + public java.lang.String getVmInstanceUuid() { + return this.vmInstanceUuid; + } + + public java.lang.String modelUuid; + public void setModelUuid(java.lang.String modelUuid) { + this.modelUuid = modelUuid; + } + public java.lang.String getModelUuid() { + return this.modelUuid; + } + + public java.lang.String modelName; + public void setModelName(java.lang.String modelName) { + this.modelName = modelName; + } + public java.lang.String getModelName() { + return this.modelName; + } + + public java.lang.String juicefsSubdir; + public void setJuicefsSubdir(java.lang.String juicefsSubdir) { + this.juicefsSubdir = juicefsSubdir; + } + public java.lang.String getJuicefsSubdir() { + return this.juicefsSubdir; + } + + public java.lang.String mountPath; + public void setMountPath(java.lang.String mountPath) { + this.mountPath = mountPath; + } + public java.lang.String getMountPath() { + return this.mountPath; + } + + public java.lang.String status; + public void setStatus(java.lang.String status) { + this.status = status; + } + public java.lang.String getStatus() { + return this.status; + } + + 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; + } + +} diff --git a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy index 8a8f2b9141..504a64984f 100644 --- a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy +++ b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy @@ -962,8 +962,8 @@ abstract class ApiHelper { } - def addBareMetal2Gateway(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.AddBareMetal2GatewayAction.class) Closure c) { - def a = new org.zstack.sdk.AddBareMetal2GatewayAction() + def addBareMetal2DpuChassis(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.AddBareMetal2DpuChassisAction.class) Closure c) { + def a = new org.zstack.sdk.AddBareMetal2DpuChassisAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -989,8 +989,8 @@ abstract class ApiHelper { } - def addBareMetal2IpmiChassis(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.AddBareMetal2IpmiChassisAction.class) Closure c) { - def a = new org.zstack.sdk.AddBareMetal2IpmiChassisAction() + def addBareMetal2Gateway(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.AddBareMetal2GatewayAction.class) Closure c) { + def a = new org.zstack.sdk.AddBareMetal2GatewayAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -1016,26 +1016,26 @@ abstract class ApiHelper { } - def addBareMetal2DpuChassis(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.AddBareMetal2DpuChassisAction.class) Closure c) { - def a = new org.zstack.sdk.AddBareMetal2DpuChassisAction() + def addBareMetal2IpmiChassis(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.AddBareMetal2IpmiChassisAction.class) Closure c) { + def a = new org.zstack.sdk.AddBareMetal2IpmiChassisAction() 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()) @@ -27584,6 +27584,33 @@ abstract class ApiHelper { } + def mountModelToVmInstance(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.MountModelToVmInstanceAction.class) Closure c) { + def a = new org.zstack.sdk.MountModelToVmInstanceAction() + 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 mountVmInstanceRecoveryPoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.MountVmInstanceRecoveryPointAction.class) Closure c) { def a = new org.zstack.sdk.MountVmInstanceRecoveryPointAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -35992,6 +36019,35 @@ abstract class ApiHelper { } + def queryVmModelMount(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.QueryVmModelMountAction.class) Closure c) { + def a = new org.zstack.sdk.QueryVmModelMountAction() + 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 queryVmNic(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.QueryVmNicAction.class) Closure c) { def a = new org.zstack.sdk.QueryVmNicAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -42347,6 +42403,33 @@ abstract class ApiHelper { } + def unmountModelFromVmInstance(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UnmountModelFromVmInstanceAction.class) Closure c) { + def a = new org.zstack.sdk.UnmountModelFromVmInstanceAction() + 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 unmountVmInstanceRecoveryPoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UnmountVmInstanceRecoveryPointAction.class) Closure c) { def a = new org.zstack.sdk.UnmountVmInstanceRecoveryPointAction() a.sessionId = Test.currentEnvSpec?.session?.uuid