Apple FR66 - Host Control Plane Status (#213)
* Apple FR66 - Host Control Plane Status
* Apple FR66 - use Offline instead of Disconnected
* Apple FR66: fix smoke test test_router_host_control_state
* Apple FR66: reorder import
* Apple FR66: revert DetailsTab.vue and apply new changes
* Update PR: update en.json
* Update PR: add hostcontrolstate to routers/systemvms
* Update PR: test stop/start cloudstack-agent in smoke test
* Update PR: fix UI build error (The template root requires exactly one element vue/valid-template-root)
* Update PR: update message on ui
* Update PR: Disable rebootVM and create vm/volume snapshot for KVM VMs
* Update PR: add more unit tests
diff --git a/api/src/main/java/com/cloud/host/ControlState.java b/api/src/main/java/com/cloud/host/ControlState.java
new file mode 100644
index 0000000..335125d
--- /dev/null
+++ b/api/src/main/java/com/cloud/host/ControlState.java
@@ -0,0 +1,41 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.host;
+
+import com.cloud.resource.ResourceState;
+
+public enum ControlState {
+ Enabled,
+ Disabled,
+ Offline,
+ Maintenance,
+ Unknown;
+
+ public static ControlState getControlState(Status hostStatus, ResourceState hostResourceState) {
+ if (hostStatus == null || Status.Unknown.equals(hostStatus) || hostResourceState == null) {
+ return ControlState.Unknown;
+ } else if (hostStatus.lostConnection()) {
+ return Offline;
+ } else if (ResourceState.isMaintenanceState(hostResourceState)) {
+ return Maintenance;
+ } else if (ResourceState.Enabled.equals(hostResourceState)) {
+ return Enabled;
+ } else {
+ return Disabled;
+ }
+ }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
index 83fcad5..1d4bb98 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -192,6 +192,7 @@
public static final String HOST_ID = "hostid";
public static final String HOST_IDS = "hostids";
public static final String HOST_NAME = "hostname";
+ public static final String HOST_CONTROL_STATE = "hostcontrolstate";
public static final String HOSTS_MAP = "hostsmap";
public static final String HYPERVISOR = "hypervisor";
public static final String INLINE = "inline";
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DomainRouterResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DomainRouterResponse.java
index 66f4030..0f63561 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/DomainRouterResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/DomainRouterResponse.java
@@ -89,6 +89,10 @@
@Param(description = "the hostname for the router")
private String hostName;
+ @SerializedName(ApiConstants.HOST_CONTROL_STATE)
+ @Param(description = "the control state of the host for the router")
+ private String hostControlState;
+
@SerializedName("hypervisor")
@Param(description = "the hypervisor on which the template runs")
private String hypervisor;
@@ -298,6 +302,10 @@
this.hostName = hostName;
}
+ public void setHostControlState(String hostControlState) {
+ this.hostControlState = hostControlState;
+ }
+
public String getHypervisor() {
return hypervisor;
}
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/SystemVmResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/SystemVmResponse.java
index ce2b406..69b9b4c 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/SystemVmResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/SystemVmResponse.java
@@ -90,6 +90,10 @@
@Param(description = "the hostname for the system VM")
private String hostName;
+ @SerializedName(ApiConstants.HOST_CONTROL_STATE)
+ @Param(description = "the control state of the host for the system VM")
+ private String hostControlState;
+
@SerializedName("hypervisor")
@Param(description = "the hypervisor on which the template runs")
private String hypervisor;
@@ -283,6 +287,14 @@
this.hostName = hostName;
}
+ public String getHostControlState() {
+ return hostControlState;
+ }
+
+ public void setHostControlState(String hostControlState) {
+ this.hostControlState = hostControlState;
+ }
+
public String getHypervisor() {
return hypervisor;
}
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java
index 17b577b..6e1c5ae 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java
@@ -118,6 +118,10 @@
@Param(description = "the name of the host for the virtual machine")
private String hostName;
+ @SerializedName(ApiConstants.HOST_CONTROL_STATE)
+ @Param(description = "the control state of the host for the virtual machine")
+ private String hostControlState;
+
@SerializedName(ApiConstants.TEMPLATE_ID)
@Param(description = "the ID of the template for the virtual machine. A -1 is returned if the virtual machine was created from an ISO file.")
private String templateId;
@@ -449,6 +453,10 @@
return hostName;
}
+ public String getHostControlState() {
+ return hostControlState;
+ }
+
public String getTemplateId() {
return templateId;
}
@@ -687,6 +695,10 @@
this.hostName = hostName;
}
+ public void setHostControlState(String hostControlState) {
+ this.hostControlState = hostControlState;
+ }
+
public void setTemplateId(String templateId) {
this.templateId = templateId;
}
diff --git a/api/src/test/java/com/cloud/host/ControlStateTest.java b/api/src/test/java/com/cloud/host/ControlStateTest.java
new file mode 100644
index 0000000..7b27d4c
--- /dev/null
+++ b/api/src/test/java/com/cloud/host/ControlStateTest.java
@@ -0,0 +1,98 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.host;
+
+import com.cloud.resource.ResourceState;
+
+import junit.framework.TestCase;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ControlStateTest extends TestCase {
+
+ void verifyHostControlState(Status hostStatus, ResourceState hostResourceState, ControlState expectedControlState) {
+ Assert.assertEquals(expectedControlState, ControlState.getControlState(hostStatus, hostResourceState));
+ }
+
+ @Test
+ public void testHostControlState() {
+ // Unknown state
+ verifyHostControlState(null, null, ControlState.Unknown);
+ verifyHostControlState(null, ResourceState.Enabled, ControlState.Unknown);
+ verifyHostControlState(Status.Up, null, ControlState.Unknown);
+ verifyHostControlState(Status.Disconnected, null, ControlState.Unknown);
+ verifyHostControlState(Status.Down, null, ControlState.Unknown);
+
+ verifyHostControlState(Status.Unknown, null, ControlState.Unknown);
+ verifyHostControlState(Status.Unknown, ResourceState.Enabled, ControlState.Unknown);
+ verifyHostControlState(Status.Unknown, ResourceState.ErrorInPrepareForMaintenance, ControlState.Unknown);
+ verifyHostControlState(Status.Unknown, ResourceState.PrepareForMaintenance, ControlState.Unknown);
+ verifyHostControlState(Status.Unknown, ResourceState.ErrorInMaintenance, ControlState.Unknown);
+ verifyHostControlState(Status.Unknown, ResourceState.Maintenance, ControlState.Unknown);
+ verifyHostControlState(Status.Unknown, ResourceState.Creating, ControlState.Unknown);
+ verifyHostControlState(Status.Unknown, ResourceState.Disabled, ControlState.Unknown);
+ verifyHostControlState(Status.Unknown, ResourceState.Error, ControlState.Unknown);
+ verifyHostControlState(Status.Unknown, ResourceState.Degraded, ControlState.Unknown);
+
+ // Host is Up and Enabled
+ verifyHostControlState(Status.Creating, ResourceState.Enabled, ControlState.Enabled);
+ verifyHostControlState(Status.Connecting, ResourceState.Enabled, ControlState.Enabled);
+ verifyHostControlState(Status.Up, ResourceState.Enabled, ControlState.Enabled);
+
+ // Host is Up and not Enabled
+ verifyHostControlState(Status.Up, ResourceState.Creating, ControlState.Disabled);
+ verifyHostControlState(Status.Up, ResourceState.Disabled, ControlState.Disabled);
+ verifyHostControlState(Status.Up, ResourceState.Error, ControlState.Disabled);
+ verifyHostControlState(Status.Up, ResourceState.Degraded, ControlState.Disabled);
+
+ // Host is Up and Maintenance mode
+ verifyHostControlState(Status.Up, ResourceState.ErrorInPrepareForMaintenance, ControlState.Maintenance);
+ verifyHostControlState(Status.Up, ResourceState.PrepareForMaintenance, ControlState.Maintenance);
+ verifyHostControlState(Status.Up, ResourceState.ErrorInMaintenance, ControlState.Maintenance);
+ verifyHostControlState(Status.Up, ResourceState.Maintenance, ControlState.Maintenance);
+
+ // Host is Creating and not Enabled
+ verifyHostControlState(Status.Creating, ResourceState.Creating, ControlState.Disabled);
+ verifyHostControlState(Status.Creating, ResourceState.Disabled, ControlState.Disabled);
+ verifyHostControlState(Status.Creating, ResourceState.Error, ControlState.Disabled);
+ verifyHostControlState(Status.Creating, ResourceState.Degraded, ControlState.Disabled);
+
+ // Host is Connecting and not Enabled
+ verifyHostControlState(Status.Connecting, ResourceState.Creating, ControlState.Disabled);
+ verifyHostControlState(Status.Connecting, ResourceState.Disabled, ControlState.Disabled);
+ verifyHostControlState(Status.Connecting, ResourceState.Error, ControlState.Disabled);
+ verifyHostControlState(Status.Connecting, ResourceState.Degraded, ControlState.Disabled);
+
+ // Host in other states and Enabled
+ verifyHostControlState(Status.Down, ResourceState.Enabled, ControlState.Offline);
+ verifyHostControlState(Status.Disconnected, ResourceState.Enabled, ControlState.Offline);
+ verifyHostControlState(Status.Alert, ResourceState.Enabled, ControlState.Offline);
+ verifyHostControlState(Status.Removed, ResourceState.Enabled, ControlState.Offline);
+ verifyHostControlState(Status.Error, ResourceState.Enabled, ControlState.Offline);
+ verifyHostControlState(Status.Rebalancing, ResourceState.Enabled, ControlState.Offline);
+
+ // Host in other states and Disabled
+ verifyHostControlState(Status.Down, ResourceState.Disabled, ControlState.Offline);
+ verifyHostControlState(Status.Disconnected, ResourceState.Disabled, ControlState.Offline);
+ verifyHostControlState(Status.Alert, ResourceState.Disabled, ControlState.Offline);
+ verifyHostControlState(Status.Removed, ResourceState.Disabled, ControlState.Offline);
+ verifyHostControlState(Status.Error, ResourceState.Disabled, ControlState.Offline);
+ verifyHostControlState(Status.Rebalancing, ResourceState.Disabled, ControlState.Offline);
+ }
+
+}
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41600to41610.sql b/engine/schema/src/main/resources/META-INF/db/schema-41600to41610.sql
index 4d0f3f2..56a0d36 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41600to41610.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41600to41610.sql
@@ -586,6 +586,8 @@
`host`.`uuid` AS `host_uuid`,
`host`.`name` AS `host_name`,
`host`.`cluster_id` AS `cluster_id`,
+ `host`.`status` AS `host_status`,
+ `host`.`resource_state` AS `host_resource_state`,
`vm_template`.`id` AS `template_id`,
`vm_template`.`uuid` AS `template_uuid`,
`vm_template`.`name` AS `template_name`,
@@ -725,3 +727,110 @@
AND (`custom_speed`.`name` = 'CpuSpeed'))))
LEFT JOIN `user_vm_details` `custom_ram_size` ON (((`custom_ram_size`.`vm_id` = `vm_instance`.`id`)
AND (`custom_ram_size`.`name` = 'memory'))));
+
+DROP VIEW IF EXISTS `cloud`.`domain_router_view`;
+CREATE VIEW `cloud`.`domain_router_view` AS
+select
+ vm_instance.id id,
+ vm_instance.name name,
+ account.id account_id,
+ account.uuid account_uuid,
+ account.account_name account_name,
+ account.type account_type,
+ domain.id domain_id,
+ domain.uuid domain_uuid,
+ domain.name domain_name,
+ domain.path domain_path,
+ projects.id project_id,
+ projects.uuid project_uuid,
+ projects.name project_name,
+ vm_instance.uuid uuid,
+ vm_instance.created created,
+ vm_instance.state state,
+ vm_instance.removed removed,
+ vm_instance.pod_id pod_id,
+ vm_instance.instance_name instance_name,
+ host_pod_ref.uuid pod_uuid,
+ data_center.id data_center_id,
+ data_center.uuid data_center_uuid,
+ data_center.name data_center_name,
+ data_center.networktype data_center_type,
+ data_center.dns1 dns1,
+ data_center.dns2 dns2,
+ data_center.ip6_dns1 ip6_dns1,
+ data_center.ip6_dns2 ip6_dns2,
+ host.id host_id,
+ host.uuid host_uuid,
+ host.name host_name,
+ host.hypervisor_type,
+ host.cluster_id cluster_id,
+ host.status host_status,
+ host.resource_state host_resource_state,
+ vm_template.id template_id,
+ vm_template.uuid template_uuid,
+ service_offering.id service_offering_id,
+ disk_offering.uuid service_offering_uuid,
+ disk_offering.name service_offering_name,
+ nics.id nic_id,
+ nics.uuid nic_uuid,
+ nics.network_id network_id,
+ nics.ip4_address ip_address,
+ nics.ip6_address ip6_address,
+ nics.ip6_gateway ip6_gateway,
+ nics.ip6_cidr ip6_cidr,
+ nics.default_nic is_default_nic,
+ nics.gateway gateway,
+ nics.netmask netmask,
+ nics.mac_address mac_address,
+ nics.broadcast_uri broadcast_uri,
+ nics.isolation_uri isolation_uri,
+ vpc.id vpc_id,
+ vpc.uuid vpc_uuid,
+ vpc.name vpc_name,
+ networks.uuid network_uuid,
+ networks.name network_name,
+ networks.network_domain network_domain,
+ networks.traffic_type traffic_type,
+ networks.guest_type guest_type,
+ async_job.id job_id,
+ async_job.uuid job_uuid,
+ async_job.job_status job_status,
+ async_job.account_id job_account_id,
+ domain_router.template_version template_version,
+ domain_router.scripts_version scripts_version,
+ domain_router.is_redundant_router is_redundant_router,
+ domain_router.redundant_state redundant_state,
+ domain_router.stop_pending stop_pending,
+ domain_router.role role
+from
+ `cloud`.`domain_router`
+ inner join
+ `cloud`.`vm_instance` ON vm_instance.id = domain_router.id
+ inner join
+ `cloud`.`account` ON vm_instance.account_id = account.id
+ inner join
+ `cloud`.`domain` ON vm_instance.domain_id = domain.id
+ left join
+ `cloud`.`host_pod_ref` ON vm_instance.pod_id = host_pod_ref.id
+ left join
+ `cloud`.`projects` ON projects.project_account_id = account.id
+ left join
+ `cloud`.`data_center` ON vm_instance.data_center_id = data_center.id
+ left join
+ `cloud`.`host` ON vm_instance.host_id = host.id
+ left join
+ `cloud`.`vm_template` ON vm_instance.vm_template_id = vm_template.id
+ left join
+ `cloud`.`service_offering` ON vm_instance.service_offering_id = service_offering.id
+ left join
+ `cloud`.`disk_offering` ON vm_instance.service_offering_id = disk_offering.id
+ left join
+ `cloud`.`nics` ON vm_instance.id = nics.instance_id and nics.removed is null
+ left join
+ `cloud`.`networks` ON nics.network_id = networks.id
+ left join
+ `cloud`.`vpc` ON domain_router.vpc_id = vpc.id and vpc.removed is null
+ left join
+ `cloud`.`async_job` ON async_job.instance_id = vm_instance.id
+ and async_job.instance_type = 'DomainRouter'
+ and async_job.job_status = 0;
diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
index 8c58ab6..a713bdc 100644
--- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java
+++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
@@ -36,6 +36,7 @@
import javax.inject.Inject;
+import com.cloud.host.ControlState;
import com.cloud.utils.security.CertificateHelper;
import com.cloud.user.UserData;
import org.apache.cloudstack.acl.ControlledEntity;
@@ -1499,6 +1500,7 @@
if (host != null) {
vmResponse.setHostId(host.getUuid());
vmResponse.setHostName(host.getName());
+ vmResponse.setHostControlState(ControlState.getControlState(host.getStatus(), host.getResourceState()).toString());
vmResponse.setHypervisor(host.getHypervisorType().toString());
}
} else if (vm.getLastHostId() != null) {
diff --git a/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java
index 96c129a..ed8a926 100644
--- a/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java
@@ -37,6 +37,7 @@
import com.cloud.api.ApiResponseHelper;
import com.cloud.api.query.vo.DomainRouterJoinVO;
import com.cloud.dc.HostPodVO;
+import com.cloud.host.ControlState;
import com.cloud.network.Networks.TrafficType;
import com.cloud.network.router.VirtualRouter;
import com.cloud.network.router.VirtualRouter.Role;
@@ -110,6 +111,7 @@
if (router.getHostId() != null) {
routerResponse.setHostId(router.getHostUuid());
routerResponse.setHostName(router.getHostName());
+ routerResponse.setHostControlState(ControlState.getControlState(router.getHostStatus(), router.getHostResourceState()).toString());
routerResponse.setHypervisor(router.getHypervisorType().toString());
}
routerResponse.setPodId(router.getPodUuid());
diff --git a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
index 28fb84d..bd3761b 100644
--- a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
@@ -49,6 +49,7 @@
import com.cloud.api.ApiResponseHelper;
import com.cloud.api.query.vo.UserVmJoinVO;
import com.cloud.gpu.GPU;
+import com.cloud.host.ControlState;
import com.cloud.service.ServiceOfferingDetailsVO;
import com.cloud.storage.GuestOS;
import com.cloud.user.Account;
@@ -166,6 +167,9 @@
userVmResponse.setHostId(userVm.getHostUuid());
userVmResponse.setHostName(userVm.getHostName());
}
+ if (userVm.getHostStatus() != null) {
+ userVmResponse.setHostControlState(ControlState.getControlState(userVm.getHostStatus(), userVm.getHostResourceState()).toString());
+ }
if (details.contains(VMDetails.all) || details.contains(VMDetails.tmpl)) {
userVmResponse.setTemplateId(userVm.getTemplateUuid());
diff --git a/server/src/main/java/com/cloud/api/query/vo/DomainRouterJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/DomainRouterJoinVO.java
index d34bd5e..096be23 100644
--- a/server/src/main/java/com/cloud/api/query/vo/DomainRouterJoinVO.java
+++ b/server/src/main/java/com/cloud/api/query/vo/DomainRouterJoinVO.java
@@ -26,11 +26,13 @@
import javax.persistence.Id;
import javax.persistence.Table;
+import com.cloud.host.Status;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.network.Network.GuestType;
import com.cloud.network.Networks.TrafficType;
import com.cloud.network.router.VirtualRouter;
import com.cloud.network.router.VirtualRouter.RedundantState;
+import com.cloud.resource.ResourceState;
import com.cloud.utils.db.GenericDao;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachine.State;
@@ -127,6 +129,12 @@
@Column(name = "host_name", nullable = false)
private String hostName;
+ @Column(name = "host_status")
+ private Status hostStatus;
+
+ @Column(name = "host_resource_state")
+ private ResourceState hostResourceState;
+
@Column(name="hypervisor_type")
@Enumerated(value=EnumType.STRING)
private Hypervisor.HypervisorType hypervisorType;
@@ -346,6 +354,14 @@
return hostName;
}
+ public Status getHostStatus() {
+ return hostStatus;
+ }
+
+ public ResourceState getHostResourceState() {
+ return hostResourceState;
+ }
+
public Hypervisor.HypervisorType getHypervisorType() {
return hypervisorType;
}
diff --git a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java
index c3ad84f..87d7b88 100644
--- a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java
+++ b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java
@@ -29,9 +29,11 @@
import javax.persistence.Table;
import javax.persistence.Transient;
+import com.cloud.host.Status;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.network.Network.GuestType;
import com.cloud.network.Networks.TrafficType;
+import com.cloud.resource.ResourceState;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.Volume;
import com.cloud.utils.db.GenericDao;
@@ -169,9 +171,15 @@
@Column(name = "host_uuid")
private String hostUuid;
- @Column(name = "host_name", nullable = false)
+ @Column(name = "host_name")
private String hostName;
+ @Column(name = "host_status")
+ private Status hostStatus;
+
+ @Column(name = "host_resource_state")
+ private ResourceState hostResourceState;
+
@Column(name = "template_id", updatable = true, nullable = true, length = 17)
private long templateId;
@@ -596,6 +604,14 @@
return hostName;
}
+ public Status getHostStatus() {
+ return hostStatus;
+ }
+
+ public ResourceState getHostResourceState() {
+ return hostResourceState;
+ }
+
public long getTemplateId() {
return templateId;
}
diff --git a/test/integration/smoke/test_host_control_state.py b/test/integration/smoke/test_host_control_state.py
new file mode 100644
index 0000000..3e39872
--- /dev/null
+++ b/test/integration/smoke/test_host_control_state.py
@@ -0,0 +1,254 @@
+#!/usr/bin/env python
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""
+Tests for host control state
+"""
+
+from marvin.cloudstackAPI import updateHost
+from nose.plugins.attrib import attr
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.lib.common import (get_domain,
+ get_zone,
+ get_template,
+ list_hosts,
+ list_routers,
+ list_ssvms)
+from marvin.lib.base import (Account,
+ Domain,
+ Host,
+ ServiceOffering,
+ VirtualMachine)
+from marvin.sshClient import SshClient
+import time
+
+
+class TestHostControlState(cloudstackTestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ cls.testClient = super(TestHostControlState, cls).getClsTestClient()
+ cls.apiclient = cls.testClient.getApiClient()
+ cls.services = cls.testClient.getParsedTestDataConfig()
+ # Get Zone, Domain and templates
+ cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
+ cls.hypervisor = cls.testClient.getHypervisorInfo()
+ cls.hostConfig = cls.config.__dict__["zones"][0].__dict__["pods"][0].__dict__["clusters"][0].__dict__["hosts"][0].__dict__
+
+ cls.template = get_template(
+ cls.apiclient,
+ cls.zone.id,
+ cls.hypervisor
+ )
+
+ cls.services["virtual_machine"]["zoneid"] = cls.zone.id
+ cls.services["template"] = cls.template.id
+ cls.services["zoneid"] = cls.zone.id
+
+ cls.domain = Domain.create(
+ cls.apiclient,
+ cls.services["acl"]["domain1"]
+ )
+ cls.account = Account.create(
+ cls.apiclient,
+ cls.services["account"],
+ domainid=cls.domain.id
+ )
+ cls.service_offering = ServiceOffering.create(
+ cls.apiclient,
+ cls.services["service_offerings"]["tiny"]
+ )
+ cls.vm = VirtualMachine.create(
+ cls.apiclient,
+ cls.services["virtual_machine"],
+ templateid=cls.template.id,
+ accountid=cls.account.name,
+ domainid=cls.account.domainid,
+ serviceofferingid=cls.service_offering.id
+ )
+
+ cls._cleanup = [
+ cls.domain,
+ cls.account,
+ cls.vm,
+ cls.service_offering
+ ]
+ return
+
+ @classmethod
+ def tearDownClass(cls):
+ super(TestHostControlState, cls).tearDownClass()
+
+ def setUp(self):
+ self.apiclient = self.testClient.getApiClient()
+ self.cleanup = []
+ return
+
+ def tearDown(self):
+ super(TestHostControlState, self).tearDown()
+
+ def disable_host(self, id):
+ cmd = updateHost.updateHostCmd()
+ cmd.id = id
+ cmd.allocationstate = "Disable"
+ response = self.apiclient.updateHost(cmd)
+ self.assertEqual(response.resourcestate, "Disabled")
+
+ def enable_host(self, id):
+ cmd = updateHost.updateHostCmd()
+ cmd.id = id
+ cmd.allocationstate = "Enable"
+ response = self.apiclient.updateHost(cmd)
+ self.assertEqual(response.resourcestate, "Enabled")
+
+ def get_host_ipaddress(self, hostId):
+ hosts = list_hosts(
+ self.apiclient,
+ type='Routing',
+ id=hostId
+ )
+ return hosts[0].ipaddress
+
+ def stop_agent(self, host_ipaddress):
+ SshClient(host_ipaddress, port=22, user=self.hostConfig["username"], passwd=self.hostConfig["password"]).execute\
+ ("systemctl stop cloudstack-agent || service cloudstack-agent stop")
+
+ def start_agent(self, host_ipaddress):
+ SshClient(host_ipaddress, port=22, user=self.hostConfig["username"], passwd=self.hostConfig["password"]).execute\
+ ("systemctl start cloudstack-agent || service cloudstack-agent start")
+
+ def verify_uservm_host_control_state(self, vm_id, state):
+ list_vms = VirtualMachine.list(
+ self.apiclient,
+ id=vm_id
+ )
+ vm = list_vms[0]
+ self.assertEqual(vm.hostcontrolstate,
+ state,
+ msg="host control state should be %s, but it is %s" % (state, vm.hostcontrolstate))
+
+ def verify_ssvm_host_control_state(self, vm_id, state):
+ list_ssvm_response = list_ssvms(
+ self.apiclient,
+ id=vm_id
+ )
+ vm = list_ssvm_response[0]
+ self.assertEqual(vm.hostcontrolstate,
+ state,
+ msg="host control state should be %s, but it is %s" % (state, vm.hostcontrolstate))
+
+ def verify_router_host_control_state(self, vm_id, state):
+ list_router_response = list_routers(
+ self.apiclient,
+ id=vm_id
+ )
+ vm = list_router_response[0]
+ self.assertEqual(vm.hostcontrolstate,
+ state,
+ msg="host control state should be %s, but it is %s" % (state, vm.hostcontrolstate))
+
+ @attr(tags=["basic", "advanced"], required_hardware="false")
+ def test_uservm_host_control_state(self):
+ """ Verify host control state for user vm """
+ # 1. verify hostcontrolstate = Enabled
+ # 2. Disable the host, verify hostcontrolstate = Disabled
+
+ list_vms = VirtualMachine.list(
+ self.apiclient,
+ id=self.vm.id
+ )
+ host_id = list_vms[0].hostid
+
+ self.verify_uservm_host_control_state(self.vm.id, "Enabled")
+
+ self.disable_host(host_id)
+ self.verify_uservm_host_control_state(self.vm.id, "Disabled")
+
+ if self.hypervisor == "kvm":
+ host_ipaddress = self.get_host_ipaddress(host_id)
+
+ self.stop_agent(host_ipaddress)
+ time.sleep(5) # wait for the host to be Disconnected
+ self.verify_uservm_host_control_state(self.vm.id, "Offline")
+
+ self.enable_host(host_id)
+ self.verify_uservm_host_control_state(self.vm.id, "Offline")
+
+ self.start_agent(host_ipaddress)
+ time.sleep(10) # wait for the host to be Up
+ self.verify_uservm_host_control_state(self.vm.id, "Enabled")
+
+ else:
+ self.enable_host(host_id)
+ self.verify_uservm_host_control_state(self.vm.id, "Enabled")
+
+ @attr(tags=["basic", "advanced"], required_hardware="false")
+ def test_ssvm_host_control_state(self):
+ """ Verify host control state for systemvm """
+ # 1. verify hostcontrolstate = Enabled
+ # 2. Disable the host, verify hostcontrolstate = Disabled
+
+ list_ssvm_response = list_ssvms(
+ self.apiclient,
+ systemvmtype='secondarystoragevm',
+ state='Running',
+ zoneid=self.zone.id
+ )
+ self.assertEqual(
+ isinstance(list_ssvm_response, list),
+ True,
+ "Check list response returns a valid list"
+ )
+ ssvm = list_ssvm_response[0]
+ host_id = ssvm.hostid
+
+ self.verify_ssvm_host_control_state(ssvm.id, "Enabled")
+
+ self.disable_host(host_id)
+ self.verify_ssvm_host_control_state(ssvm.id, "Disabled")
+
+ self.enable_host(host_id)
+ self.verify_ssvm_host_control_state(ssvm.id, "Enabled")
+
+ @attr(tags=["basic", "advanced"], required_hardware="false")
+ def test_router_host_control_state(self):
+ """ Verify host control state for router """
+ # 1. verify hostcontrolstate = Enabled
+ # 2. Disable the host, verify hostcontrolstate = Disabled
+
+ list_router_response = list_routers(
+ self.apiclient,
+ state='Running',
+ listall=True,
+ zoneid=self.zone.id
+ )
+ self.assertEqual(
+ isinstance(list_router_response, list),
+ True,
+ "Check list response returns a valid list"
+ )
+ router = list_router_response[0]
+ host_id = router.hostid
+
+ self.verify_router_host_control_state(router.id, "Enabled")
+
+ self.disable_host(host_id)
+ self.verify_router_host_control_state(router.id, "Disabled")
+
+ self.enable_host(host_id)
+ self.verify_router_host_control_state(router.id, "Enabled")
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index 83ba35a..160f5e3 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -1068,6 +1068,7 @@
"label.host.name": "Host Name",
"label.host.tag": "Host Tag",
"label.host.ueficapability": "UEFI Supported",
+"label.hostcontrolstate": "Control Plane Status",
"label.hostid": "Host",
"label.hostname": "Host",
"label.hostnamelabel": "Host Name",
@@ -3041,6 +3042,8 @@
"message.guestnetwork.state.implementing": "Indicates the network configuration is being implemented",
"message.guestnetwork.state.setup": "Indicates the network configuration is setup",
"message.guestnetwork.state.shutdown": "Indicates the network configuration is being destroyed",
+"message.host.controlstate": "The Control Plane Status of this instance is ",
+"message.host.controlstate.retry": "Some actions on this instance will fail, if so please wait a while and retry.",
"message.host.dedicated": "Host Dedicated",
"message.host.dedication.released": "Host dedication released",
"message.info.cloudian.console": "Cloudian Management Console should open in another window",
diff --git a/ui/src/components/view/DetailsTab.vue b/ui/src/components/view/DetailsTab.vue
index 10b759f..c043efa 100644
--- a/ui/src/components/view/DetailsTab.vue
+++ b/ui/src/components/view/DetailsTab.vue
@@ -16,52 +16,61 @@
// under the License.
<template>
- <a-list
- size="small"
- :dataSource="fetchDetails()">
- <a-list-item slot="renderItem" slot-scope="item" v-if="item in resource">
- <div>
- <strong>{{ item === 'service' ? $t('label.supportedservices') : $t('label.' + String(item).toLowerCase()) }}</strong>
- <br/>
- <div v-if="Array.isArray(resource[item]) && item === 'service'">
- <div v-for="(service, idx) in resource[item]" :key="idx">
- {{ service.name }} : {{ service.provider[0].name }}
+ <div>
+ <a-alert type="error" v-if="['vm', 'systemvm', 'router', 'ilbvm'].includes($route.meta.name) && 'hostcontrolstate' in resource && resource.hostcontrolstate !== 'Enabled'">
+ <template #message>
+ <div class="title">
+ {{ $t('message.host.controlstate') }} {{ resource.hostcontrolstate }}. {{ $t('message.host.controlstate.retry') }}
+ </div>
+ </template>
+ </a-alert>
+ <a-list
+ size="small"
+ :dataSource="fetchDetails()">
+ <a-list-item slot="renderItem" slot-scope="item" v-if="item in resource">
+ <div>
+ <strong>{{ item === 'service' ? $t('label.supportedservices') : $t('label.' + String(item).toLowerCase()) }}</strong>
+ <br/>
+ <div v-if="Array.isArray(resource[item]) && item === 'service'">
+ <div v-for="(service, idx) in resource[item]" :key="idx">
+ {{ service.name }} : {{ service.provider[0].name }}
+ </div>
</div>
- </div>
- <div v-else-if="$route.meta.name === 'backup' && item === 'volumes'">
- <div v-for="(volume, idx) in JSON.parse(resource[item])" :key="idx">
- <router-link :to="{ path: '/volume/' + volume.uuid }">{{ volume.type }} - {{ volume.path }}</router-link> ({{ parseFloat(volume.size / (1024.0 * 1024.0 * 1024.0)).toFixed(1) }} GB)
+ <div v-else-if="$route.meta.name === 'backup' && item === 'volumes'">
+ <div v-for="(volume, idx) in JSON.parse(resource[item])" :key="idx">
+ <router-link :to="{ path: '/volume/' + volume.uuid }">{{ volume.type }} - {{ volume.path }}</router-link> ({{ parseFloat(volume.size / (1024.0 * 1024.0 * 1024.0)).toFixed(1) }} GB)
+ </div>
</div>
- </div>
- <div v-else-if="$route.meta.name === 'computeoffering' && item === 'rootdisksize'">
- <div>
- {{ resource.rootdisksize }} GB
+ <div v-else-if="$route.meta.name === 'computeoffering' && item === 'rootdisksize'">
+ <div>
+ {{ resource.rootdisksize }} GB
+ </div>
</div>
+ <div v-else-if="['name', 'type'].includes(item)">
+ <span v-if="['USER.LOGIN', 'USER.LOGOUT', 'ROUTER.HEALTH.CHECKS', 'FIREWALL.CLOSE', 'ALERT.SERVICE.DOMAINROUTER'].includes(resource[item])">{{ $t(resource[item].toLowerCase()) }}</span>
+ <span v-else>{{ resource[item] }}</span>
+ </div>
+ <div v-else-if="['created', 'sent', 'lastannotated'].includes(item)">
+ {{ $toLocaleDate(resource[item]) }}
+ </div>
+ <div v-else-if="$route.meta.name === 'userdata' && item === 'userdata'">
+ <div style="white-space: pre-wrap;"> {{ decodeUserData(resource.userdata) }} </div>
+ </div>
+ <div v-else>{{ resource[item] }}</div>
</div>
- <div v-else-if="['name', 'type'].includes(item)">
- <span v-if="['USER.LOGIN', 'USER.LOGOUT', 'ROUTER.HEALTH.CHECKS', 'FIREWALL.CLOSE', 'ALERT.SERVICE.DOMAINROUTER'].includes(resource[item])">{{ $t(resource[item].toLowerCase()) }}</span>
- <span v-else>{{ resource[item] }}</span>
+ </a-list-item>
+ <a-list-item slot="renderItem" slot-scope="item" v-else-if="item === 'ip6address' && ipV6Address && ipV6Address.length > 0">
+ <div>
+ <strong>{{ $t('label.' + String(item).toLowerCase()) }}</strong>
+ <br/>
+ <div>{{ ipV6Address }}</div>
</div>
- <div v-else-if="['created', 'sent', 'lastannotated'].includes(item)">
- {{ $toLocaleDate(resource[item]) }}
- </div>
- <div v-else-if="$route.meta.name === 'userdata' && item === 'userdata'">
- <div style="white-space: pre-wrap;"> {{ decodeUserData(resource.userdata) }} </div>
- </div>
- <div v-else>{{ resource[item] }}</div>
- </div>
- </a-list-item>
- <a-list-item slot="renderItem" slot-scope="item" v-else-if="item === 'ip6address' && ipV6Address && ipV6Address.length > 0">
- <div>
- <strong>{{ $t('label.' + String(item).toLowerCase()) }}</strong>
- <br/>
- <div>{{ ipV6Address }}</div>
- </div>
- </a-list-item>
- <HostInfo :resource="resource" v-if="$route.meta.name === 'host' && 'listHosts' in $store.getters.apis" />
- <DedicateData :resource="resource" v-if="dedicatedSectionActive" />
- <VmwareData :resource="resource" v-if="$route.meta.name === 'zone' && 'listVmwareDcs' in $store.getters.apis" />
- </a-list>
+ </a-list-item>
+ <HostInfo :resource="resource" v-if="$route.meta.name === 'host' && 'listHosts' in $store.getters.apis" />
+ <DedicateData :resource="resource" v-if="dedicatedSectionActive" />
+ <VmwareData :resource="resource" v-if="$route.meta.name === 'zone' && 'listVmwareDcs' in $store.getters.apis" />
+ </a-list>
+ </div>
</template>
<script>
diff --git a/ui/src/components/widgets/Console.vue b/ui/src/components/widgets/Console.vue
index 6f4a3e3..f03b3f7 100644
--- a/ui/src/components/widgets/Console.vue
+++ b/ui/src/components/widgets/Console.vue
@@ -19,7 +19,7 @@
<a
v-if="['vm', 'systemvm', 'router', 'ilbvm'].includes($route.meta.name) && 'updateVirtualMachine' in $store.getters.apis && 'createConsoleEndpoint' in $store.getters.apis"
@click="consoleUrl">
- <a-button style="margin-left: 5px" shape="circle" type="dashed" :size="size" :disabled="['Stopped', 'Error', 'Destroyed'].includes(resource.state)" >
+ <a-button style="margin-left: 5px" shape="circle" type="dashed" :size="size" :disabled="['Stopped', 'Error', 'Destroyed'].includes(resource.state) || resource.hostcontrolstate === 'Offline'" >
<a-icon type="code" />
</a-button>
</a>
diff --git a/ui/src/config/section/compute.js b/ui/src/config/section/compute.js
index 867c229..8bf6e3f 100644
--- a/ui/src/config/section/compute.js
+++ b/ui/src/config/section/compute.js
@@ -66,7 +66,7 @@
return fields
},
searchFilters: ['name', 'zoneid', 'domainid', 'account', 'tags'],
- details: ['displayname', 'name', 'id', 'state', 'ipaddress', 'ip6address', 'templatename', 'ostypename', 'serviceofferingname', 'isdynamicallyscalable', 'haenable', 'hypervisor', 'boottype', 'bootmode', 'account', 'domain', 'zonename', 'userdataid', 'userdataname', 'userdataparams', 'userdatadetails', 'userdatapolicy'],
+ details: ['displayname', 'name', 'id', 'state', 'ipaddress', 'ip6address', 'templatename', 'ostypename', 'serviceofferingname', 'isdynamicallyscalable', 'haenable', 'hypervisor', 'boottype', 'bootmode', 'account', 'domain', 'zonename', 'userdataid', 'userdataname', 'userdataparams', 'userdatadetails', 'userdatapolicy', 'hostcontrolstate'],
tabs: [{
component: () => import('@/views/compute/InstanceTab.vue')
}],
@@ -121,6 +121,7 @@
docHelp: 'adminguide/virtual_machines.html#stopping-and-starting-vms',
dataView: true,
show: (record) => { return ['Running'].includes(record.state) },
+ disabled: (record) => { return record.hostcontrolstate === 'Offline' },
args: (record, store) => {
var fields = []
fields.push('forced')
@@ -148,6 +149,7 @@
value: (record) => { return record.id }
}
},
+ disabled: (record) => { return record.hostcontrolstate === 'Offline' },
successMethod: (obj, result) => {
const vm = result.jobresult.virtualmachine || {}
if (result.jobstatus === 1 && vm.password) {
@@ -172,6 +174,7 @@
(['Stopped'].includes(record.state) && ((record.hypervisor !== 'KVM' && record.hypervisor !== 'LXC') ||
(record.hypervisor === 'KVM' && record.pooltype === 'PowerFlex'))))
},
+ disabled: (record) => { return record.hostcontrolstate === 'Offline' && record.hypervisor === 'KVM' },
mapping: {
virtualmachineid: {
value: (record, params) => { return record.id }
@@ -189,6 +192,7 @@
return ((['Running'].includes(record.state) && record.hypervisor !== 'LXC') ||
(['Stopped'].includes(record.state) && !['KVM', 'LXC'].includes(record.hypervisor)))
},
+ disabled: (record) => { return record.hostcontrolstate === 'Offline' && record.hypervisor === 'KVM' },
component: () => import('@/views/compute/CreateSnapshotWizard.vue')
},
{
@@ -262,6 +266,7 @@
dataView: true,
popup: true,
show: (record) => { return ['Running', 'Stopped'].includes(record.state) && !record.isoid },
+ disabled: (record) => { return record.hostcontrolstate === 'Offline' || record.hostcontrolstate === 'Maintenance' },
component: () => import('@/views/compute/AttachIso.vue')
},
{
@@ -278,6 +283,7 @@
return args
},
show: (record) => { return ['Running', 'Stopped'].includes(record.state) && 'isoid' in record && record.isoid },
+ disabled: (record) => { return record.hostcontrolstate === 'Offline' || record.hostcontrolstate === 'Maintenance' },
mapping: {
virtualmachineid: {
value: (record, params) => { return record.id }
@@ -313,6 +319,7 @@
docHelp: 'adminguide/virtual_machines.html#moving-vms-between-hosts-manual-live-migration',
dataView: true,
show: (record, store) => { return ['Running'].includes(record.state) && ['Admin'].includes(store.userInfo.roletype) },
+ disabled: (record) => { return record.hostcontrolstate === 'Offline' },
popup: true,
component: () => import('@/views/compute/MigrateWizard')
},
@@ -324,6 +331,7 @@
docHelp: 'adminguide/virtual_machines.html#moving-vms-between-hosts-manual-live-migration',
dataView: true,
show: (record, store) => { return ['Stopped'].includes(record.state) && ['Admin'].includes(store.userInfo.roletype) },
+ disabled: (record) => { return record.hostcontrolstate === 'Offline' },
component: () => import('@/views/compute/MigrateVMStorage'),
popup: true
},
diff --git a/ui/src/config/section/infra/ilbvms.js b/ui/src/config/section/infra/ilbvms.js
index 2a22922..5adee93 100644
--- a/ui/src/config/section/infra/ilbvms.js
+++ b/ui/src/config/section/infra/ilbvms.js
@@ -22,7 +22,7 @@
permission: ['listInternalLoadBalancerVMs'],
params: { projectid: '-1' },
columns: ['name', 'state', 'publicip', 'guestnetworkname', 'vpcname', 'version', 'hostname', 'account', 'zonename', 'requiresupgrade'],
- details: ['name', 'id', 'version', 'requiresupgrade', 'guestnetworkname', 'vpcname', 'publicip', 'guestipaddress', 'linklocalip', 'serviceofferingname', 'networkdomain', 'isredundantrouter', 'redundantstate', 'hostname', 'account', 'zonename', 'created'],
+ details: ['name', 'id', 'version', 'requiresupgrade', 'guestnetworkname', 'vpcname', 'publicip', 'guestipaddress', 'linklocalip', 'serviceofferingname', 'networkdomain', 'isredundantrouter', 'redundantstate', 'hostname', 'account', 'zonename', 'created', 'hostcontrolstate'],
actions: [
{
api: 'startInternalLoadBalancerVM',
@@ -52,6 +52,7 @@
label: 'label.action.migrate.router',
dataView: true,
show: (record, store) => { return record.state === 'Running' && ['Admin'].includes(store.userInfo.roletype) },
+ disabled: (record) => { return record.hostcontrolstate === 'Offline' },
component: () => import('@/views/compute/MigrateWizard'),
popup: true
},
@@ -61,6 +62,7 @@
label: 'label.action.migrate.systemvm.to.ps',
dataView: true,
show: (record, store) => { return ['Stopped'].includes(record.state) && ['VMware'].includes(record.hypervisor) },
+ disabled: (record) => { return record.hostcontrolstate === 'Offline' },
component: () => import('@/views/compute/MigrateVMStorage'),
popup: true
}
diff --git a/ui/src/config/section/infra/routers.js b/ui/src/config/section/infra/routers.js
index f14cc5e..b9d3218 100644
--- a/ui/src/config/section/infra/routers.js
+++ b/ui/src/config/section/infra/routers.js
@@ -26,7 +26,7 @@
params: { projectid: '-1' },
columns: ['name', 'state', 'publicip', 'guestnetworkname', 'vpcname', 'redundantstate', 'version', 'hostname', 'account', 'zonename', 'requiresupgrade'],
searchFilters: ['name', 'zoneid', 'podid', 'clusterid'],
- details: ['name', 'id', 'version', 'requiresupgrade', 'guestnetworkname', 'vpcname', 'publicip', 'guestipaddress', 'linklocalip', 'serviceofferingname', 'networkdomain', 'isredundantrouter', 'redundantstate', 'hostname', 'account', 'zonename', 'created'],
+ details: ['name', 'id', 'version', 'requiresupgrade', 'guestnetworkname', 'vpcname', 'publicip', 'guestipaddress', 'linklocalip', 'serviceofferingname', 'networkdomain', 'isredundantrouter', 'redundantstate', 'hostname', 'account', 'zonename', 'created', 'hostcontrolstate'],
resourceType: 'VirtualRouter',
tabs: [{
name: 'details',
@@ -127,6 +127,7 @@
message: 'message.migrate.router.confirm',
dataView: true,
show: (record, store) => { return record.state === 'Running' && ['Admin'].includes(store.userInfo.roletype) },
+ disabled: (record) => { return record.hostcontrolstate === 'Offline' },
component: () => import('@/views/compute/MigrateWizard'),
popup: true
},
@@ -136,6 +137,7 @@
label: 'label.action.migrate.systemvm.to.ps',
dataView: true,
show: (record, store) => { return ['Stopped'].includes(record.state) && ['VMware'].includes(record.hypervisor) },
+ disabled: (record) => { return record.hostcontrolstate === 'Offline' },
component: () => import('@/views/compute/MigrateVMStorage'),
popup: true
},
diff --git a/ui/src/config/section/infra/systemVms.js b/ui/src/config/section/infra/systemVms.js
index 2309fe3..34f8533 100644
--- a/ui/src/config/section/infra/systemVms.js
+++ b/ui/src/config/section/infra/systemVms.js
@@ -24,7 +24,7 @@
docHelp: 'adminguide/systemvm.html',
permission: ['listSystemVms'],
columns: ['name', 'state', 'agentstate', 'systemvmtype', 'publicip', 'privateip', 'linklocalip', 'hostname', 'zonename'],
- details: ['name', 'id', 'agentstate', 'systemvmtype', 'publicip', 'privateip', 'linklocalip', 'gateway', 'hostname', 'zonename', 'created', 'activeviewersessions', 'isdynamicallyscalable'],
+ details: ['name', 'id', 'agentstate', 'systemvmtype', 'publicip', 'privateip', 'linklocalip', 'gateway', 'hostname', 'zonename', 'created', 'activeviewersessions', 'isdynamicallyscalable', 'hostcontrolstate'],
resourceType: 'SystemVm',
tabs: [
{
@@ -100,6 +100,7 @@
message: 'message.migrate.systemvm.confirm',
dataView: true,
show: (record, store) => { return record.state === 'Running' && ['Admin'].includes(store.userInfo.roletype) },
+ disabled: (record) => { return record.hostcontrolstate === 'Offline' },
component: () => import('@/views/compute/MigrateWizard'),
popup: true
},
@@ -109,6 +110,7 @@
label: 'label.action.migrate.systemvm.to.ps',
dataView: true,
show: (record, store) => { return ['Stopped'].includes(record.state) && ['VMware'].includes(record.hypervisor) },
+ disabled: (record) => { return record.hostcontrolstate === 'Offline' },
component: () => import('@/views/compute/MigrateVMStorage'),
popup: true
},