diff options
| author | Justin Santa Barbara <justin@fathomdb.com> | 2011-03-14 14:17:58 -0700 |
|---|---|---|
| committer | Justin Santa Barbara <justin@fathomdb.com> | 2011-03-14 14:17:58 -0700 |
| commit | c94ec9a5bab6c07b402b68e2f4ff081247a27cda (patch) | |
| tree | 437e4e1cbb128812af04a6aa9cb2ab50661bf89a | |
| parent | 119bbe04f3c1de06a8c40502c314f13c89561564 (diff) | |
Initial implementation of refresh instance states
| -rw-r--r-- | nova/compute/driver.py | 38 | ||||
| -rw-r--r-- | nova/compute/manager.py | 56 | ||||
| -rw-r--r-- | nova/compute/power_state.py | 16 | ||||
| -rw-r--r-- | nova/utils.py | 9 | ||||
| -rw-r--r-- | nova/virt/connection.py | 4 | ||||
| -rw-r--r-- | nova/virt/fake.py | 36 | ||||
| -rw-r--r-- | nova/virt/hyperv.py | 4 | ||||
| -rw-r--r-- | nova/virt/libvirt_conn.py | 24 | ||||
| -rw-r--r-- | nova/virt/xenapi/vmops.py | 19 | ||||
| -rw-r--r-- | nova/virt/xenapi_conn.py | 7 |
10 files changed, 194 insertions, 19 deletions
diff --git a/nova/compute/driver.py b/nova/compute/driver.py new file mode 100644 index 000000000..bda82c60a --- /dev/null +++ b/nova/compute/driver.py @@ -0,0 +1,38 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Justin Santa Barbara +# All Rights Reserved. +# +# 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. + +""" +Driver base-classes: + + (Beginning of) the contract that compute drivers must follow, and shared + types that support that contract +""" + +from nova.compute import power_state + + +class InstanceInfo(object): + def __init__(self, name, state): + self.name = name + assert state in power_state.valid_states() + self.state = state + + +class ComputeDriver(object): + def list_instances_detail(self): + """Return a list of InstanceInfo for all registered VMs""" + raise NotImplementedError() diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 0cab10fc3..057371d40 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -2,6 +2,7 @@ # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. +# Copyright 2011 Justin Santa Barbara # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -51,6 +52,7 @@ from nova import manager from nova import rpc from nova import utils from nova.compute import power_state +from nova.compute import driver FLAGS = flags.FLAGS flags.DEFINE_string('instances_path', '$state_path/instances', @@ -115,7 +117,9 @@ class ComputeManager(manager.Manager): # and redocument the module docstring if not compute_driver: compute_driver = FLAGS.compute_driver - self.driver = utils.import_object(compute_driver) + self.driver = utils.check_instance(utils.import_object( + compute_driver), + driver.ComputeDriver) self.network_manager = utils.import_object(FLAGS.network_manager) self.volume_manager = utils.import_object(FLAGS.volume_manager) super(ComputeManager, self).__init__(*args, **kwargs) @@ -974,3 +978,53 @@ class ComputeManager(manager.Manager): for volume in instance_ref['volumes']: self.db.volume_update(ctxt, volume['id'], {'status': 'in-use'}) + + def periodic_tasks(self, context=None): + """Tasks to be run at a periodic interval.""" + super(ComputeManager, self).periodic_tasks(context) + try: + self._poll_instance_states(context) + except Exception as ex: + LOG.warning(_("Error during instance poll: %s"), + unicode(ex)) + + def _poll_instance_states(self, context): + vm_instances = self.driver.list_instances_detail(context) + vm_instances = dict((vm.name, vm) for vm in vm_instances) + + # Keep a list of VMs not in the DB, cross them off as we find them + vms_not_found_in_db = [vm.name for vm in vm_instances] + + db_instances = self.db.instance_get_all_by_host(context, self.host) + for db_instance in db_instances: + name = db_instance['name'] + vm_instance = vm_instances.get(name) + if vm_instance is None: + LOG.info(_("Found instance '%(name)' in DB but no VM. " + "Shutting off.") % locals()) + vm_state = power_state.SHUTOFF + else: + vm_state = vm_instance.state + vms_not_found_in_db.remove(name) + + db_state = db_instance['state'] + if vm_state != db_state: + LOG.info(_("DB/VM state mismatch. Changing state from " + "%(db_state) to %(vm_state)") % locals()) + self.db.instance_set_state(context, + db_instance['id'], + vm_state) + + if vm_state == power_state.SHUTOFF: + # TODO(soren): This is what the compute manager does when you + # terminate an instance. At some point I figure we'll have a + # "terminated" state and some sort of cleanup job that runs + # occasionally, cleaning them out. + self.db.instance_destroy(context, db_instance['id']) + + # Are there VMs not in the DB? + for vm_not_found_in_db in vms_not_found_in_db: + name = vm_not_found_in_db + #TODO(justinsb): What to do here? Adopt it? Shut it down? + LOG.warning(_("Found VM not in DB: %(name). Ignoring") + % locals()) diff --git a/nova/compute/power_state.py b/nova/compute/power_state.py index adfc2dff0..145362f97 100644 --- a/nova/compute/power_state.py +++ b/nova/compute/power_state.py @@ -2,6 +2,7 @@ # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. +# Copyright 2011 Justin Santa Barbara # All Rights Reserved. # Copyright (c) 2010 Citrix Systems, Inc. # @@ -19,6 +20,7 @@ """The various power states that a VM can be in.""" +#NOTE(justinsb): These are the virDomainState values from libvirt NOSTATE = 0x00 RUNNING = 0x01 BLOCKED = 0x02 @@ -29,9 +31,8 @@ CRASHED = 0x06 SUSPENDED = 0x07 FAILED = 0x08 - -def name(code): - d = { +#TODO(justinsb): Power state really needs to be a proper class... +_STATE_MAP = { NOSTATE: 'pending', RUNNING: 'running', BLOCKED: 'blocked', @@ -41,4 +42,11 @@ def name(code): CRASHED: 'crashed', SUSPENDED: 'suspended', FAILED: 'failed to spawn'} - return d[code] + + +def name(code): + return _STATE_MAP[code] + + +def valid_states(): + return _STATE_MAP.values() diff --git a/nova/utils.py b/nova/utils.py index 87e726394..e93f489be 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -585,3 +585,12 @@ def get_from_path(items, path): return results else: return get_from_path(results, remainder) + + +def check_instance(obj, cls): + """Checks that obj is of type cls, and lets PyLint infer types""" + if isinstance(obj, cls): + return obj + raise Exception(_("Expected object of type: %s") % (str(cls))) + #TODO(justinsb): Can we make this better?? + return cls() # Ugly PyLint hack diff --git a/nova/virt/connection.py b/nova/virt/connection.py index 13181b730..d585b6c21 100644 --- a/nova/virt/connection.py +++ b/nova/virt/connection.py @@ -23,6 +23,8 @@ import sys from nova import flags from nova import log as logging +from nova import utils +from nova.compute import driver from nova.virt import fake from nova.virt import libvirt_conn from nova.virt import xenapi_conn @@ -72,4 +74,4 @@ def get_connection(read_only=False): if conn is None: LOG.error(_('Failed to open connection to the hypervisor')) sys.exit(1) - return conn + return utils.check_instance(conn, driver.ComputeDriver) diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 3a06284a1..18cca3f5e 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -26,6 +26,8 @@ semantics of real hypervisor connections. """ from nova import exception +from nova import utils +from nova.compute import driver from nova.compute import power_state @@ -34,7 +36,14 @@ def get_connection(_): return FakeConnection.instance() -class FakeConnection(object): +class FakeInstance(object): + + def __init__(self, name, state): + self.name = name + self.state = state + + +class FakeConnection(driver.ComputeDriver): """ The interface to this class talks in terms of 'instances' (Amazon EC2 and internal Nova terminology), by which we mean 'running virtual machine' @@ -90,6 +99,17 @@ class FakeConnection(object): """ return self.instances.keys() + def _map_to_instance_info(self, instance): + instance = utils.check_instance(instance, FakeInstance) + info = driver.InstanceInfo(instance.name, instance.state) + return info + + def list_instances_detail(self): + info_list = [] + for instance in self.instances: + info_list.append(self._map_to_instance_info(instance)) + return info_list + def spawn(self, instance): """ Create a new instance/VM/domain on the virtualization platform. @@ -109,9 +129,10 @@ class FakeConnection(object): that it was before this call began. """ - fake_instance = FakeInstance() - self.instances[instance.name] = fake_instance - fake_instance._state = power_state.RUNNING + name = instance.name + state = power_state.RUNNING + fake_instance = FakeInstance(name, state) + self.instances[name] = fake_instance def snapshot(self, instance, name): """ @@ -270,7 +291,7 @@ class FakeConnection(object): raise exception.NotFound(_("Instance %s Not Found") % instance_name) i = self.instances[instance_name] - return {'state': i._state, + return {'state': i.state, 'max_mem': 0, 'mem': 0, 'num_cpu': 2, @@ -428,8 +449,3 @@ class FakeConnection(object): """This method is supported only by libvirt.""" raise NotImplementedError('This method is supported only by libvirt.') - -class FakeInstance(object): - - def __init__(self): - self._state = power_state.NOSTATE diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 29d18dac5..aea7413c6 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -67,6 +67,7 @@ from nova import exception from nova import flags from nova import log as logging from nova.auth import manager +from nova.compute import driver from nova.compute import power_state from nova.virt import images @@ -108,8 +109,9 @@ def get_connection(_): return HyperVConnection() -class HyperVConnection(object): +class HyperVConnection(driver.ComputeDriver): def __init__(self): + super(HyperVConnection, self).__init__() self._conn = wmi.WMI(moniker='//./root/virtualization') self._cim_conn = wmi.WMI(moniker='//./root/cimv2') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 0b306c950..e95bcac39 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -60,6 +60,7 @@ from nova import log as logging #from nova import test from nova import utils from nova.auth import manager +from nova.compute import driver from nova.compute import instance_types from nova.compute import power_state from nova.virt import disk @@ -154,9 +155,10 @@ def _get_ip_version(cidr): return int(net.version()) -class LibvirtConnection(object): +class LibvirtConnection(driver.ComputeDriver): def __init__(self, read_only): + super(LibvirtConnection, self).__init__() self.libvirt_uri = self.get_uri() self.libvirt_xml = open(FLAGS.libvirt_xml_template).read() @@ -235,6 +237,26 @@ class LibvirtConnection(object): return [self._conn.lookupByID(x).name() for x in self._conn.listDomainsID()] + def _map_to_instance_info(self, domain): + # .info() returns a list of: + #state: one of the state values (virDomainState) + #maxMemory: the maximum memory used by the domain + #memory: the current amount of memory used by the domain + #nbVirtCPU: the number of virtual CPU + #cpuTime: the time used by the domain in nanoseconds + (state, _max_mem, _mem, _num_cpu, _cpu_time) = domain.info() + name = domain.name() + + return driver.InstanceInfo(name, state) + + def list_instances_detail(self): + infos = [] + for domain_id in self._conn.listDomainsID(): + domain = self._conn.lookupById(domain_id) + info = self._map_to_instance_info(domain) + infos.append(info) + return infos + def destroy(self, instance, cleanup=True): try: virt_dom = self._conn.lookupByName(instance['name']) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index fcb290d03..2fce93e38 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -34,6 +34,7 @@ from nova import exception from nova import utils from nova.auth.manager import AuthManager +from nova.compute import driver from nova.compute import power_state from nova.virt.xenapi.network_utils import NetworkHelper from nova.virt.xenapi.vm_utils import VMHelper @@ -55,6 +56,8 @@ class VMOps(object): def list_instances(self): """List VM instances""" + # TODO(justinsb): Should we just always use the details method? + # Seems to be the same number of API calls.. vm_refs = [] for vm_ref in self._session.get_xenapi().VM.get_all(): vm_rec = self._session.get_xenapi().VM.get_record(vm_ref) @@ -62,6 +65,22 @@ class VMOps(object): vm_refs.append(vm_rec["name_label"]) return vm_refs + def list_instances_detail(self): + """List VM instances, returning InstanceInfo objects""" + instance_infos = [] + for vm_ref in self._session.get_xenapi().VM.get_all(): + vm_rec = self._session.get_xenapi().VM.get_record(vm_ref) + if not vm_rec["is_a_template"] and not vm_rec["is_control_domain"]: + name = vm_rec["name_label"] + + #TODO(justinsb): Yuk... + openstack_format = VMHelper.compile_info(vm_rec) + state = openstack_format['state'] + + instance_info = driver.InstanceInfo(name, state) + instance_infos.append(instance_info) + return instance_infos + def _start(self, instance, vm_ref=None): """Power on a VM instance""" if not vm_ref: diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index da42a83b6..9390db0bb 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -69,6 +69,7 @@ from nova import db from nova import utils from nova import flags from nova import log as logging +from nova.compute import driver from nova.virt.xenapi.vmops import VMOps from nova.virt.xenapi.volumeops import VolumeOps @@ -141,10 +142,11 @@ def get_connection(_): return XenAPIConnection(url, username, password) -class XenAPIConnection(object): +class XenAPIConnection(driver.ComputeDriver): """A connection to XenServer or Xen Cloud Platform""" def __init__(self, url, user, pw): + super(XenAPIConnection, self).__init__() session = XenAPISession(url, user, pw) self._vmops = VMOps(session) self._volumeops = VolumeOps(session) @@ -160,6 +162,9 @@ class XenAPIConnection(object): """List VM instances""" return self._vmops.list_instances() + def list_instances_detail(self): + return self._vmops.list_instances_detail() + def spawn(self, instance): """Create VM instance""" self._vmops.spawn(instance) |
