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
This commit is contained in:
@@ -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)
|
||||
|
@@ -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."""
|
||||
|
@@ -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)
|
||||
|
142
nova/virt/vmwareapi/host.py
Normal file
142
nova/virt/vmwareapi/host.py
Normal file
@@ -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
|
@@ -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()
|
||||
|
Reference in New Issue
Block a user