diff options
-rw-r--r-- | nova/tests/test_vmwareapi.py | 45 | ||||
-rw-r--r-- | nova/virt/vmwareapi/driver.py | 73 | ||||
-rw-r--r-- | nova/virt/vmwareapi/fake.py | 47 | ||||
-rw-r--r-- | nova/virt/vmwareapi/host.py | 142 | ||||
-rw-r--r-- | nova/virt/vmwareapi/vm_util.py | 29 |
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() |