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
     },