summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSean Chen <xuchenx@gmail.com>2013-01-10 15:31:14 -0800
committerSean Chen <xuchenx@gmail.com>2013-01-25 15:39:35 -0800
commitf3055e77efbf1e9de23996fd6d3aea3135796f3b (patch)
tree81a0369bc63ca8e9896519d68a222a052339373e
parentfff46247c058e69fcdf25e6845c34509f4688c30 (diff)
downloadnova-f3055e77efbf1e9de23996fd6d3aea3135796f3b.tar.gz
nova-f3055e77efbf1e9de23996fd6d3aea3135796f3b.tar.xz
nova-f3055e77efbf1e9de23996fd6d3aea3135796f3b.zip
VMware Compute Driver Host Ops
blueprint vmware-compute-driver Host power action and maintenance mode Get host stats and update host status Change-Id: I88eff5482ad50a8ffa262dd015d2395fe2095fba
-rw-r--r--nova/tests/test_vmwareapi.py45
-rw-r--r--nova/virt/vmwareapi/driver.py73
-rw-r--r--nova/virt/vmwareapi/fake.py47
-rw-r--r--nova/virt/vmwareapi/host.py142
-rw-r--r--nova/virt/vmwareapi/vm_util.py29
5 files changed, 329 insertions, 7 deletions
diff --git a/nova/tests/test_vmwareapi.py b/nova/tests/test_vmwareapi.py
index 8db4c80ad..f99f1abe0 100644
--- a/nova/tests/test_vmwareapi.py
+++ b/nova/tests/test_vmwareapi.py
@@ -282,3 +282,48 @@ class VMwareAPIVMTestCase(test.TestCase):
def test_get_console_output(self):
pass
+
+
+class VMwareAPIHostTestCase(test.TestCase):
+ """Unit tests for Vmware API host calls."""
+
+ def setUp(self):
+ super(VMwareAPIHostTestCase, self).setUp()
+ self.flags(vmwareapi_host_ip='test_url',
+ vmwareapi_host_username='test_username',
+ vmwareapi_host_password='test_pass')
+ vmwareapi_fake.reset()
+ stubs.set_stubs(self.stubs)
+ self.conn = driver.VMwareESXDriver(False)
+
+ def tearDown(self):
+ super(VMwareAPIHostTestCase, self).tearDown()
+ vmwareapi_fake.cleanup()
+
+ def test_host_state(self):
+ stats = self.conn.get_host_stats()
+ self.assertEquals(stats['vcpus'], 16)
+ self.assertEquals(stats['disk_total'], 1024)
+ self.assertEquals(stats['disk_available'], 500)
+ self.assertEquals(stats['disk_used'], 1024 - 500)
+ self.assertEquals(stats['host_memory_total'], 1024)
+ self.assertEquals(stats['host_memory_free'], 1024 - 500)
+
+ def _test_host_action(self, method, action, expected=None):
+ result = method('host', action)
+ self.assertEqual(result, expected)
+
+ def test_host_reboot(self):
+ self._test_host_action(self.conn.host_power_action, 'reboot')
+
+ def test_host_shutdown(self):
+ self._test_host_action(self.conn.host_power_action, 'shutdown')
+
+ def test_host_startup(self):
+ self._test_host_action(self.conn.host_power_action, 'startup')
+
+ def test_host_maintenance_on(self):
+ self._test_host_action(self.conn.host_maintenance_mode, True)
+
+ def test_host_maintenance_off(self):
+ self._test_host_action(self.conn.host_maintenance_mode, False)
diff --git a/nova/virt/vmwareapi/driver.py b/nova/virt/vmwareapi/driver.py
index d5a7e5875..855f55345 100644
--- a/nova/virt/vmwareapi/driver.py
+++ b/nova/virt/vmwareapi/driver.py
@@ -38,10 +38,12 @@ from eventlet import event
from nova import exception
from nova.openstack.common import cfg
+from nova.openstack.common import jsonutils
from nova.openstack.common import log as logging
from nova import utils
from nova.virt import driver
from nova.virt.vmwareapi import error_util
+from nova.virt.vmwareapi import host
from nova.virt.vmwareapi import vim
from nova.virt.vmwareapi import vim_util
from nova.virt.vmwareapi import vmops
@@ -100,20 +102,30 @@ class VMwareESXDriver(driver.ComputeDriver):
def __init__(self, virtapi, read_only=False, scheme="https"):
super(VMwareESXDriver, self).__init__(virtapi)
- host_ip = CONF.vmwareapi_host_ip
+ self._host_ip = CONF.vmwareapi_host_ip
host_username = CONF.vmwareapi_host_username
host_password = CONF.vmwareapi_host_password
api_retry_count = CONF.vmwareapi_api_retry_count
- if not host_ip or host_username is None or host_password is None:
+ if not self._host_ip or host_username is None or host_password is None:
raise Exception(_("Must specify vmwareapi_host_ip,"
"vmwareapi_host_username "
"and vmwareapi_host_password to use"
"compute_driver=vmwareapi.VMwareESXDriver"))
- self._session = VMwareAPISession(host_ip, host_username, host_password,
- api_retry_count, scheme=scheme)
+ self._session = VMwareAPISession(self._host_ip,
+ host_username, host_password,
+ api_retry_count, scheme=scheme)
self._volumeops = volumeops.VMwareVolumeOps(self._session)
self._vmops = vmops.VMwareVMOps(self._session)
+ self._host = host.Host(self._session)
+ self._host_state = None
+
+ @property
+ def host_state(self):
+ if not self._host_state:
+ self._host_state = host.HostState(self._session,
+ self._host_ip)
+ return self._host_state
def init_host(self, host):
"""Do the initialization that needs to be done."""
@@ -178,6 +190,10 @@ class VMwareESXDriver(driver.ComputeDriver):
"""Return volume connector information."""
return self._volumeops.get_volume_connector(instance)
+ def get_host_ip_addr(self):
+ """Retrieves the IP address of the ESX host."""
+ return self._host_ip
+
def attach_volume(self, connection_info, instance, mountpoint):
"""Attach volume storage to VM instance."""
return self._volumeops.attach_volume(connection_info,
@@ -197,8 +213,53 @@ class VMwareESXDriver(driver.ComputeDriver):
'password': CONF.vmwareapi_host_password}
def get_available_resource(self, nodename):
- """This method is supported only by libvirt."""
- return
+ """Retrieve resource info.
+
+ This method is called when nova-compute launches, and
+ as part of a periodic task.
+
+ :returns: dictionary describing resources
+
+ """
+ host_stats = self.get_host_stats(refresh=True)
+
+ # Updating host information
+ dic = {'vcpus': host_stats["vcpus"],
+ 'memory_mb': host_stats['host_memory_total'],
+ 'local_gb': host_stats['disk_total'],
+ 'vcpus_used': 0,
+ 'memory_mb_used': host_stats['host_memory_total'] -
+ host_stats['host_memory_free'],
+ 'local_gb_used': host_stats['disk_used'],
+ 'hypervisor_type': host_stats['hypervisor_type'],
+ 'hypervisor_version': host_stats['hypervisor_version'],
+ 'hypervisor_hostname': host_stats['hypervisor_hostname'],
+ 'cpu_info': jsonutils.dumps(host_stats['cpu_info'])}
+
+ return dic
+
+ def update_host_status(self):
+ """Update the status info of the host, and return those values
+ to the calling program."""
+ return self.host_state.update_status()
+
+ def get_host_stats(self, refresh=False):
+ """Return the current state of the host. If 'refresh' is
+ True, run the update first."""
+ return self.host_state.get_host_stats(refresh=refresh)
+
+ def host_power_action(self, host, action):
+ """Reboots, shuts down or powers up the host."""
+ return self._host.host_power_action(host, action)
+
+ def host_maintenance_mode(self, host, mode):
+ """Start/Stop host maintenance window. On start, it triggers
+ guest VMs evacuation."""
+ return self._host.host_maintenance_mode(host, mode)
+
+ def set_host_enabled(self, host, enabled):
+ """Sets the specified host's ability to accept new instances."""
+ return self._host.set_host_enabled(host, enabled)
def inject_network_info(self, instance, network_info):
"""inject network info for specified instance."""
diff --git a/nova/virt/vmwareapi/fake.py b/nova/virt/vmwareapi/fake.py
index 7fb014075..692e5f253 100644
--- a/nova/virt/vmwareapi/fake.py
+++ b/nova/virt/vmwareapi/fake.py
@@ -255,6 +255,8 @@ class Datastore(ManagedObject):
super(Datastore, self).__init__("Datastore")
self.set("summary.type", "VMFS")
self.set("summary.name", "fake-ds")
+ self.set("summary.capacity", 1024 * 1024 * 1024)
+ self.set("summary.freeSpace", 500 * 1024 * 1024)
class HostNetworkSystem(ManagedObject):
@@ -285,6 +287,29 @@ class HostSystem(ManagedObject):
host_net_sys = _db_content["HostNetworkSystem"][host_net_key].obj
self.set("configManager.networkSystem", host_net_sys)
+ summary = DataObject()
+ hardware = DataObject()
+ hardware.numCpuCores = 8
+ hardware.numCpuPkgs = 2
+ hardware.numCpuThreads = 16
+ hardware.vendor = "Intel"
+ hardware.cpuModel = "Intel(R) Xeon(R)"
+ hardware.memorySize = 1024 * 1024 * 1024
+ summary.hardware = hardware
+
+ quickstats = DataObject()
+ quickstats.overallMemoryUsage = 500
+ summary.quickStats = quickstats
+
+ product = DataObject()
+ product.name = "VMware ESXi"
+ product.version = "5.0.0"
+ config = DataObject()
+ config.product = product
+ summary.config = config
+
+ self.set("summary", summary)
+
if _db_content.get("Network", None) is None:
create_network()
net_ref = _db_content["Network"][_db_content["Network"].keys()[0]].obj
@@ -599,6 +624,11 @@ class FakeVim(object):
"""Fakes a return."""
return
+ def _just_return_task(self, method):
+ """Fakes a task return."""
+ task_mdo = create_task(method, "success")
+ return task_mdo.obj
+
def _unregister_vm(self, method, *args, **kwargs):
"""Unregisters a VM from the Host System."""
vm_ref = args[0]
@@ -627,7 +657,7 @@ class FakeVim(object):
def _set_power_state(self, method, vm_ref, pwr_state="poweredOn"):
"""Sets power state for the VM."""
if _db_content.get("VirtualMachine", None) is None:
- raise exception.NotFound(_(" No Virtual Machine has been "
+ raise exception.NotFound(_("No Virtual Machine has been "
"registered yet"))
if vm_ref not in _db_content.get("VirtualMachine"):
raise exception.NotFound(_("Virtual Machine with ref %s is not "
@@ -722,6 +752,9 @@ class FakeVim(object):
elif attr_name == "DeleteVirtualDisk_Task":
return lambda *args, **kwargs: self._delete_disk(attr_name,
*args, **kwargs)
+ elif attr_name == "Destroy_Task":
+ return lambda *args, **kwargs: self._unregister_vm(attr_name,
+ *args, **kwargs)
elif attr_name == "UnregisterVM":
return lambda *args, **kwargs: self._unregister_vm(attr_name,
*args, **kwargs)
@@ -739,3 +772,15 @@ class FakeVim(object):
elif attr_name == "AddPortGroup":
return lambda *args, **kwargs: self._add_port_group(attr_name,
*args, **kwargs)
+ elif attr_name == "RebootHost_Task":
+ return lambda *args, **kwargs: self._just_return_task(attr_name)
+ elif attr_name == "ShutdownHost_Task":
+ return lambda *args, **kwargs: self._just_return_task(attr_name)
+ elif attr_name == "PowerDownHostToStandBy_Task":
+ return lambda *args, **kwargs: self._just_return_task(attr_name)
+ elif attr_name == "PowerUpHostFromStandBy_Task":
+ return lambda *args, **kwargs: self._just_return_task(attr_name)
+ elif attr_name == "EnterMaintenanceMode_Task":
+ return lambda *args, **kwargs: self._just_return_task(attr_name)
+ elif attr_name == "ExitMaintenanceMode_Task":
+ return lambda *args, **kwargs: self._just_return_task(attr_name)
diff --git a/nova/virt/vmwareapi/host.py b/nova/virt/vmwareapi/host.py
new file mode 100644
index 000000000..424dac707
--- /dev/null
+++ b/nova/virt/vmwareapi/host.py
@@ -0,0 +1,142 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2012 VMware, Inc.
+#
+# Licensed 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.
+
+"""
+Management class for host-related functions (start, reboot, etc).
+"""
+
+import json
+
+from nova import exception
+from nova.openstack.common import log as logging
+from nova.virt.vmwareapi import vim_util
+from nova.virt.vmwareapi import vm_util
+
+LOG = logging.getLogger(__name__)
+
+
+class Host(object):
+ """
+ Implements host related operations.
+ """
+ def __init__(self, session):
+ self._session = session
+
+ def host_power_action(self, host, action):
+ """Reboots or shuts down the host."""
+ host_mor = self._session._call_method(vim_util, "get_objects",
+ "HostSystem")[0].obj
+ LOG.debug(_("%(action)s %(host)s") % locals())
+ if action == "reboot":
+ host_task = self._session._call_method(
+ self._session._get_vim(),
+ "RebootHost_Task", host_mor,
+ force=False)
+ elif action == "shutdown":
+ host_task = self._session._call_method(
+ self._session._get_vim(),
+ "ShutdownHost_Task", host_mor,
+ force=False)
+ elif action == "startup":
+ host_task = self._session._call_method(
+ self._session._get_vim(),
+ "PowerUpHostFromStandBy_Task", host_mor,
+ timeoutSec=60)
+ self._session._wait_for_task(host, host_task)
+
+ def host_maintenance_mode(self, host, mode):
+ """Start/Stop host maintenance window. On start, it triggers
+ guest VMs evacuation."""
+ host_mor = self._session._call_method(vim_util, "get_objects",
+ "HostSystem")[0].obj
+ LOG.debug(_("Set maintenance mod on %(host)s to %(mode)s") % locals())
+ if mode:
+ host_task = self._session._call_method(
+ self._session._get_vim(),
+ "EnterMaintenanceMode_Task",
+ host_mor, timeout=0,
+ evacuatePoweredOffVms=True)
+ else:
+ host_task = self._session._call_method(
+ self._session._get_vim(),
+ "ExitMaintenanceMode_Task",
+ host_mor, timeout=0)
+ self._session._wait_for_task(host, host_task)
+
+ def set_host_enabled(self, _host, enabled):
+ """Sets the specified host's ability to accept new instances."""
+ pass
+
+
+class HostState(object):
+ """Manages information about the ESX host this compute
+ node is running on.
+ """
+ def __init__(self, session, host_name):
+ super(HostState, self).__init__()
+ self._session = session
+ self._host_name = host_name
+ self._stats = {}
+ self.update_status()
+
+ def get_host_stats(self, refresh=False):
+ """Return the current state of the host. If 'refresh' is
+ True, run the update first.
+ """
+ if refresh:
+ self.update_status()
+ return self._stats
+
+ def update_status(self):
+ """Update the current state of the host.
+ """
+ host_mor = self._session._call_method(vim_util, "get_objects",
+ "HostSystem")[0].obj
+ summary = self._session._call_method(vim_util,
+ "get_dynamic_property",
+ host_mor,
+ "HostSystem",
+ "summary")
+
+ if summary is None:
+ return
+
+ try:
+ ds = vm_util.get_datastore_ref_and_name(self._session)
+ except exception.DatastoreNotFound:
+ ds = (None, None, 0, 0)
+
+ data = {}
+ data["vcpus"] = summary.hardware.numCpuThreads
+ data["cpu_info"] = \
+ {"vendor": summary.hardware.vendor,
+ "model": summary.hardware.cpuModel,
+ "topology": {"cores": summary.hardware.numCpuCores,
+ "sockets": summary.hardware.numCpuPkgs,
+ "threads": summary.hardware.numCpuThreads}
+ }
+ data["disk_total"] = ds[2] / (1024 * 1024)
+ data["disk_available"] = ds[3] / (1024 * 1024)
+ data["disk_used"] = data["disk_total"] - data["disk_available"]
+ data["host_memory_total"] = summary.hardware.memorySize / (1024 * 1024)
+ data["host_memory_free"] = data["host_memory_total"] - \
+ summary.quickStats.overallMemoryUsage
+ data["hypervisor_type"] = summary.config.product.name
+ data["hypervisor_version"] = summary.config.product.version
+ data["hypervisor_hostname"] = self._host_name
+
+ self._stats = data
+ return data
diff --git a/nova/virt/vmwareapi/vm_util.py b/nova/virt/vmwareapi/vm_util.py
index 381c47193..c80754327 100644
--- a/nova/virt/vmwareapi/vm_util.py
+++ b/nova/virt/vmwareapi/vm_util.py
@@ -20,6 +20,7 @@ The VMware API VM utility module to build SOAP object specs.
"""
import copy
+from nova import exception
from nova.virt.vmwareapi import vim_util
@@ -431,3 +432,31 @@ def get_vm_ref_from_name(session, vm_name):
if vm.propSet[0].val == vm_name:
return vm.obj
return None
+
+
+def get_datastore_ref_and_name(session):
+ """Get the datastore list and choose the first local storage."""
+ data_stores = session._call_method(vim_util, "get_objects",
+ "Datastore", ["summary.type", "summary.name",
+ "summary.capacity", "summary.freeSpace"])
+ for elem in data_stores:
+ ds_name = None
+ ds_type = None
+ ds_cap = None
+ ds_free = None
+ for prop in elem.propSet:
+ if prop.name == "summary.type":
+ ds_type = prop.val
+ elif prop.name == "summary.name":
+ ds_name = prop.val
+ elif prop.name == "summary.capacity":
+ ds_cap = prop.val
+ elif prop.name == "summary.freeSpace":
+ ds_free = prop.val
+ # Local storage identifier
+ if ds_type == "VMFS" or ds_type == "NFS":
+ data_store_name = ds_name
+ return elem.obj, data_store_name, ds_cap, ds_free
+
+ if data_store_name is None:
+ raise exception.DatastoreNotFound()