usedIps;
private String internalName;
private Integer deviceId;
@@ -223,6 +225,14 @@ public void setDriverType(String driverType) {
this.driverType = driverType;
}
+ public String getInterfaceId() {
+ return interfaceId;
+ }
+
+ public void setInterfaceId(String interfaceId) {
+ this.interfaceId = interfaceId;
+ }
+
public String getType() {
return type;
}
diff --git a/header/src/main/java/org/zstack/header/vm/VmNicLifecycleContext.java b/header/src/main/java/org/zstack/header/vm/VmNicLifecycleContext.java
new file mode 100644
index 00000000000..3300b17e33a
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/VmNicLifecycleContext.java
@@ -0,0 +1,60 @@
+package org.zstack.header.vm;
+
+import org.zstack.header.vm.VmInstanceConstant.VmOperation;
+
+import java.util.Objects;
+
+public class VmNicLifecycleContext {
+ private VmOperation operation;
+ private String vmUuid;
+ private String srcHostUuid;
+ private String destHostUuid;
+ private String lastHostUuid;
+
+ public VmOperation getOperation() {
+ return operation;
+ }
+
+ public void setOperation(VmOperation operation) {
+ this.operation = operation;
+ }
+
+ public String getVmUuid() {
+ return vmUuid;
+ }
+
+ public void setVmUuid(String vmUuid) {
+ this.vmUuid = vmUuid;
+ }
+
+ public String getSrcHostUuid() {
+ return srcHostUuid;
+ }
+
+ public void setSrcHostUuid(String srcHostUuid) {
+ this.srcHostUuid = srcHostUuid;
+ }
+
+ public String getDestHostUuid() {
+ return destHostUuid;
+ }
+
+ public void setDestHostUuid(String destHostUuid) {
+ this.destHostUuid = destHostUuid;
+ }
+
+ public String getLastHostUuid() {
+ return lastHostUuid;
+ }
+
+ public void setLastHostUuid(String lastHostUuid) {
+ this.lastHostUuid = lastHostUuid;
+ }
+
+ public boolean isStartWithChangedHost() {
+ return operation == VmOperation.Start
+ && lastHostUuid != null
+ && destHostUuid != null
+ && !Objects.equals(lastHostUuid, destHostUuid);
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/VmNicLifecycleExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/VmNicLifecycleExtensionPoint.java
new file mode 100644
index 00000000000..a90b790366d
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/VmNicLifecycleExtensionPoint.java
@@ -0,0 +1,121 @@
+package org.zstack.header.vm;
+
+import org.zstack.header.core.Completion;
+import org.zstack.header.core.NoErrorCompletion;
+
+import java.util.List;
+
+/**
+ * Extension point for managing the lifecycle of VM NICs on hosts.
+ * Implementations are invoked during VM start, stop, migration, NIC attach/detach,
+ * and periodic reconciliation driven by KVM heartbeat.
+ *
+ * All async methods must invoke their completion callback exactly once,
+ * even on error paths. {@link Completion}-based methods may fail the operation;
+ * {@link NoErrorCompletion}-based methods must always succeed (log and absorb errors).
+ */
+public interface VmNicLifecycleExtensionPoint {
+
+ /**
+ * Returns true if this extension should manage the given NIC.
+ * Called synchronously; must not block or throw checked exceptions.
+ *
+ * @param nic the NIC to evaluate
+ * @return true if this extension handles the NIC
+ */
+ boolean isApplicable(VmNicInventory nic);
+
+ /**
+ * Called when a VM starts or a NIC is attached to a running VM.
+ * Failure aborts the VM start / NIC attach operation.
+ *
+ * @param hostUuid UUID of the host where the VM is starting
+ * @param nics NICs filtered by {@link #isApplicable}
+ * @param completion call {@code success()} or {@code fail()} exactly once
+ */
+ void setupOnHost(String hostUuid, List nics, Completion completion);
+
+ default void setupOnHost(VmNicLifecycleContext context, String hostUuid,
+ List nics, Completion completion) {
+ setupOnHost(hostUuid, nics, completion);
+ }
+
+ /**
+ * Called when a VM stops or a NIC is detached. Errors are logged but do not
+ * block the operation.
+ *
+ * @param hostUuid UUID of the host the VM is leaving
+ * @param nics NICs filtered by {@link #isApplicable}
+ * @param completion call {@code done()} exactly once
+ */
+ void cleanupFromHost(String hostUuid, List nics, NoErrorCompletion completion);
+
+ /**
+ * Called before live migration starts. Default: setup on destination host.
+ * Failure aborts the migration.
+ * Execution order: preMigrate → (live migration) → postMigrate or failedMigrate.
+ *
+ * @param srcHostUuid UUID of the source host
+ * @param destHostUuid UUID of the destination host
+ * @param nics NICs filtered by {@link #isApplicable}
+ * @param completion call {@code success()} or {@code fail()} exactly once
+ */
+ default void preMigrate(String srcHostUuid, String destHostUuid,
+ List nics, Completion completion) {
+ setupOnHost(destHostUuid, nics, completion);
+ }
+
+ /**
+ * Called after live migration succeeds. Errors are logged but do not block.
+ *
+ * @param srcHostUuid UUID of the source host
+ * @param destHostUuid UUID of the destination host
+ * @param nics NICs filtered by {@link #isApplicable}
+ * @param completion call {@code done()} exactly once
+ */
+ default void postMigrate(String srcHostUuid, String destHostUuid,
+ List nics, NoErrorCompletion completion) {
+ cleanupFromHost(srcHostUuid, nics, completion);
+ }
+
+ /**
+ * Called when live migration fails. Errors are logged but do not block.
+ *
+ * @param srcHostUuid UUID of the source host
+ * @param destHostUuid UUID of the destination host (where partial setup may exist)
+ * @param nics NICs filtered by {@link #isApplicable}
+ * @param completion call {@code done()} exactly once
+ */
+ default void failedMigrate(String srcHostUuid, String destHostUuid,
+ List nics, NoErrorCompletion completion) {
+ cleanupFromHost(destHostUuid, nics, completion);
+ }
+
+ /**
+ * Called on VM start when the VM's last known host differs from the destination host.
+ * Used to clean up state left on the previous host after an ungraceful shutdown.
+ * Errors are logged but do not block.
+ *
+ * @param lastHostUuid UUID of the host where the VM last ran
+ * @param nics NICs filtered by {@link #isApplicable}
+ * @param completion call {@code done()} exactly once
+ */
+ default void cleanupStaleResource(String lastHostUuid, List nics,
+ NoErrorCompletion completion) {
+ cleanupFromHost(lastHostUuid, nics, completion);
+ }
+
+ /**
+ * Called periodically on each successful KVM heartbeat to reconcile NIC state.
+ * Implementations should ensure remote systems match the expected state in {@code expectedNics}.
+ * Errors are logged but do not block the heartbeat.
+ *
+ * @param hostUuid UUID of the host being reconciled
+ * @param expectedNics all Running-VM NICs on this host, filtered by {@link #isApplicable}
+ * @param completion call {@code done()} exactly once
+ */
+ default void reconcileOnHost(String hostUuid, List expectedNics,
+ NoErrorCompletion completion) {
+ completion.done();
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/VmOvsNicConstant.java b/header/src/main/java/org/zstack/header/vm/VmOvsNicConstant.java
deleted file mode 100644
index 964a41e8861..00000000000
--- a/header/src/main/java/org/zstack/header/vm/VmOvsNicConstant.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.zstack.header.vm;
-
-import org.zstack.header.configuration.PythonClass;
-
-@PythonClass
-public class VmOvsNicConstant {
- public static final String ACCEL_TYPE_VDPA = "vDPA";
- public static final String ACCEL_TYPE_VHOST_USER_SPACE = "dpdkvhostuserclient";
-}
diff --git a/header/src/main/java/org/zstack/header/volume/APIReInitDataVolumeEvent.java b/header/src/main/java/org/zstack/header/volume/APIReInitDataVolumeEvent.java
new file mode 100644
index 00000000000..a5e1e57b908
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/volume/APIReInitDataVolumeEvent.java
@@ -0,0 +1,53 @@
+package org.zstack.header.volume;
+
+import org.zstack.header.message.APIEvent;
+import org.zstack.header.rest.RestResponse;
+import org.zstack.utils.data.SizeUnit;
+
+import java.sql.Timestamp;
+
+@RestResponse(allTo = "inventory")
+public class APIReInitDataVolumeEvent extends APIEvent {
+ private VolumeInventory inventory;
+
+ public void setInventory(VolumeInventory inventory) {
+ this.inventory = inventory;
+ }
+
+ public VolumeInventory getInventory() {
+ return inventory;
+ }
+
+ public APIReInitDataVolumeEvent() {
+ super();
+ }
+
+ public APIReInitDataVolumeEvent(String id) {
+ super(id);
+ }
+
+ public static APIReInitDataVolumeEvent __example__() {
+ APIReInitDataVolumeEvent event = new APIReInitDataVolumeEvent();
+
+ String volumeUuid = uuid();
+ VolumeInventory vol = new VolumeInventory();
+ vol.setName("test-data-volume");
+ vol.setCreateDate(new Timestamp(org.zstack.header.message.DocUtils.date));
+ vol.setLastOpDate(new Timestamp(org.zstack.header.message.DocUtils.date));
+ vol.setType(VolumeType.Data.toString());
+ vol.setUuid(volumeUuid);
+ vol.setSize(SizeUnit.GIGABYTE.toByte(100));
+ vol.setActualSize(SizeUnit.GIGABYTE.toByte(20));
+ vol.setDeviceId(1);
+ vol.setState(VolumeState.Enabled.toString());
+ vol.setFormat("raw");
+ vol.setDiskOfferingUuid(uuid());
+ vol.setInstallPath(String.format("/zstack_ps/dataVolumes/acct-36c27e8ff05c4780bf6d2fa65700f22e/vol-%s/%s.qcow2", volumeUuid, volumeUuid));
+ vol.setStatus(VolumeStatus.Ready.toString());
+ vol.setPrimaryStorageUuid(uuid());
+ vol.setVmInstanceUuid(uuid());
+
+ event.setInventory(vol);
+ return event;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/volume/APIReInitDataVolumeEventDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/volume/APIReInitDataVolumeEventDoc_zh_cn.groovy
new file mode 100644
index 00000000000..91b427d28ea
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/volume/APIReInitDataVolumeEventDoc_zh_cn.groovy
@@ -0,0 +1,32 @@
+package org.zstack.header.volume
+
+import org.zstack.header.volume.VolumeInventory
+import org.zstack.header.errorcode.ErrorCode
+
+doc {
+
+ title "重新初始化数据云盘事件"
+
+ ref {
+ name "inventory"
+ path "org.zstack.header.volume.APIReInitDataVolumeEvent.inventory"
+ desc "重新初始化后的数据云盘清单"
+ type "VolumeInventory"
+ since "5.4.2"
+ clz VolumeInventory.class
+ }
+ field {
+ name "success"
+ desc "操作是否成功"
+ type "boolean"
+ since "5.4.2"
+ }
+ ref {
+ name "error"
+ path "org.zstack.header.volume.APIReInitDataVolumeEvent.error"
+ desc "错误码,若不为null,则表示操作失败,操作成功时该字段为null"
+ type "ErrorCode"
+ since "5.4.2"
+ clz ErrorCode.class
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/volume/APIReInitDataVolumeMsg.java b/header/src/main/java/org/zstack/header/volume/APIReInitDataVolumeMsg.java
new file mode 100644
index 00000000000..48c86ba6261
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/volume/APIReInitDataVolumeMsg.java
@@ -0,0 +1,49 @@
+package org.zstack.header.volume;
+
+import org.springframework.http.HttpMethod;
+import org.zstack.header.identity.Action;
+import org.zstack.header.message.APIEvent;
+import org.zstack.header.message.APIMessage;
+import org.zstack.header.message.APIParam;
+import org.zstack.header.message.DefaultTimeout;
+import org.zstack.header.other.APIAuditor;
+import org.zstack.header.rest.RestRequest;
+
+import java.util.concurrent.TimeUnit;
+
+@Action(category = VolumeConstant.ACTION_CATEGORY)
+@DefaultTimeout(timeunit = TimeUnit.HOURS, value = 3)
+@RestRequest(
+ path = "/volumes/{uuid}/actions",
+ isAction = true,
+ method = HttpMethod.PUT,
+ responseClass = APIReInitDataVolumeEvent.class
+)
+public class APIReInitDataVolumeMsg extends APIMessage implements VolumeMessage, APIAuditor {
+ @APIParam(resourceType = VolumeVO.class, checkAccount = true, operationTarget = true)
+ private String uuid;
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ @Override
+ public String getVolumeUuid() {
+ return uuid;
+ }
+
+ @Override
+ public Result audit(APIMessage msg, APIEvent rsp) {
+ return new Result(((APIReInitDataVolumeMsg) msg).getUuid(), VolumeVO.class);
+ }
+
+ public static APIReInitDataVolumeMsg __example__() {
+ APIReInitDataVolumeMsg msg = new APIReInitDataVolumeMsg();
+ msg.setUuid(uuid());
+ return msg;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/volume/APIReInitDataVolumeMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/volume/APIReInitDataVolumeMsgDoc_zh_cn.groovy
new file mode 100644
index 00000000000..949817704e9
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/volume/APIReInitDataVolumeMsgDoc_zh_cn.groovy
@@ -0,0 +1,58 @@
+package org.zstack.header.volume
+
+import org.zstack.header.volume.APIReInitDataVolumeEvent
+
+doc {
+ title "重新初始化数据云盘"
+
+ category "volume"
+
+ desc """重新初始化一个处于可用状态的数据云盘。该操作会清空数据云盘中的现有数据,若数据云盘已挂载到云主机,云主机必须处于已停止状态。"""
+
+ rest {
+ request {
+ url "PUT /v1/volumes/{uuid}/actions"
+
+ header (Authorization: 'OAuth the-session-uuid')
+
+ clz APIReInitDataVolumeMsg.class
+
+ desc """"""
+
+ params {
+
+ column {
+ name "uuid"
+ enclosedIn "reInitDataVolume"
+ desc "资源的UUID,唯一标示该资源"
+ location "url"
+ type "String"
+ optional false
+ since "5.4.2"
+ }
+ column {
+ name "systemTags"
+ enclosedIn ""
+ desc "系统标签"
+ location "body"
+ type "List"
+ optional true
+ since "5.4.2"
+ }
+ column {
+ name "userTags"
+ enclosedIn ""
+ desc "用户标签"
+ location "body"
+ type "List"
+ optional true
+ since "5.4.2"
+ }
+ }
+ }
+
+ response {
+ clz APIReInitDataVolumeEvent.class
+ }
+ }
+}
\ No newline at end of file
diff --git a/identity/src/main/java/org/zstack/identity/login/LoginManagerImpl.java b/identity/src/main/java/org/zstack/identity/login/LoginManagerImpl.java
index 45086320dba..b7cf5370ba6 100644
--- a/identity/src/main/java/org/zstack/identity/login/LoginManagerImpl.java
+++ b/identity/src/main/java/org/zstack/identity/login/LoginManagerImpl.java
@@ -267,6 +267,7 @@ private SessionInventory processSession(LoginSessionInfo info) {
session.setUserType(info.getUserType());
} else {
session = Session.login(info.getAccountUuid(), info.getUserUuid());
+ session.setUserType(info.getUserType());
}
return session;
diff --git a/longjob/src/main/java/org/zstack/longjob/LongJobManagerImpl.java b/longjob/src/main/java/org/zstack/longjob/LongJobManagerImpl.java
index 975b0e711db..79a01a76efa 100755
--- a/longjob/src/main/java/org/zstack/longjob/LongJobManagerImpl.java
+++ b/longjob/src/main/java/org/zstack/longjob/LongJobManagerImpl.java
@@ -613,6 +613,9 @@ private void handle(SubmitLongJobMsg msg) {
vo.setTargetResourceUuid(msg.getTargetResourceUuid());
vo.setManagementNodeUuid(Platform.getManagementServerId());
vo.setAccountUuid(msg.getAccountUuid());
+ Timestamp now = Timestamp.valueOf(LocalDateTime.now());
+ vo.setCreateDate(now);
+ vo.setLastOpDate(now);
vo = dbf.persistAndRefresh(vo);
msg.setJobUuid(vo.getUuid());
tagMgr.createTags(msg.getSystemTags(), msg.getUserTags(), vo.getUuid(), LongJobVO.class.getSimpleName());
@@ -831,9 +834,7 @@ public void validateGlobalConfig(String category, String name, String oldValue,
dbf.installEntityLifeCycleCallback(LongJobVO.class, EntityEvent.PRE_UPDATE, (evt, o) -> {
LongJobVO job = (LongJobVO) o;
if (job.getExecuteTime() == null && jobCompleted(job)) {
- long time = (System.currentTimeMillis() - job.getCreateDate().getTime()) / 1000;
- job.setExecuteTime(Long.max(time, 1));
- logger.info(String.format("longjob [uuid:%s] set execute time:%d", job.getUuid(), time));
+ setExecuteTimeIfNeed(job);
}
});
diff --git a/longjob/src/main/java/org/zstack/longjob/LongJobUtils.java b/longjob/src/main/java/org/zstack/longjob/LongJobUtils.java
index 2d6d2fabc8e..c4d37f98ebe 100644
--- a/longjob/src/main/java/org/zstack/longjob/LongJobUtils.java
+++ b/longjob/src/main/java/org/zstack/longjob/LongJobUtils.java
@@ -202,9 +202,10 @@ private static boolean isRecoverableError(ErrorCode errorCode) {
return recoverable instanceof Boolean && (Boolean) recoverable;
}
- private static void setExecuteTimeIfNeed(LongJobVO job) {
+ static void setExecuteTimeIfNeed(LongJobVO job) {
if (job.getExecuteTime() == null) {
- long time = (System.currentTimeMillis() - job.getCreateDate().getTime()) / 1000;
+ long startTime = job.getCreateDate() == null ? System.currentTimeMillis() : job.getCreateDate().getTime();
+ long time = (System.currentTimeMillis() - startTime) / 1000;
job.setExecuteTime(Long.max(time, 1));
logger.info(String.format("longjob [uuid:%s] set execute time:%d.", job.getUuid(), time));
}
diff --git a/network/src/main/java/org/zstack/network/l2/L2NetworkApiInterceptor.java b/network/src/main/java/org/zstack/network/l2/L2NetworkApiInterceptor.java
index 40786eb7b2e..9b4ab106303 100755
--- a/network/src/main/java/org/zstack/network/l2/L2NetworkApiInterceptor.java
+++ b/network/src/main/java/org/zstack/network/l2/L2NetworkApiInterceptor.java
@@ -80,7 +80,9 @@ private void validate(final APIAttachL2NetworkToClusterMsg msg) {
/* current ovs only support vlan, vxlan*/
L2NetworkVO l2 = dbf.findByUuid(msg.getL2NetworkUuid(), L2NetworkVO.class);
- if (!StringUtils.isEmpty(l2.getPhysicalInterface())) {
+ // ZNS L2 networks are managed by SDN controller, physicalInterface is irrelevant
+ if (!L2NetworkConstant.VSWITCH_TYPE_ZNS.equals(l2.getvSwitchType())
+ && !StringUtils.isEmpty(l2.getPhysicalInterface())) {
/* find l2 network with same physical interface, but different vswitch Type */
List otherL2s = Q.New(L2NetworkVO.class).select(L2NetworkVO_.uuid)
.eq(L2NetworkVO_.physicalInterface, l2.getPhysicalInterface())
@@ -113,6 +115,13 @@ private void validate(APIDeleteL2NetworkMsg msg) {
}
}
+ private boolean hasSdnControllerTag(List systemTags) {
+ if (systemTags == null || systemTags.isEmpty()) {
+ return false;
+ }
+ return systemTags.stream().anyMatch(tag -> tag.startsWith(L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID_TOKEN + "::"));
+ }
+
private void validate(APICreateL2NetworkMsg msg) {
if (!L2NetworkType.hasType(msg.getType())) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10011, "unsupported l2Network type[%s]", msg.getType()));
@@ -124,8 +133,11 @@ private void validate(APICreateL2NetworkMsg msg) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10012, "unsupported vSwitch type[%s]", msg.getvSwitchType()));
}
+ msg.setPhysicalInterface(StringUtils.trimToNull(msg.getPhysicalInterface()));
+
if (L2NetworkConstant.VSWITCH_TYPE_LINUX_BRIDGE.equals(msg.getvSwitchType())
- && (msg.getPhysicalInterface() == null || msg.getPhysicalInterface().trim().isEmpty())) {
+ && msg.getPhysicalInterface() == null
+ && !hasSdnControllerTag(msg.getSystemTags())) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10021,
"physicalInterface is required when vSwitchType is [%s]", msg.getvSwitchType()));
}
@@ -133,26 +145,45 @@ private void validate(APICreateL2NetworkMsg msg) {
private void validate(APIChangeL2NetworkVlanIdMsg msg) {
L2NetworkVO l2 = dbf.findByUuid(msg.getL2NetworkUuid(), L2NetworkVO.class);
- l2.getAttachedClusterRefs().forEach(ref -> {
- if (Q.New(HostVO.class).eq(HostVO_.clusterUuid, ref.getClusterUuid())
- .notEq(HostVO_.status, HostStatus.Connected).isExists()) {
- throw new ApiMessageInterceptionException(operr(ORG_ZSTACK_NETWORK_L2_10013, "cannot change vlan for l2Network[uuid:%s]" +
- " because there are hosts status in Connecting or Disconnected", l2.getUuid()));
- }
- if (!Q.New(ClusterVO.class).eq(ClusterVO_.uuid, ref.getClusterUuid())
- .eq(ClusterVO_.hypervisorType, L2NetworkConstant.KVM_HYPERVISOR_TYPE).isExists()) {
- throw new ApiMessageInterceptionException(operr(ORG_ZSTACK_NETWORK_L2_10014, "cannot change vlan for l2Network[uuid:%s]" +
- " because it only supports an L2Network that is exclusively attached to a kvm cluster", l2.getUuid()));
- }
- });
+ if (!L2NetworkConstant.VSWITCH_TYPE_ZNS.equals(l2.getvSwitchType())) {
+ l2.getAttachedClusterRefs().forEach(ref -> {
+ if (Q.New(HostVO.class).eq(HostVO_.clusterUuid, ref.getClusterUuid())
+ .notEq(HostVO_.status, HostStatus.Connected).isExists()) {
+ throw new ApiMessageInterceptionException(operr(ORG_ZSTACK_NETWORK_L2_10013, "cannot change vlan for l2Network[uuid:%s]" +
+ " because there are hosts status in Connecting or Disconnected", l2.getUuid()));
+ }
+ if (!Q.New(ClusterVO.class).eq(ClusterVO_.uuid, ref.getClusterUuid())
+ .eq(ClusterVO_.hypervisorType, L2NetworkConstant.KVM_HYPERVISOR_TYPE).isExists()) {
+ throw new ApiMessageInterceptionException(operr(ORG_ZSTACK_NETWORK_L2_10014, "cannot change vlan for l2Network[uuid:%s]" +
+ " because it only supports an L2Network that is exclusively attached to a kvm cluster", l2.getUuid()));
+ }
+ });
+ }
// pvlan isolated not support change vlan
if (l2.getIsolated()) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10015, "cannot change vlan for l2Network[uuid:%s]" +
" because this l2Network is isolated", l2.getUuid()));
}
+ String targetType = StringUtils.trimToNull(msg.getType());
+ msg.setType(targetType);
+ // When type is not specified (or blank), default to the current network type.
+ if (targetType == null) {
+ targetType = l2.getType();
+ msg.setType(targetType);
+ }
+
+ boolean targetIsVlan = L2NetworkConstant.L2_VLAN_NETWORK_TYPE.equals(targetType);
+ boolean targetIsNoVlan = L2NetworkConstant.L2_NO_VLAN_NETWORK_TYPE.equals(targetType);
+ boolean targetIsGeneve = L2NetworkConstant.L2_GENEVE_NETWORK_TYPE.equals(targetType);
+ boolean targetIsVxlan = L2NetworkConstant.VXLAN_NETWORK_TYPE.equals(targetType);
+ if (!targetIsVlan && !targetIsNoVlan && !targetIsGeneve && !targetIsVxlan) {
+ throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10021,
+ "unsupported l2Network type[%s] for ChangeL2NetworkVlanId", targetType));
+ }
+
String sdnControllerUuid = L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID
.getTokenByResourceUuid(msg.getL2NetworkUuid(), L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID_TOKEN);
- if (msg.getType().equals(L2NetworkConstant.L2_VLAN_NETWORK_TYPE)) {
+ if (targetIsVlan) {
if (msg.getVlan() == null) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10016, "vlan is required for " +
"ChangeL2NetworkVlanId with type[%s]", msg.getType()));
@@ -163,7 +194,9 @@ private void validate(APIChangeL2NetworkVlanIdMsg msg) {
List attachedClusters = l2.getAttachedClusterRefs().stream()
.map(L2NetworkClusterRefVO::getClusterUuid).collect(Collectors.toList());
List l2s;
- if (sdnControllerUuid == null) {
+ if (attachedClusters.isEmpty()) {
+ l2s = java.util.Collections.emptyList();
+ } else if (sdnControllerUuid == null) {
l2s = SQL.New("select l2" +
" from L2NetworkVO l2, L2NetworkClusterRefVO ref" +
" where l2.uuid = ref.l2NetworkUuid" +
@@ -196,7 +229,7 @@ private void validate(APIChangeL2NetworkVlanIdMsg msg) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10018, "There has been a l2Network attached to cluster with virtual network id[%s] and physical interface[%s]. Failed to change L2 network[uuid:%s]",
msg.getVlan(), l2.getPhysicalInterface(), l2.getUuid()));
}
- } else if (msg.getType().equals(L2NetworkConstant.L2_NO_VLAN_NETWORK_TYPE)) {
+ } else if (targetIsNoVlan) {
if (msg.getVlan() != null) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10019, "vlan is not allowed for " +
"ChangeL2NetworkVlanId with type[%s]", msg.getType()));
@@ -204,7 +237,9 @@ private void validate(APIChangeL2NetworkVlanIdMsg msg) {
List attachedClusters = l2.getAttachedClusterRefs().stream()
.map(L2NetworkClusterRefVO::getClusterUuid).collect(Collectors.toList());
List l2s;
- if (sdnControllerUuid != null) {
+ if (attachedClusters.isEmpty()) {
+ l2s = java.util.Collections.emptyList();
+ } else if (sdnControllerUuid != null) {
l2s = SQL.New("select l2" +
" from L2NetworkVO l2, L2NetworkClusterRefVO ref, SystemTagVO tag" +
" where l2.uuid = ref.l2NetworkUuid" +
@@ -233,6 +268,15 @@ private void validate(APIChangeL2NetworkVlanIdMsg msg) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10020, "There has been a l2Network attached to cluster that has physical interface[%s]. Failed to change l2Network[uuid:%s]",
l2.getPhysicalInterface(), l2.getUuid()));
}
+ } else if (targetIsGeneve || targetIsVxlan) {
+ if (msg.getVlan() == null) {
+ throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10016, "vni is required for " +
+ "ChangeL2NetworkVlanId with type[%s]", msg.getType()));
+ }
+ if (!NetworkUtils.isValidVni(msg.getVlan())) {
+ throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10017, "invalid vni[%d] for " +
+ "ChangeL2NetworkVlanId, must be between 1 and %d", msg.getVlan(), L2NetworkConstant.VXLAN_ID_MAX));
+ }
}
}
}
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 fa864bafaf2..b1eecae4d5e 100755
--- a/network/src/main/java/org/zstack/network/l2/L2NetworkExtensionPointEmitter.java
+++ b/network/src/main/java/org/zstack/network/l2/L2NetworkExtensionPointEmitter.java
@@ -53,6 +53,20 @@ public void run(L2NetworkDeleteExtensionPoint arg) {
});
}
+ public void beforeUpdate(final L2NetworkInventory inv) {
+ for (L2NetworkUpdateExtensionPoint ext : updateExtensions) {
+ try {
+ ext.beforeChangeL2NetworkVlanId(inv);
+ } catch (RuntimeException e) {
+ // propagate validation failures and other runtime exceptions immediately
+ throw e;
+ } catch (Exception e) {
+ logger.warn(String.format("unhandled exception in L2NetworkUpdateExtensionPoint.beforeChangeL2NetworkVlanId of %s",
+ ext.getClass().getCanonicalName()), e);
+ }
+ }
+ }
+
public void afterUpdate(final L2NetworkInventory inv) {
CollectionUtils.safeForEach(updateExtensions, arg -> arg.afterChangeL2NetworkVlanId(inv));
}
diff --git a/network/src/main/java/org/zstack/network/l2/L2NetworkHostHelper.java b/network/src/main/java/org/zstack/network/l2/L2NetworkHostHelper.java
index a612b3847c5..0136649f4bf 100644
--- a/network/src/main/java/org/zstack/network/l2/L2NetworkHostHelper.java
+++ b/network/src/main/java/org/zstack/network/l2/L2NetworkHostHelper.java
@@ -15,7 +15,6 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import java.util.stream.Collectors;
import static java.util.Arrays.asList;
@@ -87,6 +86,10 @@ public L2NetworkHostRefInventory getL2NetworkHostRef(String l2NetworkUuid, Strin
}
public static Set getHostsByL2NetworkAttachedCluster(L2NetworkInventory l2NetworkInventory) {
+ if (l2NetworkInventory.getAttachedClusterUuids() == null || l2NetworkInventory.getAttachedClusterUuids().isEmpty()) {
+ return new HashSet<>();
+ }
+
return new HashSet<>(Q.New(HostVO.class)
.in(HostVO_.clusterUuid, l2NetworkInventory.getAttachedClusterUuids())
.notIn(HostVO_.state,asList(HostState.PreMaintenance, HostState.Maintenance))
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 821a76ad462..ec9a7a5be0b 100755
--- a/network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java
+++ b/network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java
@@ -443,6 +443,7 @@ public String getSyncSignature() {
@Override
public void run(SyncTaskChain chain) {
+ extpEmitter.beforeUpdate(getSelfInventory());
changeL2NetworkVlanId(msg, new Completion(chain) {
@Override
public void success() {
@@ -968,6 +969,10 @@ protected void scripts() {
}
L2NetworkVO tl2 = Q.New(L2NetworkVO.class).eq(L2NetworkVO_.uuid, msg.getL2NetworkUuid()).find();
+ // ZNS L2NoVlan segments are uniquely identified by SDN controller, not by physicalInterface
+ if (L2NetworkConstant.VSWITCH_TYPE_ZNS.equals(tl2.getvSwitchType())) {
+ return;
+ }
for (L2NetworkVO l2 : l2s) {
if (l2.getPhysicalInterface().equals(tl2.getPhysicalInterface())) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10006, "There has been a l2Network[uuid:%s, name:%s] attached to cluster[uuid:%s] that has physical interface[%s]. Failed to attach l2Network[uuid:%s]",
@@ -980,8 +985,10 @@ protected void scripts() {
l2s = SQL.New("select l2" +
" from L2VlanNetworkVO l2, L2NetworkClusterRefVO ref" +
" where l2.uuid = ref.l2NetworkUuid" +
- " and ref.clusterUuid = :clusterUuid")
- .param("clusterUuid", msg.getClusterUuid()).list();
+ " and ref.clusterUuid = :clusterUuid" +
+ " and l2.vSwitchType != :znsType")
+ .param("clusterUuid", msg.getClusterUuid())
+ .param("znsType", L2NetworkConstant.VSWITCH_TYPE_ZNS).list();
} else {
l2s = SQL.New("select l2" +
" from L2VlanNetworkVO l2, L2NetworkClusterRefVO ref, SystemTagVO tag" +
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 d7c6c6798d9..43849b41e34 100755
--- a/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java
+++ b/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java
@@ -1047,6 +1047,7 @@ public void fail(ErrorCode errorCode) {
private void handle(APIUpdateIpRangeMsg msg) {
IpRangeVO vo = dbf.findByUuid(msg.getUuid(), IpRangeVO.class);
+ IpRangeInventory oldIpr = IpRangeInventory.valueOf(vo);
boolean update = false;
if (msg.getName() != null) {
vo.setName(msg.getName());
@@ -1059,8 +1060,14 @@ private void handle(APIUpdateIpRangeMsg msg) {
if (update) {
vo = dbf.updateAndRefresh(vo);
}
+
+ IpRangeInventory newIpr = IpRangeInventory.valueOf(vo);
+ for (AfterUpdateIpRangeExtensionPoint ext : pluginRgty.getExtensionList(AfterUpdateIpRangeExtensionPoint.class)) {
+ ext.afterUpdateIpRange(oldIpr, newIpr);
+ }
+
APIUpdateIpRangeEvent evt = new APIUpdateIpRangeEvent(msg.getId());
- evt.setInventory(IpRangeInventory.valueOf(vo));
+ evt.setInventory(newIpr);
bus.publish(evt);
}
@@ -1534,12 +1541,14 @@ public void done(ErrorCodeList errorCodeList) {
detachNetworkServiceFromL3NetworkMsg(l3VO, refVOS, new Completion(msg) {
@Override
public void success() {
+ dbf.removeCollection(refVOS, NetworkServiceL3NetworkRefVO.class);
reply.setError(errorCodeList.getCauses().get(0));
bus.reply(msg, reply);
}
@Override
public void fail(ErrorCode errorCode) {
+ dbf.removeCollection(refVOS, NetworkServiceL3NetworkRefVO.class);
reply.setError(errorCodeList.getCauses().get(0));
bus.reply(msg, reply);
}
@@ -1551,6 +1560,13 @@ public void fail(ErrorCode errorCode) {
private void handle(APIAttachNetworkServiceToL3NetworkMsg msg) {
APIAttachNetworkServiceToL3NetworkEvent evt = new APIAttachNetworkServiceToL3NetworkEvent(msg.getId());
+ if (msg.isSkipAttach()) {
+ self = dbf.reload(self);
+ evt.setInventory(L3NetworkInventory.valueOf(self));
+ bus.publish(evt);
+ return;
+ }
+
AttachNetworkServiceToL3Msg amsg = new AttachNetworkServiceToL3Msg();
amsg.setL3NetworkUuid(msg.getL3NetworkUuid());
amsg.setNetworkServices(msg.getNetworkServices());
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 81fb7e94b3f..45fecdc0aa1 100755
--- a/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java
+++ b/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java
@@ -184,6 +184,41 @@ public void run(MessageReply reply) {
}
});
}
+ }).then(new NoRollbackFlow() {
+ @Override
+ public void run(FlowTrigger trigger, Map data) {
+ List exts =
+ pluginRgty.getExtensionList(AfterSetL3NetworkMtuExtensionPoint.class);
+ if (exts.isEmpty()) {
+ trigger.next();
+ return;
+ }
+
+ L3NetworkInventory l3Inv = L3NetworkInventory.valueOf(dbf.findByUuid(msg.getL3NetworkUuid(), L3NetworkVO.class));
+ new While<>(exts).each((ext, wcompl) -> {
+ ext.afterSetL3NetworkMtu(l3Inv, msg.getMtu(), new Completion(wcompl) {
+ @Override
+ public void success() {
+ wcompl.done();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ wcompl.addError(errorCode);
+ wcompl.allDone();
+ }
+ });
+ }).run(new WhileDoneCompletion(trigger) {
+ @Override
+ public void done(ErrorCodeList errorCodeList) {
+ if (errorCodeList.getCauses().isEmpty()) {
+ trigger.next();
+ } else {
+ trigger.fail(errorCodeList.getCauses().get(0));
+ }
+ }
+ });
+ }
}).done(new FlowDoneHandler(msg) {
@Override
public void handle(Map data) {
@@ -192,8 +227,8 @@ public void handle(Map data) {
}).error(new FlowErrorHandler(msg) {
@Override
public void handle(ErrorCode errCode, Map data) {
+ NetworkServiceSystemTag.L3_MTU.delete(msg.getL3NetworkUuid());
if (oldmtu != null) {
- NetworkServiceSystemTag.L3_MTU.delete(msg.getL3NetworkUuid());
SystemTagCreator creator = NetworkServiceSystemTag.L3_MTU.newSystemTagCreator(msg.getL3NetworkUuid());
creator.recreate = true;
creator.inherent = false;
@@ -518,6 +553,7 @@ private void handle(APICreateL3NetworkMsg msg) {
vo.setIpVersion(IPv6Constants.IPv4);
}
vo.setInternalId((int)dbf.generateSequenceNumber(L3NetworkSequenceNumberVO.class));
+ vo.setType(msg.getType() != null ? msg.getType() : L3NetworkConstant.L3_BASIC_NETWORK_TYPE);
FlowChain fchain = new SimpleFlowChain();
fchain.setName(String.format("create-l3-network-%s", vo.getUuid()));
diff --git a/network/src/main/java/org/zstack/network/service/DhcpExtension.java b/network/src/main/java/org/zstack/network/service/DhcpExtension.java
index 8e37da8b1e9..ff66b997243 100755
--- a/network/src/main/java/org/zstack/network/service/DhcpExtension.java
+++ b/network/src/main/java/org/zstack/network/service/DhcpExtension.java
@@ -437,6 +437,7 @@ public void enableNetworkService(L3NetworkVO l3VO, NetworkServiceProviderType pr
SdnControllerEnableDHCPMsg msg = new SdnControllerEnableDHCPMsg();
msg.setL3NetworkUuid(l3VO.getUuid());
msg.setSdnControllerUuid(sdnControllerUuid);
+ msg.setSystemTags(systemTags);
bus.makeTargetServiceIdByResourceUuid(msg, SdnControllerConstant.SERVICE_ID, sdnControllerUuid);
bus.send(msg, new CloudBusCallBack(completion) {
@Override
diff --git a/network/src/main/java/org/zstack/network/service/MtuGetter.java b/network/src/main/java/org/zstack/network/service/MtuGetter.java
index 9635ca3f0f5..b53da773b90 100644
--- a/network/src/main/java/org/zstack/network/service/MtuGetter.java
+++ b/network/src/main/java/org/zstack/network/service/MtuGetter.java
@@ -3,6 +3,7 @@
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
+import org.zstack.core.Platform;
import org.zstack.core.componentloader.PluginRegistry;
import org.zstack.core.db.Q;
import org.zstack.header.network.l2.*;
@@ -28,6 +29,13 @@ public class MtuGetter {
@Autowired
private PluginRegistry pluginRgty;
+ private PluginRegistry getPluginRegistry() {
+ if (pluginRgty == null) {
+ pluginRgty = Platform.getComponentLoader().getComponent(PluginRegistry.class);
+ }
+ return pluginRgty;
+ }
+
public Integer getMtu(String l3NetworkUuid) {
String l2NetworkUuid = Q.New(L3NetworkVO.class).select(L3NetworkVO_.l2NetworkUuid).eq(L3NetworkVO_.uuid, l3NetworkUuid).findValue();
@@ -43,7 +51,7 @@ public Integer getMtu(String l3NetworkUuid) {
}
L2NetworkVO l2VO = Q.New(L2NetworkVO.class).eq(L2NetworkVO_.uuid, l2NetworkUuid).find();
- for (L2NetworkDefaultMtu e : pluginRgty.getExtensionList(L2NetworkDefaultMtu.class)) {
+ for (L2NetworkDefaultMtu e : getPluginRegistry().getExtensionList(L2NetworkDefaultMtu.class)) {
if (l2VO.getType().equals(e.getL2NetworkType())) {
mtu = e.getDefaultMtu(L2NetworkInventory.valueOf(l2VO));
}
@@ -82,7 +90,7 @@ public Integer getL2Mtu(L2NetworkInventory l2Inv) {
}
/* compare to default mtu */
- for (L2NetworkDefaultMtu e : pluginRgty.getExtensionList(L2NetworkDefaultMtu.class)) {
+ for (L2NetworkDefaultMtu e : getPluginRegistry().getExtensionList(L2NetworkDefaultMtu.class)) {
if (l2Inv.getType().equals(e.getL2NetworkType())) {
Integer l2mtu = e.getDefaultMtu(l2Inv);
if (mtu == null) {
diff --git a/network/src/main/java/org/zstack/network/service/NetworkServiceApiInterceptor.java b/network/src/main/java/org/zstack/network/service/NetworkServiceApiInterceptor.java
index dad5d87781a..9b95e53ee9a 100755
--- a/network/src/main/java/org/zstack/network/service/NetworkServiceApiInterceptor.java
+++ b/network/src/main/java/org/zstack/network/service/NetworkServiceApiInterceptor.java
@@ -1,6 +1,7 @@
package org.zstack.network.service;
import org.springframework.beans.factory.annotation.Autowired;
+import org.zstack.core.componentloader.PluginRegistry;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.db.SimpleQuery;
import org.zstack.core.db.SimpleQuery.Op;
@@ -29,16 +30,22 @@
/**
*/
-public class NetworkServiceApiInterceptor implements ApiMessageInterceptor {
- private final static CLogger logger = Utils.getLogger(NetworkServiceApiInterceptor.class);
- @Autowired
+public class NetworkServiceApiInterceptor implements ApiMessageInterceptor {
+ private final static CLogger logger = Utils.getLogger(NetworkServiceApiInterceptor.class);
+ @Autowired
private DatabaseFacade dbf;
+ @Autowired
+ private PluginRegistry pluginRgty;
@Override
public APIMessage intercept(APIMessage msg) throws ApiMessageInterceptionException {
if (msg instanceof APIAttachNetworkServiceToL3NetworkMsg) {
APIAttachNetworkServiceToL3NetworkMsg attachMsg = (APIAttachNetworkServiceToL3NetworkMsg)msg;
attachMsg.setNetworkServices(convertNetworkProviderTypeToUuid(attachMsg.getNetworkServices()));
+ if (skipAttachNetworkService(attachMsg)) {
+ attachMsg.setSkipAttach(true);
+ return msg;
+ }
validate(attachMsg);
} else if (msg instanceof APIDetachNetworkServiceFromL3NetworkMsg) {
APIDetachNetworkServiceFromL3NetworkMsg detachMsg = (APIDetachNetworkServiceFromL3NetworkMsg)msg;
@@ -59,6 +66,15 @@ public APIMessage intercept(APIMessage msg) throws ApiMessageInterceptionExcepti
return msg;
}
+ private boolean skipAttachNetworkService(APIAttachNetworkServiceToL3NetworkMsg msg) {
+ for (NetworkServiceAttachExtensionPoint ext : pluginRgty.getExtensionList(NetworkServiceAttachExtensionPoint.class)) {
+ if (ext.skipAttachNetworkService(msg)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void validate(APIAttachNetworkServiceToL3NetworkMsg msg) {
if (msg.getNetworkServices().isEmpty()) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SERVICE_10006, "networkServices cannot be empty"));
@@ -157,11 +173,11 @@ public String call(String type) {
}
private Map> convertNetworkProviderTypeToUuid(Map> map){
- if (map.isEmpty()) {
+ Map> mapNew = normalizeNetworkServices(map);
+ if (mapNew.isEmpty()) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SERVICE_10012, "networkServices cannot be empty"));
}
- Map> mapNew = new HashMap<>(map);
List networkServiceProviderVOs = Q.New(NetworkServiceProviderVO.class).list();
for (NetworkServiceProviderVO vo :networkServiceProviderVOs) {
@@ -172,4 +188,39 @@ private Map> convertNetworkProviderTypeToUuid(Map> normalizeNetworkServices(Map> map) {
+ Map> ret = new LinkedHashMap<>();
+ if (map == null) {
+ return ret;
+ }
+
+ for (Map.Entry> entry : map.entrySet()) {
+ if (entry.getKey() == null) {
+ continue;
+ }
+
+ String provider = entry.getKey().trim();
+ if (provider.isEmpty()) {
+ continue;
+ }
+
+ List services = new ArrayList<>();
+ if (entry.getValue() != null) {
+ for (String service : entry.getValue()) {
+ if (service == null) {
+ continue;
+ }
+
+ String normalized = service.trim();
+ if (!normalized.isEmpty()) {
+ services.add(normalized);
+ }
+ }
+ }
+
+ ret.computeIfAbsent(provider, k -> new ArrayList<>()).addAll(services);
+ }
+ return ret;
+ }
}
diff --git a/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmConstant.java b/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmConstant.java
index dcada8edeee..094771f24bb 100755
--- a/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmConstant.java
+++ b/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmConstant.java
@@ -47,6 +47,7 @@ public static enum Params {
timeout,
rebuildSnat,
fromApi,
+ skipGrayscaleUpgradeCheck,
}
public static final String REFRESH_FIREWALL_PATH = "/appliancevm/refreshfirewall";
diff --git a/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmFacadeImpl.java b/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmFacadeImpl.java
index 6d63ce3522f..76d98729668 100755
--- a/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmFacadeImpl.java
+++ b/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmFacadeImpl.java
@@ -296,7 +296,7 @@ class VmDestroyHook implements VmInstanceBeforeDestroyHook, VmInstanceAfterDestr
@Override
public void afterDestroy(VmInstanceInventory vm) {
if (applianceVm != null) {
- ApplianceVmCanonicalEvents.NewVmCreatedData data = new ApplianceVmCanonicalEvents.NewVmCreatedData();
+ ApplianceVmCanonicalEvents.VmDestroyedData data = new ApplianceVmCanonicalEvents.VmDestroyedData();
data.vm = applianceVm;
data.fire();
}
diff --git a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java
index c3b01dc3c8b..fab8dd0b583 100755
--- a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java
+++ b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java
@@ -5429,6 +5429,139 @@ public void handle(ErrorCode errCode, Map data) {
}).start();
}
+ @Override
+ protected void handle(final ReInitDataVolumeOnPrimaryStorageMsg msg) {
+ final ReInitDataVolumeOnPrimaryStorageReply reply = new ReInitDataVolumeOnPrimaryStorageReply();
+
+ final FlowChain chain = FlowChainBuilder.newShareFlowChain();
+ chain.setName(String.format("reinit-data-volume-%s", msg.getVolume().getUuid()));
+ chain.then(new ShareFlow() {
+ final String originalVolumePath = msg.getVolume().getInstallPath();
+ final String targetPoolName = getTargetPoolNameFromAllocatedUrl(msg.getAllocatedInstallUrl());
+ String newVolumePath = makeReInitDataVolumeInstallPath(msg.getVolume().getUuid(), targetPoolName);
+ boolean newVolumeCreated;
+
+ @Override
+ public void setup() {
+ flow(new NoRollbackFlow() {
+ String __name__ = "create-empty-data-volume";
+
+ @Override
+ public void run(final FlowTrigger trigger, Map data) {
+ CreateEmptyVolumeCmd cmd = new CreateEmptyVolumeCmd();
+ cmd.installPath = newVolumePath;
+ cmd.size = msg.getVolume().getSize();
+ cmd.setShareable(msg.getVolume().isShareable());
+ httpCall(CREATE_VOLUME_PATH, cmd, CreateEmptyVolumeRsp.class, new ReturnValueCompletion(trigger) {
+ @Override
+ public void success(CreateEmptyVolumeRsp ret) {
+ newVolumeCreated = true;
+ trigger.next();
+ }
+
+ @Override
+ public void fail(ErrorCode err) {
+ trigger.fail(err);
+ }
+ });
+ }
+ });
+
+ flow(new NoRollbackFlow() {
+ String __name__ = "delete-origin-data-volume-which-has-no-snapshot";
+
+ @Override
+ public void run(FlowTrigger trigger, Map data) {
+ long snapshotCount = Q.New(VolumeSnapshotVO.class)
+ .like(VolumeSnapshotVO_.primaryStorageInstallPath,
+ String.format("%s@%%", originalVolumePath))
+ .count();
+ if (snapshotCount == 0) {
+ DeleteCmd cmd = new DeleteCmd();
+ cmd.installPath = originalVolumePath;
+ httpCall(DELETE_PATH, cmd, DeleteRsp.class, new ReturnValueCompletion(null) {
+ @Override
+ public void success(DeleteRsp returnValue) {
+ logger.debug(String.format("successfully deleted %s", originalVolumePath));
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ logger.warn(String.format("unable to delete %s, %s. Need a cleanup",
+ originalVolumePath, errorCode));
+ trash.createTrash(TrashType.ReInitDataVolume, false, msg.getVolume());
+ }
+ });
+ } else {
+ trash.createTrash(TrashType.ReInitDataVolume, false, msg.getVolume());
+ }
+ trigger.next();
+ }
+ });
+
+ done(new FlowDoneHandler(msg) {
+ @Override
+ public void handle(Map data) {
+ reply.setNewVolumeInstallPath(newVolumePath);
+ bus.reply(msg, reply);
+ }
+ });
+
+ error(new FlowErrorHandler(msg) {
+ @Override
+ public void handle(ErrorCode errCode, Map data) {
+ if (newVolumeCreated) {
+ cleanupNewReInitDataVolumeBits(newVolumePath, msg.getVolume());
+ }
+ reply.setError(errCode);
+ bus.reply(msg, reply);
+ }
+ });
+ }
+ }).start();
+ }
+
+ private void cleanupNewReInitDataVolumeBits(String installPath, VolumeInventory volume) {
+ DeleteCmd cmd = new DeleteCmd();
+ cmd.installPath = installPath;
+ httpCall(DELETE_PATH, cmd, DeleteRsp.class, new ReturnValueCompletion(null) {
+ @Override
+ public void success(DeleteRsp returnValue) {
+ logger.debug(String.format("successfully deleted reinit new volume bits[path:%s, volumeUuid:%s]",
+ installPath, volume.getUuid()));
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ logger.warn(String.format("unable to delete reinit new volume bits[path:%s, volumeUuid:%s], submit GC. %s",
+ installPath, volume.getUuid(), errorCode));
+ submitNewReInitDataVolumeBitsGC(installPath, volume);
+ }
+ });
+ }
+
+ private void submitNewReInitDataVolumeBitsGC(String installPath, VolumeInventory volume) {
+ VolumeInventory cleanupVolume = new VolumeInventory();
+ cleanupVolume.setUuid(volume.getUuid());
+ cleanupVolume.setInstallPath(installPath);
+ cleanupVolume.setPrimaryStorageUuid(volume.getPrimaryStorageUuid());
+ cleanupVolume.setFormat(volume.getFormat());
+ cleanupVolume.setSize(0L);
+
+ CephDeleteVolumeGC gc = new CephDeleteVolumeGC();
+ gc.NAME = String.format("gc-ceph-%s-reinit-volume-%s-new-bits", self.getUuid(), volume.getUuid());
+ gc.primaryStorageUuid = self.getUuid();
+ gc.volume = cleanupVolume;
+ gc.deduplicateSubmit(CephGlobalConfig.GC_INTERVAL.value(Long.class), TimeUnit.SECONDS);
+ }
+
+ private String makeReInitDataVolumeInstallPath(String volUuid, String targetPoolName) {
+ return String.format("ceph://%s/reinit-%s-%s",
+ targetPoolName,
+ volUuid,
+ System.currentTimeMillis());
+ }
+
@Override
protected void handle(DeleteSnapshotOnPrimaryStorageMsg msg) {
inQueue().name(String.format("delete-snapshot-on-primarystorage-%s", self.getUuid()))
diff --git a/plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java b/plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java
index df5b1081d08..2f24bc3feaf 100644
--- a/plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java
+++ b/plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java
@@ -620,14 +620,13 @@ public void cleanActiveRecord(ImageCacheInventory cache) {
}
@Override
- public void blacklist(String installPath, String protocol, HostInventory h, Completion comp) {
+ public void blacklist(String installPath, String protocol, HostInventory h) {
logger.debug(String.format("blacklisting volume[path: %s, protocol:%s] on host[uuid:%s, ip:%s]",
installPath, protocol, h.getUuid(), h.getManagementIp()));
UssGatewayModule uss = getUssGateway(VolumeProtocol.valueOf(protocol), h.getManagementIp());
VolumeModule exponVol = apiHelper.getVolume(getVolIdFromPath(installPath));
apiHelper.addVolumePathToBlacklist(buildExponVolumeBoundPath(uss, exponVol.getVolumeName()));
- comp.success();
}
@Override
diff --git a/plugin/externalStorage/src/main/java/org/zstack/externalStorage/primary/kvm/ExternalPrimaryStorageKvmFactory.java b/plugin/externalStorage/src/main/java/org/zstack/externalStorage/primary/kvm/ExternalPrimaryStorageKvmFactory.java
index 6e5c0c591b8..aa25ed03592 100644
--- a/plugin/externalStorage/src/main/java/org/zstack/externalStorage/primary/kvm/ExternalPrimaryStorageKvmFactory.java
+++ b/plugin/externalStorage/src/main/java/org/zstack/externalStorage/primary/kvm/ExternalPrimaryStorageKvmFactory.java
@@ -141,6 +141,27 @@ public void done(ErrorCodeList errorCodeList) {
}
});
}
+ }).then(new NoRollbackFlow() {
+ String __name__ = "ensure-heartbeat-volume";
+
+ @Override
+ public void run(FlowTrigger trigger, Map data) {
+ ensureHeartbeatVolume(context.getInventory(), extPss, new WhileDoneCompletion(trigger) {
+ @Override
+ public void done(ErrorCodeList errorCodeList) {
+ if (errorCodeList.getCauses().isEmpty()) {
+ trigger.next();
+ } else {
+ logger.warn(String.format("failed to ensure heartbeat volumes before checking KVM host[uuid:%s, name:%s] storage connection, %s",
+ context.getInventory().getUuid(), context.getInventory().getName(), errorCodeList.getReadableDetails()));
+ trigger.fail(operr(ORG_ZSTACK_EXTERNALSTORAGE_PRIMARY_KVM_10000,
+ new ErrorCodeList().causedBy(errorCodeList.getCauses()),
+ "failed to ensure heartbeat volumes before checking KVM host[uuid:%s, name:%s] storage connection",
+ context.getInventory().getUuid(), context.getInventory().getName()));
+ }
+ }
+ });
+ }
}).then(new NoRollbackFlow() {
String __name__ = "check-host-status";
@@ -167,6 +188,25 @@ public void handle(Map data) {
}).start();
}
+ private void ensureHeartbeatVolume(HostInventory host, List extPss, WhileDoneCompletion completion) {
+ new While<>(extPss).each((extPs, compl) -> {
+ logger.debug(String.format("ensuring heartbeat volume for external primary storage[uuid:%s, name:%s] before checking KVM host[uuid:%s, name:%s] storage connection",
+ extPs.getUuid(), extPs.getName(), host.getUuid(), host.getName()));
+ extPsFactory.getNodeSvc(extPs.getUuid()).activateHeartbeatVolume(host, new ReturnValueCompletion(compl) {
+ @Override
+ public void success(HeartbeatVolumeTopology returnValue) {
+ compl.done();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ compl.addError(errorCode);
+ compl.done();
+ }
+ });
+ }).run(completion);
+ }
+
private void deployClient(final KVMHostConnectedContext context, List extPss, WhileDoneCompletion completion) {
new While<>(extPss).each((extPs, compl) -> {
logger.debug(String.format("deploying client for external primary storage[uuid:%s, name:%s] on KVM host[uuid:%s, name:%s]",
@@ -335,6 +375,9 @@ public void done(ErrorCodeList errorCodeList) {
@Override
public void beforeStartVmOnKvm(KVMHostInventory host, VmInstanceSpec spec, KVMAgentCommands.StartVmCmd cmd) {
List vols = getManagerExclusiveVolume(spec);
+ // captured here so blacklist can be invoked from the for-each body itself; a throw from
+ // inside the Completion callback would be eaten by AsyncSafeAspect on deactivate.
+ ErrorCode[] deactivateErr = new ErrorCode[1];
for (VolumeInventory vol : vols) {
PrimaryStorageNodeSvc nodeSvc = extPsFactory.getNodeSvc(vol.getPrimaryStorageUuid());
@@ -360,6 +403,7 @@ public void beforeStartVmOnKvm(KVMHostInventory host, VmInstanceSpec spec, KVMAg
clientHost.getUuid(), clientHost.getManagementIp(),
host.getUuid(), host.getManagementIp()));
+ deactivateErr[0] = null;
nodeSvc.deactivate(vol.getInstallPath(), vol.getProtocol(), client, new Completion(null) {
@Override
public void success() {
@@ -369,11 +413,14 @@ public void success() {
@Override
public void fail(ErrorCode errorCode) {
- logger.warn(String.format("failed to deactivate volume[uuid:%s, installPath:%s] on host[uuid:%s, ip:%s], add it to blacklist",
- vol.getUuid(), vol.getInstallPath(), clientHost.getUuid(), clientHost.getManagementIp()));
- nodeSvc.blacklist(vol.getInstallPath(), vol.getProtocol(), HostInventory.valueOf(clientHost), new NopeCompletion());
+ deactivateErr[0] = errorCode;
}
});
+ if (deactivateErr[0] != null) {
+ logger.warn(String.format("failed to deactivate volume[uuid:%s, installPath:%s] on host[uuid:%s, ip:%s]: %s, add it to blacklist",
+ vol.getUuid(), vol.getInstallPath(), clientHost.getUuid(), clientHost.getManagementIp(), deactivateErr[0].getDetails()));
+ nodeSvc.blacklist(vol.getInstallPath(), vol.getProtocol(), HostInventory.valueOf(clientHost));
+ }
}
}
});
diff --git a/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/FlatDhcpBackend.java b/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/FlatDhcpBackend.java
index 5823bb2b9ee..db72febcf4d 100755
--- a/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/FlatDhcpBackend.java
+++ b/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/FlatDhcpBackend.java
@@ -996,7 +996,7 @@ public static Map getExistingDhcpServerIp(String l3Uuid, int ipV
@Deferred
private String allocateDhcpIp(String l3Uuid, int ipVersion, boolean allocate_ip, String requiredIp, String excludedIp) {
- if (!isAllocateDhcpServerIp(l3Uuid)) {
+ if (!isAllocateDhcpServerIp(l3Uuid, ipVersion)) {
return null;
}
@@ -1276,6 +1276,17 @@ private boolean isAllocateDhcpServerIp(String l3Uuid) {
return type.isAllocateDhcpServerIp();
}
+ private boolean isAllocateDhcpServerIp(String l3Uuid, int ipVersion) {
+ String providerType = new NetworkProviderFinder().getNetworkProviderTypeByNetworkServiceType(l3Uuid, NetworkServiceType.DHCP.toString());
+ if (providerType == null) {
+ return false;
+ }
+
+ NetworkServiceProviderType type = NetworkServiceProviderType.valueOf(providerType);
+ return type.isAllocateDhcpServerIp()
+ && (ipVersion != IPv6Constants.IPv6 || type.isAllocateDhcpv6ServerIp());
+ }
+
private boolean isCreateDhcpNameSpace(String l3Uuid) {
String providerType = new NetworkProviderFinder().getNetworkProviderTypeByNetworkServiceType(l3Uuid, NetworkServiceType.DHCP.toString());
if (providerType == null) {
@@ -2490,7 +2501,10 @@ private void validate(APIDetachNetworkServiceFromL3NetworkMsg msg) {
}
private void validate(APIChangeL3NetworkDhcpIpAddressMsg msg) {
- if (!isAllocateDhcpServerIp(msg.getL3NetworkUuid())) {
+ if ((msg.getDhcpServerIp() != null && !isAllocateDhcpServerIp(msg.getL3NetworkUuid(), IPv6Constants.IPv4))
+ || (msg.getDhcpv6ServerIp() != null && !isAllocateDhcpServerIp(msg.getL3NetworkUuid(), IPv6Constants.IPv6))
+ || (msg.getDhcpServerIp() == null && msg.getDhcpv6ServerIp() == null
+ && !isAllocateDhcpServerIp(msg.getL3NetworkUuid()))) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SERVICE_FLAT_10044, "could change dhcp server ip, because flat dhcp is not enabled"));
}
diff --git a/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/userdata/UserdataExtension.java b/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/userdata/UserdataExtension.java
index 8e75c4edd53..0abb2aab587 100755
--- a/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/userdata/UserdataExtension.java
+++ b/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/userdata/UserdataExtension.java
@@ -1,10 +1,14 @@
package org.zstack.network.service.userdata;
import org.springframework.beans.factory.annotation.Autowired;
+import org.zstack.compute.vm.UserdataBuilder;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.componentloader.PluginRegistry;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.db.Q;
+import org.zstack.core.thread.ChainTask;
+import org.zstack.core.thread.SyncTaskChain;
+import org.zstack.core.thread.ThreadFacade;
import org.zstack.header.Component;
import org.zstack.header.core.Completion;
import org.zstack.header.core.NoErrorCompletion;
@@ -14,9 +18,15 @@
import org.zstack.header.network.l3.L3NetworkVO;
import org.zstack.header.network.l3.L3NetworkVO_;
import org.zstack.header.network.service.*;
+import org.zstack.header.vm.VmAfterAttachNicExtensionPoint;
+import org.zstack.header.vm.VmInstanceInventory;
import org.zstack.header.vm.VmInstanceSpec;
+import org.zstack.header.vm.VmInstanceState;
+import org.zstack.header.vm.VmInstanceVO;
import org.zstack.header.vm.VmNicInventory;
import org.zstack.header.vm.VmNicSpec;
+import org.zstack.header.vm.VmNicVO;
+import org.zstack.header.vm.VmNicVO_;
import org.zstack.network.securitygroup.SecurityGroupGetDefaultRuleExtensionPoint;
import org.zstack.network.service.AbstractNetworkServiceExtension;
import org.zstack.utils.CollectionUtils;
@@ -25,11 +35,17 @@
import org.zstack.utils.network.IPv6Constants;
import java.util.*;
+import java.util.concurrent.TimeUnit;
/**
* Created by frank on 10/13/2015.
*/
-public class UserdataExtension extends AbstractNetworkServiceExtension implements Component, SecurityGroupGetDefaultRuleExtensionPoint {
+public class UserdataExtension extends AbstractNetworkServiceExtension implements Component,
+ SecurityGroupGetDefaultRuleExtensionPoint, VmAfterAttachNicExtensionPoint {
+ private static final int APPLY_USERDATA_AFTER_ATTACH_NIC_RETRY_TIMES = 3;
+ private static final long APPLY_USERDATA_AFTER_ATTACH_NIC_INITIAL_DELAY_SECONDS = 0;
+ private static final long APPLY_USERDATA_AFTER_ATTACH_NIC_RETRY_DELAY_SECONDS = 5;
+
private CLogger logger = Utils.getLogger(UserdataExtension.class);
@Autowired
@@ -38,6 +54,8 @@ public class UserdataExtension extends AbstractNetworkServiceExtension implement
private CloudBus bus;
@Autowired
private PluginRegistry pluginRgty;
+ @Autowired
+ private ThreadFacade thdf;
private Map backends = new HashMap();
@@ -150,6 +168,162 @@ public List getGroupMembers(String sgUuid, int ipVersion) {
return members;
}
+ private boolean isAttachedNicOnDefaultL3(String nicUuid, VmInstanceInventory vm) {
+ if (vm.getDefaultL3NetworkUuid() == null) {
+ return false;
+ }
+
+ String l3NetworkUuid = Q.New(VmNicVO.class)
+ .select(VmNicVO_.l3NetworkUuid)
+ .eq(VmNicVO_.uuid, nicUuid)
+ .eq(VmNicVO_.vmInstanceUuid, vm.getUuid())
+ .findValue();
+
+ return vm.getDefaultL3NetworkUuid().equals(l3NetworkUuid);
+ }
+
+ @Override
+ public void afterAttachNic(String nicUuid, VmInstanceInventory vm, Completion completion) {
+ if (!VmInstanceState.Running.toString().equals(vm.getState())) {
+ completion.success();
+ return;
+ }
+
+ if (vm.getDefaultL3NetworkUuid() == null || vm.getHostUuid() == null) {
+ completion.success();
+ return;
+ }
+
+ scheduleApplyUserdataAfterAttachNic(vm.getUuid(), nicUuid,
+ APPLY_USERDATA_AFTER_ATTACH_NIC_INITIAL_DELAY_SECONDS, 1);
+ completion.success();
+ }
+
+ @Override
+ public void afterAttachNicRollback(String nicUuid, VmInstanceInventory vmInstanceInventory, NoErrorCompletion completion) {
+ completion.done();
+ }
+
+ private boolean needRefreshUserdataAfterAttachNic(String nicUuid, VmInstanceInventory vm) {
+ if (!VmInstanceState.Running.toString().equals(vm.getState())) {
+ return false;
+ }
+
+ if (vm.getDefaultL3NetworkUuid() == null || vm.getHostUuid() == null) {
+ return false;
+ }
+
+ return !isAttachedNicOnDefaultL3(nicUuid, vm);
+ }
+
+ private void scheduleApplyUserdataAfterAttachNic(String vmUuid, String nicUuid, long delaySeconds, int currentAttempt) {
+ thdf.submitTimeoutTask(() -> thdf.chainSubmit(new ChainTask(null) {
+ @Override
+ public String getSyncSignature() {
+ return String.format("refresh-userdata-metadata-after-attach-nic-vm-%s", vmUuid);
+ }
+
+ @Override
+ public void run(SyncTaskChain chain) {
+ applyUserdataAfterAttachNic(vmUuid, nicUuid, currentAttempt, new NoErrorCompletion(chain) {
+ @Override
+ public void done() {
+ chain.next();
+ }
+ });
+ }
+
+ @Override
+ public String getName() {
+ return String.format("refresh-userdata-metadata-after-attach-nic-vm-%s", vmUuid);
+ }
+ }),
+ TimeUnit.SECONDS, delaySeconds);
+ }
+
+ private void applyUserdataAfterAttachNic(String vmUuid, String nicUuid, int currentAttempt, NoErrorCompletion completion) {
+ try {
+ VmInstanceVO vmVO = dbf.findByUuid(vmUuid, VmInstanceVO.class);
+ if (vmVO == null) {
+ completion.done();
+ return;
+ }
+
+ VmInstanceInventory vm = VmInstanceInventory.valueOf(vmVO);
+ if (!needRefreshUserdataAfterAttachNic(nicUuid, vm)) {
+ completion.done();
+ return;
+ }
+
+ L3NetworkVO defaultL3VO = Q.New(L3NetworkVO.class)
+ .eq(L3NetworkVO_.uuid, vm.getDefaultL3NetworkUuid())
+ .find();
+ if (defaultL3VO == null) {
+ completion.done();
+ return;
+ }
+
+ L3NetworkInventory defaultL3 = L3NetworkInventory.valueOf(defaultL3VO);
+ if (!defaultL3.getIpVersions().contains(IPv6Constants.IPv4)) {
+ completion.done();
+ return;
+ }
+
+ NetworkServiceProviderInventory provider = findProvider(defaultL3);
+ if (provider == null) {
+ completion.done();
+ return;
+ }
+
+ UserdataStruct struct = new UserdataStruct();
+ struct.setL3NetworkUuid(vm.getDefaultL3NetworkUuid());
+ struct.setParametersFromVmInventory(vm);
+ struct.setUserdataList(new UserdataBuilder().buildByVmUuid(vm.getUuid()));
+
+ UserdataBackend bkd = getUserdataBackend(provider.getType());
+ bkd.applyUserdata(struct, new Completion(null) {
+ @Override
+ public void success() {
+ completion.done();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ retryApplyUserdataAfterAttachNic(vmUuid, nicUuid, currentAttempt, errorCode, null);
+ completion.done();
+ }
+ });
+ } catch (Throwable t) {
+ if (t instanceof Error) {
+ logger.warn(String.format("fatal error happened when refreshing userdata metadata after attaching nic[uuid:%s] to vm[uuid:%s]",
+ nicUuid, vmUuid), t);
+ completion.done();
+ throw (Error) t;
+ }
+
+ retryApplyUserdataAfterAttachNic(vmUuid, nicUuid, currentAttempt, null, t);
+ completion.done();
+ }
+ }
+
+ private void retryApplyUserdataAfterAttachNic(String vmUuid, String nicUuid, int currentAttempt, ErrorCode errorCode, Throwable t) {
+ String reason = errorCode == null ? t.toString() : errorCode.toString();
+ String warn = String.format("failed to refresh userdata metadata after attaching nic[uuid:%s] to vm[uuid:%s], attempt %s/%s, %s",
+ nicUuid, vmUuid, currentAttempt, APPLY_USERDATA_AFTER_ATTACH_NIC_RETRY_TIMES, reason);
+ if (t == null) {
+ logger.warn(warn);
+ } else {
+ logger.warn(warn, t);
+ }
+
+ if (currentAttempt >= APPLY_USERDATA_AFTER_ATTACH_NIC_RETRY_TIMES) {
+ return;
+ }
+
+ scheduleApplyUserdataAfterAttachNic(vmUuid, nicUuid,
+ APPLY_USERDATA_AFTER_ATTACH_NIC_RETRY_DELAY_SECONDS, currentAttempt + 1);
+ }
+
@Override
public void releaseNetworkService(final VmInstanceSpec servedVm, Map data, final NoErrorCompletion completion) {
L3NetworkInventory defaultL3 = CollectionUtils.find(VmNicSpec.getL3NetworkInventoryOfSpec(servedVm.getL3Networks()),
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 90e576cad7f..bc13261fb77 100755
--- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java
+++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java
@@ -90,6 +90,28 @@ public static class UpdateVmPriorityCmd extends AgentCommand {
public static class UpdateVmPriorityRsp extends AgentResponse {
}
+ public static class VmShmemDevice {
+ public String name;
+ public String path;
+ public long size;
+ }
+
+ public static class HotPlugVmShmemCmd extends AgentCommand {
+ public String vmUuid;
+ public VmShmemDevice shmem;
+ }
+
+ public static class HotPlugVmShmemRsp extends AgentResponse {
+ }
+
+ public static class HotUnplugVmShmemCmd extends AgentCommand {
+ public String vmUuid;
+ public VmShmemDevice shmem;
+ }
+
+ public static class HotUnplugVmShmemRsp extends AgentResponse {
+ }
+
public static class ChangeVmNicStateCommand extends AgentCommand {
@GrayVersion(value = "5.0.0")
private String vmUuid;
@@ -339,6 +361,8 @@ public void setVersion(String version) {
public static class ConnectResponse extends AgentResponse {
private String libvirtVersion;
private String qemuVersion;
+ private boolean firstConnect;
+ private long agentStartTimeMillis;
public String getLibvirtVersion() {
return libvirtVersion;
@@ -355,6 +379,22 @@ public String getQemuVersion() {
public void setQemuVersion(String qemuVersion) {
this.qemuVersion = qemuVersion;
}
+
+ public boolean isFirstConnect() {
+ return firstConnect;
+ }
+
+ public void setFirstConnect(boolean firstConnect) {
+ this.firstConnect = firstConnect;
+ }
+
+ public long getAgentStartTimeMillis() {
+ return agentStartTimeMillis;
+ }
+
+ public void setAgentStartTimeMillis(long agentStartTimeMillis) {
+ this.agentStartTimeMillis = agentStartTimeMillis;
+ }
}
public static class PingCmd extends AgentCommand {
@@ -1014,6 +1054,42 @@ public static class SetVmConsolePasswordLiveCmd extends AgentCommand implements
public void setPassword(String password) { this.password = password; }
}
+ public static class SetupVmHaEnabledMetadataLiveCmd extends AgentCommand implements Serializable {
+ @GrayVersion(value = "5.5.22")
+ private String vmUuid;
+ @GrayVersion(value = "5.5.22")
+ private Boolean enableHa;
+
+ public String getVmUuid() {
+ return vmUuid;
+ }
+
+ public void setVmUuid(String vmUuid) {
+ this.vmUuid = vmUuid;
+ }
+
+ public Boolean getEnableHa() {
+ return enableHa;
+ }
+
+ public void setEnableHa(Boolean enableHa) {
+ this.enableHa = enableHa;
+ }
+ }
+
+ public static class ReconcileVmHaEnabledMetadataLiveCmd extends AgentCommand implements Serializable {
+ @GrayVersion(value = "5.5.22")
+ private List neverStopVmUuids;
+
+ public List