diff options
| author | Vishvananda Ishaya <vishvananda@yahoo.com> | 2010-10-25 23:08:57 -0700 |
|---|---|---|
| committer | Vishvananda Ishaya <vishvananda@yahoo.com> | 2010-10-25 23:08:57 -0700 |
| commit | 23ab6ceedd7d552faf2b97c44aadeccc45c9c333 (patch) | |
| tree | ea1e257180cb9abe31f993e4807150d9b6c80388 /nova | |
| parent | 627a968e79ed21d970225e5ece332d9100abe022 (diff) | |
| parent | f0d79d7d602a31fff03d8d934203128a2cd8940d (diff) | |
| download | nova-23ab6ceedd7d552faf2b97c44aadeccc45c9c333.tar.gz nova-23ab6ceedd7d552faf2b97c44aadeccc45c9c333.tar.xz nova-23ab6ceedd7d552faf2b97c44aadeccc45c9c333.zip | |
merged gundlach's excision
Diffstat (limited to 'nova')
| -rw-r--r-- | nova/api/cloud.py | 20 | ||||
| -rw-r--r-- | nova/api/ec2/cloud.py | 17 | ||||
| -rw-r--r-- | nova/compute/manager.py | 53 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/api.py | 6 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/models.py | 25 | ||||
| -rw-r--r-- | nova/network/manager.py | 2 | ||||
| -rw-r--r-- | nova/test.py | 156 | ||||
| -rw-r--r-- | nova/tests/api_unittest.py | 4 | ||||
| -rw-r--r-- | nova/tests/virt_unittest.py | 4 | ||||
| -rw-r--r-- | nova/utils.py | 10 | ||||
| -rw-r--r-- | nova/virt/fake.py | 20 | ||||
| -rw-r--r-- | nova/virt/libvirt.rescue.qemu.xml.template | 37 | ||||
| -rw-r--r-- | nova/virt/libvirt.rescue.uml.xml.template | 26 | ||||
| -rw-r--r-- | nova/virt/libvirt.rescue.xen.xml.template | 34 | ||||
| -rw-r--r-- | nova/virt/libvirt_conn.py | 114 |
15 files changed, 300 insertions, 228 deletions
diff --git a/nova/api/cloud.py b/nova/api/cloud.py index aa84075dc..b8f15019f 100644 --- a/nova/api/cloud.py +++ b/nova/api/cloud.py @@ -36,3 +36,23 @@ def reboot(instance_id, context=None): db.queue_get_for(context, FLAGS.compute_topic, host), {"method": "reboot_instance", "args": {"instance_id": instance_ref['id']}}) + + +def rescue(instance_id, context): + """Rescue the given instance.""" + instance_ref = db.instance_get_by_internal_id(context, instance_id) + host = instance_ref['host'] + rpc.cast(context, + db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "rescue_instance", + "args": {"instance_id": instance_ref['id']}}) + + +def unrescue(instance_id, context): + """Unrescue the given instance.""" + instance_ref = db.instance_get_by_internal_id(context, instance_id) + host = instance_ref['host'] + rpc.cast(context, + db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "unrescue_instance", + "args": {"instance_id": instance_ref['id']}}) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 07229dd73..dd5fe4065 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -936,8 +936,21 @@ class CloudController(object): def reboot_instances(self, context, instance_id, **kwargs): """instance_id is a list of instance ids""" - for id_str in instance_id: - cloud.reboot(id_str, context=context) + for ec2_id in instance_id: + internal_id = ec2_id_to_internal_id(ec2_id) + cloud.reboot(internal_id, context=context) + return True + + def rescue_instance(self, context, instance_id, **kwargs): + """This is an extension to the normal ec2_api""" + internal_id = ec2_id_to_internal_id(instance_id) + cloud.rescue(internal_id, context=context) + return True + + def unrescue_instance(self, context, instance_id, **kwargs): + """This is an extension to the normal ec2_api""" + internal_id = ec2_id_to_internal_id(instance_id) + cloud.unrescue(internal_id, context=context) return True def update_instance(self, context, ec2_id, **kwargs): diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 523bb8893..574feec7c 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -20,10 +20,8 @@ Handles all code relating to instances (guest vms) """ -import base64 import datetime import logging -import os from twisted.internet import defer @@ -59,7 +57,11 @@ class ComputeManager(manager.Manager): """Update the state of an instance from the driver info""" # FIXME(ja): include other fields from state? instance_ref = self.db.instance_get(context, instance_id) - state = self.driver.get_info(instance_ref.name)['state'] + try: + info = self.driver.get_info(instance_ref['name']) + state = info['state'] + except exception.NotFound: + state = power_state.NOSTATE self.db.instance_set_state(context, instance_id, state) @defer.inlineCallbacks @@ -126,16 +128,15 @@ class ComputeManager(manager.Manager): def reboot_instance(self, context, instance_id): """Reboot an instance on this server.""" context = context.elevated() - self._update_state(context, instance_id) instance_ref = self.db.instance_get(context, instance_id) + self._update_state(context, instance_id) if instance_ref['state'] != power_state.RUNNING: - raise exception.Error( - 'trying to reboot a non-running' - 'instance: %s (state: %s excepted: %s)' % - (instance_ref['internal_id'], - instance_ref['state'], - power_state.RUNNING)) + logging.warn('trying to reboot a non-running ' + 'instance: %s (state: %s excepted: %s)', + instance_ref['internal_id'], + instance_ref['state'], + power_state.RUNNING) logging.debug('instance %s: rebooting', instance_ref['name']) self.db.instance_set_state(context, @@ -145,6 +146,38 @@ class ComputeManager(manager.Manager): yield self.driver.reboot(instance_ref) self._update_state(context, instance_id) + @defer.inlineCallbacks + @exception.wrap_exception + def rescue_instance(self, context, instance_id): + """Rescue an instance on this server.""" + context = context.elevated() + instance_ref = self.db.instance_get(context, instance_id) + + logging.debug('instance %s: rescuing', + instance_ref['internal_id']) + self.db.instance_set_state(context, + instance_id, + power_state.NOSTATE, + 'rescuing') + yield self.driver.rescue(instance_ref) + self._update_state(context, instance_id) + + @defer.inlineCallbacks + @exception.wrap_exception + def unrescue_instance(self, context, instance_id): + """Rescue an instance on this server.""" + context = context.elevated() + instance_ref = self.db.instance_get(context, instance_id) + + logging.debug('instance %s: unrescuing', + instance_ref['internal_id']) + self.db.instance_set_state(context, + instance_id, + power_state.NOSTATE, + 'unrescuing') + yield self.driver.unrescue(instance_ref) + self._update_state(context, instance_id) + @exception.wrap_exception def get_console_output(self, context, instance_id): """Send the console output for an instance.""" diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 0cbe56499..a3d8dde2f 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -19,6 +19,7 @@ Implementation of SQLAlchemy backend """ +import random import warnings from nova import db @@ -542,7 +543,8 @@ def instance_create(context, values): session = get_session() with session.begin(): while instance_ref.internal_id == None: - internal_id = utils.generate_uid(instance_ref.__prefix__) + # Instances have integer internal ids. + internal_id = random.randint(0, 2 ** 32 - 1) if not instance_internal_id_exists(context, internal_id, session=session): instance_ref.internal_id = internal_id @@ -1152,7 +1154,7 @@ def volume_create(context, values): session = get_session() with session.begin(): while volume_ref.ec2_id == None: - ec2_id = utils.generate_uid(volume_ref.__prefix__) + ec2_id = utils.generate_uid('vol') if not volume_ec2_id_exists(context, ec2_id, session=session): volume_ref.ec2_id = ec2_id volume_ref.save(session=session) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 38c96bdec..2a3cfa94c 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -20,11 +20,9 @@ SQLAlchemy models for nova data """ -import sys import datetime -# TODO(vish): clean up these imports -from sqlalchemy.orm import relationship, backref, exc, object_mapper +from sqlalchemy.orm import relationship, backref, object_mapper from sqlalchemy import Column, Integer, String, schema from sqlalchemy import ForeignKey, DateTime, Boolean, Text from sqlalchemy.exc import IntegrityError @@ -46,17 +44,11 @@ class NovaBase(object): """Base class for Nova Models""" __table_args__ = {'mysql_engine': 'InnoDB'} __table_initialized__ = False - __prefix__ = 'none' created_at = Column(DateTime, default=datetime.datetime.utcnow) updated_at = Column(DateTime, onupdate=datetime.datetime.utcnow) deleted_at = Column(DateTime) deleted = Column(Boolean, default=False) - @property - def str_id(self): - """Get string id of object (generally prefix + '-' + id)""" - return "%s-%s" % (self.__prefix__, self.id) - def save(self, session=None): """Save this object""" if not session: @@ -94,7 +86,6 @@ class NovaBase(object): #class Image(BASE, NovaBase): # """Represents an image in the datastore""" # __tablename__ = 'images' -# __prefix__ = 'ami' # id = Column(Integer, primary_key=True) # ec2_id = Column(String(12), unique=True) # user_id = Column(String(255)) @@ -150,7 +141,6 @@ class Service(BASE, NovaBase): class Instance(BASE, NovaBase): """Represents a guest vm""" __tablename__ = 'instances' - __prefix__ = 'i' id = Column(Integer, primary_key=True) internal_id = Column(Integer, unique=True) @@ -227,7 +217,6 @@ class Instance(BASE, NovaBase): class Volume(BASE, NovaBase): """Represents a block storage device that can be attached to a vm""" __tablename__ = 'volumes' - __prefix__ = 'vol' id = Column(Integer, primary_key=True) ec2_id = Column(String(12), unique=True) @@ -269,10 +258,6 @@ class Quota(BASE, NovaBase): gigabytes = Column(Integer) floating_ips = Column(Integer) - @property - def str_id(self): - return self.project_id - class ExportDevice(BASE, NovaBase): """Represates a shelf and blade that a volume can be exported on""" @@ -361,10 +346,6 @@ class KeyPair(BASE, NovaBase): fingerprint = Column(String(255)) public_key = Column(Text) - @property - def str_id(self): - return '%s.%s' % (self.user_id, self.name) - class Network(BASE, NovaBase): """Represents a network""" @@ -426,10 +407,6 @@ class FixedIp(BASE, NovaBase): leased = Column(Boolean, default=False) reserved = Column(Boolean, default=False) - @property - def str_id(self): - return self.address - class User(BASE, NovaBase): """Represents a user""" diff --git a/nova/network/manager.py b/nova/network/manager.py index fddb77663..8a20cb491 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -171,7 +171,7 @@ class NetworkManager(manager.Manager): if not fixed_ip_ref['leased']: logging.warn("IP %s released that was not leased", address) self.db.fixed_ip_update(context, - fixed_ip_ref['str_id'], + fixed_ip_ref['address'], {'leased': False}) if not fixed_ip_ref['allocated']: self.db.fixed_ip_disassociate(context, address) diff --git a/nova/test.py b/nova/test.py index 8ef7eca1a..5c2a72819 100644 --- a/nova/test.py +++ b/nova/test.py @@ -28,7 +28,6 @@ import time import mox import stubout -from tornado import ioloop from twisted.internet import defer from twisted.trial import unittest @@ -159,158 +158,3 @@ class TrialTestCase(unittest.TestCase): _wrapped.func_name = self.originalAttach.func_name rpc.Consumer.attach_to_twisted = _wrapped - - -class BaseTestCase(TrialTestCase): - # TODO(jaypipes): Can this be moved into the TrialTestCase class? - """Base test case class for all unit tests. - - DEPRECATED: This is being removed once Tornado is gone, use TrialTestCase. - """ - def setUp(self): - """Run before each test method to initialize test environment""" - super(BaseTestCase, self).setUp() - # TODO(termie): we could possibly keep a more global registry of - # the injected listeners... this is fine for now though - self.ioloop = ioloop.IOLoop.instance() - - self._waiting = None - self._done_waiting = False - self._timed_out = False - - def _wait_for_test(self, timeout=60): - """ Push the ioloop along to wait for our test to complete. """ - self._waiting = self.ioloop.add_timeout(time.time() + timeout, - self._timeout) - - def _wait(): - - """Wrapped wait function. Called on timeout.""" - if self._timed_out: - self.fail('test timed out') - self._done() - if self._done_waiting: - self.ioloop.stop() - return - # we can use add_callback here but this uses less cpu when testing - self.ioloop.add_timeout(time.time() + 0.01, _wait) - - self.ioloop.add_callback(_wait) - self.ioloop.start() - - def _done(self): - """Callback used for cleaning up deferred test methods.""" - if self._waiting: - try: - self.ioloop.remove_timeout(self._waiting) - except Exception: # pylint: disable-msg=W0703 - # TODO(jaypipes): This produces a pylint warning. Should - # we really be catching Exception and then passing here? - pass - self._waiting = None - self._done_waiting = True - - def _maybe_inline_callbacks(self, func): - """ If we're doing async calls in our tests, wait on them. - - This is probably the most complicated hunk of code we have so far. - - First up, if the function is normal (not async) we just act normal - and return. - - Async tests will use the "Inline Callbacks" pattern, which means - you yield Deferreds at every "waiting" step of your code instead - of making epic callback chains. - - Example (callback chain, ugly): - - # A deferred instance - d = self.compute.terminate_instance(instance_id) - def _describe(_): - # Another deferred instance - d_desc = self.compute.describe_instances() - return d_desc - def _checkDescribe(rv): - self.assertEqual(rv, []) - d.addCallback(_describe) - d.addCallback(_checkDescribe) - d.addCallback(lambda x: self._done()) - self._wait_for_test() - - Example (inline callbacks! yay!): - - yield self.compute.terminate_instance(instance_id) - rv = yield self.compute.describe_instances() - self.assertEqual(rv, []) - - If the test fits the Inline Callbacks pattern we will automatically - handle calling wait and done. - """ - # TODO(termie): this can be a wrapper function instead and - # and we can make a metaclass so that we don't - # have to copy all that "run" code below. - g = func() - if not hasattr(g, 'send'): - self._done() - return defer.succeed(g) - - inlined = defer.inlineCallbacks(func) - d = inlined() - return d - - def _catch_exceptions(self, result, failure): - """Catches all exceptions and handles keyboard interrupts.""" - exc = (failure.type, failure.value, failure.getTracebackObject()) - if isinstance(failure.value, self.failureException): - result.addFailure(self, exc) - elif isinstance(failure.value, KeyboardInterrupt): - raise - else: - result.addError(self, exc) - - self._done() - - def _timeout(self): - """Helper method which trips the timeouts""" - self._waiting = False - self._timed_out = True - - def run(self, result=None): - """Runs the test case""" - - result.startTest(self) - test_method = getattr(self, self._testMethodName) - try: - try: - self.setUp() - except KeyboardInterrupt: - raise - except: - result.addError(self, sys.exc_info()) - return - - ok = False - try: - d = self._maybe_inline_callbacks(test_method) - d.addErrback(lambda x: self._catch_exceptions(result, x)) - d.addBoth(lambda x: self._done() and x) - self._wait_for_test() - ok = True - except self.failureException: - result.addFailure(self, sys.exc_info()) - except KeyboardInterrupt: - raise - except: - result.addError(self, sys.exc_info()) - - try: - self.tearDown() - except KeyboardInterrupt: - raise - except: - result.addError(self, sys.exc_info()) - ok = False - if ok: - result.addSuccess(self) - finally: - result.stopTest(self) diff --git a/nova/tests/api_unittest.py b/nova/tests/api_unittest.py index 0b1c3e353..0a81c575b 100644 --- a/nova/tests/api_unittest.py +++ b/nova/tests/api_unittest.py @@ -83,7 +83,7 @@ class FakeHttplibConnection(object): pass -class XmlConversionTestCase(test.BaseTestCase): +class XmlConversionTestCase(test.TrialTestCase): """Unit test api xml conversion""" def test_number_conversion(self): conv = apirequest._try_convert @@ -100,7 +100,7 @@ class XmlConversionTestCase(test.BaseTestCase): self.assertEqual(conv('-0'), 0) -class ApiEc2TestCase(test.BaseTestCase): +class ApiEc2TestCase(test.TrialTestCase): """Unit test for the cloud controller on an EC2 API""" def setUp(self): super(ApiEc2TestCase, self).setUp() diff --git a/nova/tests/virt_unittest.py b/nova/tests/virt_unittest.py index ce78d450c..d49383fb7 100644 --- a/nova/tests/virt_unittest.py +++ b/nova/tests/virt_unittest.py @@ -91,7 +91,7 @@ class LibvirtConnTestCase(test.TrialTestCase): FLAGS.libvirt_type = libvirt_type conn = libvirt_conn.LibvirtConnection(True) - uri, template = conn.get_uri_and_template() + uri, _template, _rescue = conn.get_uri_and_templates() self.assertEquals(uri, expected_uri) xml = conn.to_xml(instance_ref) @@ -114,7 +114,7 @@ class LibvirtConnTestCase(test.TrialTestCase): for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems(): FLAGS.libvirt_type = libvirt_type conn = libvirt_conn.LibvirtConnection(True) - uri, template = conn.get_uri_and_template() + uri, _template, _rescue = conn.get_uri_and_templates() self.assertEquals(uri, testuri) def tearDown(self): diff --git a/nova/utils.py b/nova/utils.py index e58302c11..2c53b027e 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -132,13 +132,9 @@ def runthis(prompt, cmd, check_exit_code=True): def generate_uid(topic, size=8): - if topic == "i": - # Instances have integer internal ids. - return random.randint(0, 2 ** 32 - 1) - else: - characters = '01234567890abcdefghijklmnopqrstuvwxyz' - choices = [random.choice(characters) for x in xrange(size)] - return '%s-%s' % (topic, ''.join(choices)) + characters = '01234567890abcdefghijklmnopqrstuvwxyz' + choices = [random.choice(characters) for x in xrange(size)] + return '%s-%s' % (topic, ''.join(choices)) def generate_mac(): diff --git a/nova/virt/fake.py b/nova/virt/fake.py index eaa2261f5..66eff4c66 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -22,10 +22,9 @@ A fake (in-memory) hypervisor+api. Allows nova testing w/o a hypervisor. This module also documents the semantics of real hypervisor connections. """ -import logging - from twisted.internet import defer +from nova import exception from nova.compute import power_state @@ -119,6 +118,18 @@ class FakeConnection(object): """ return defer.succeed(None) + def rescue(self, instance): + """ + Rescue the specified instance. + """ + return defer.succeed(None) + + def unrescue(self, instance): + """ + Unrescue the specified instance. + """ + return defer.succeed(None) + def destroy(self, instance): """ Destroy (shutdown and delete) the specified instance. @@ -148,7 +159,12 @@ class FakeConnection(object): current memory the instance has, in KiB, 'num_cpu': The current number of virtual CPUs the instance has, 'cpu_time': The total CPU time used by the instance, in nanoseconds. + + This method should raise exception.NotFound if the hypervisor has no + knowledge of the instance """ + if instance_name not in self.instances: + raise exception.NotFound("Instance %s Not Found" % instance_name) i = self.instances[instance_name] return {'state': i._state, 'max_mem': 0, diff --git a/nova/virt/libvirt.rescue.qemu.xml.template b/nova/virt/libvirt.rescue.qemu.xml.template new file mode 100644 index 000000000..c0ffbdcee --- /dev/null +++ b/nova/virt/libvirt.rescue.qemu.xml.template @@ -0,0 +1,37 @@ +<domain type='%(type)s'> + <name>%(name)s</name> + <os> + <type>hvm</type> + <kernel>%(basepath)s/rescue-kernel</kernel> + <initrd>%(basepath)s/rescue-ramdisk</initrd> + <cmdline>root=/dev/vda1 console=ttyS0</cmdline> + </os> + <features> + <acpi/> + </features> + <memory>%(memory_kb)s</memory> + <vcpu>%(vcpus)s</vcpu> + <devices> + <disk type='file'> + <source file='%(basepath)s/rescue-disk'/> + <target dev='vda' bus='virtio'/> + </disk> + <disk type='file'> + <source file='%(basepath)s/disk'/> + <target dev='vdb' bus='virtio'/> + </disk> + <interface type='bridge'> + <source bridge='%(bridge_name)s'/> + <mac address='%(mac_address)s'/> + <!-- <model type='virtio'/> CANT RUN virtio network right now --> + <filterref filter="nova-instance-%(name)s"> + <parameter name="IP" value="%(ip_address)s" /> + <parameter name="DHCPSERVER" value="%(dhcp_server)s" /> + </filterref> + </interface> + <serial type="file"> + <source path='%(basepath)s/console.log'/> + <target port='1'/> + </serial> + </devices> +</domain> diff --git a/nova/virt/libvirt.rescue.uml.xml.template b/nova/virt/libvirt.rescue.uml.xml.template new file mode 100644 index 000000000..836f47532 --- /dev/null +++ b/nova/virt/libvirt.rescue.uml.xml.template @@ -0,0 +1,26 @@ +<domain type='%(type)s'> + <name>%(name)s</name> + <memory>%(memory_kb)s</memory> + <os> + <type>%(type)s</type> + <kernel>/usr/bin/linux</kernel> + <root>/dev/ubda1</root> + </os> + <devices> + <disk type='file'> + <source file='%(basepath)s/rescue-disk'/> + <target dev='ubd0' bus='uml'/> + </disk> + <disk type='file'> + <source file='%(basepath)s/disk'/> + <target dev='ubd1' bus='uml'/> + </disk> + <interface type='bridge'> + <source bridge='%(bridge_name)s'/> + <mac address='%(mac_address)s'/> + </interface> + <console type="file"> + <source path='%(basepath)s/console.log'/> + </console> + </devices> +</domain> diff --git a/nova/virt/libvirt.rescue.xen.xml.template b/nova/virt/libvirt.rescue.xen.xml.template new file mode 100644 index 000000000..3b8d27237 --- /dev/null +++ b/nova/virt/libvirt.rescue.xen.xml.template @@ -0,0 +1,34 @@ +<domain type='%(type)s'> + <name>%(name)s</name> + <os> + <type>linux</type> + <kernel>%(basepath)s/kernel</kernel> + <initrd>%(basepath)s/ramdisk</initrd> + <root>/dev/xvda1</root> + <cmdline>ro</cmdline> + </os> + <features> + <acpi/> + </features> + <memory>%(memory_kb)s</memory> + <vcpu>%(vcpus)s</vcpu> + <devices> + <disk type='file'> + <source file='%(basepath)s/rescue-disk'/> + <target dev='sda' /> + </disk> + <disk type='file'> + <source file='%(basepath)s/disk'/> + <target dev='sdb' /> + </disk> + <interface type='bridge'> + <source bridge='%(bridge_name)s'/> + <mac address='%(mac_address)s'/> + </interface> + <console type="file"> + <source path='%(basepath)s/console.log'/> + <target port='1'/> + </console> + </devices> +</domain> + diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 509ed97a0..e32945fa5 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -48,6 +48,19 @@ libxml2 = None FLAGS = flags.FLAGS +flags.DEFINE_string('libvirt_rescue_xml_template', + utils.abspath('virt/libvirt.rescue.qemu.xml.template'), + 'Libvirt RESCUE XML Template for QEmu/KVM') +flags.DEFINE_string('libvirt_rescue_xen_xml_template', + utils.abspath('virt/libvirt.rescue.xen.xml.template'), + 'Libvirt RESCUE XML Template for xen') +flags.DEFINE_string('libvirt_rescue_uml_xml_template', + utils.abspath('virt/libvirt.rescue.uml.xml.template'), + 'Libvirt RESCUE XML Template for user-mode-linux') +# TODO(vish): These flags should probably go into a shared location +flags.DEFINE_string('rescue_image_id', 'ami-rescue', 'Rescue ami image') +flags.DEFINE_string('rescue_kernel_id', 'aki-rescue', 'Rescue aki image') +flags.DEFINE_string('rescue_ramdisk_id', 'ari-rescue', 'Rescue ari image') flags.DEFINE_string('libvirt_xml_template', utils.abspath('virt/libvirt.qemu.xml.template'), 'Libvirt XML Template for QEmu/KVM') @@ -87,9 +100,12 @@ def get_connection(read_only): class LibvirtConnection(object): def __init__(self, read_only): - self.libvirt_uri, template_file = self.get_uri_and_template() + (self.libvirt_uri, + template_file, + rescue_file) = self.get_uri_and_templates() self.libvirt_xml = open(template_file).read() + self.rescue_xml = open(rescue_file).read() self._wrapped_conn = None self.read_only = read_only @@ -112,17 +128,20 @@ class LibvirtConnection(object): return False raise - def get_uri_and_template(self): + def get_uri_and_templates(self): if FLAGS.libvirt_type == 'uml': uri = FLAGS.libvirt_uri or 'uml:///system' template_file = FLAGS.libvirt_uml_xml_template + rescue_file = FLAGS.libvirt_rescue_uml_xml_template elif FLAGS.libvirt_type == 'xen': uri = FLAGS.libvirt_uri or 'xen:///' template_file = FLAGS.libvirt_xen_xml_template + rescue_file = FLAGS.libvirt_rescue_xen_xml_template else: uri = FLAGS.libvirt_uri or 'qemu:///system' template_file = FLAGS.libvirt_xml_template - return uri, template_file + rescue_file = FLAGS.libvirt_rescue_xml_template + return uri, template_file, rescue_file def _connect(self, uri, read_only): auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_NOECHOPROMPT], @@ -138,7 +157,7 @@ class LibvirtConnection(object): return [self._conn.lookupByID(x).name() for x in self._conn.listDomainsID()] - def destroy(self, instance): + def destroy(self, instance, cleanup=True): try: virt_dom = self._conn.lookupByName(instance['name']) virt_dom.destroy() @@ -146,10 +165,11 @@ class LibvirtConnection(object): pass # If the instance is already terminated, we're still happy d = defer.Deferred() - d.addCallback(lambda _: self._cleanup(instance)) + if cleanup: + d.addCallback(lambda _: self._cleanup(instance)) # FIXME: What does this comment mean? # TODO(termie): short-circuit me for tests - # WE'LL save this for when we do shutdown, + # WE'LL save this for when we do shutdown, # instead of destroy - but destroy returns immediately timer = task.LoopingCall(f=None) @@ -199,8 +219,8 @@ class LibvirtConnection(object): @defer.inlineCallbacks @exception.wrap_exception def reboot(self, instance): + yield self.destroy(instance, False) xml = self.to_xml(instance) - yield self._conn.lookupByName(instance['name']).destroy() yield self._conn.createXML(xml, 0) d = defer.Deferred() @@ -229,6 +249,48 @@ class LibvirtConnection(object): @defer.inlineCallbacks @exception.wrap_exception + def rescue(self, instance): + yield self.destroy(instance, False) + + xml = self.to_xml(instance, rescue=True) + rescue_images = {'image_id': FLAGS.rescue_image_id, + 'kernel_id': FLAGS.rescue_kernel_id, + 'ramdisk_id': FLAGS.rescue_ramdisk_id} + yield self._create_image(instance, xml, 'rescue-', rescue_images) + yield self._conn.createXML(xml, 0) + + d = defer.Deferred() + timer = task.LoopingCall(f=None) + + def _wait_for_rescue(): + try: + state = self.get_info(instance['name'])['state'] + db.instance_set_state(None, instance['id'], state) + if state == power_state.RUNNING: + logging.debug('instance %s: rescued', instance['name']) + timer.stop() + d.callback(None) + except Exception, exn: + logging.error('_wait_for_rescue failed: %s', exn) + db.instance_set_state(None, + instance['id'], + power_state.SHUTDOWN) + timer.stop() + d.callback(None) + + timer.f = _wait_for_rescue + timer.start(interval=0.5, now=True) + yield d + + @defer.inlineCallbacks + @exception.wrap_exception + def unrescue(self, instance): + # NOTE(vish): Because reboot destroys and recreates an instance using + # the normal xml file, we can just call reboot here + yield self.reboot(instance) + + @defer.inlineCallbacks + @exception.wrap_exception def spawn(self, instance): xml = self.to_xml(instance) db.instance_set_state(context.get_admin_context(), @@ -239,8 +301,6 @@ class LibvirtConnection(object): setup_nwfilters_for_instance(instance) yield self._create_image(instance, xml) yield self._conn.createXML(xml, 0) - # TODO(termie): this should actually register - # a callback to check for successful boot logging.debug("instance %s: is running", instance['name']) local_d = defer.Deferred() @@ -311,15 +371,16 @@ class LibvirtConnection(object): return d @defer.inlineCallbacks - def _create_image(self, inst, libvirt_xml): + def _create_image(self, inst, libvirt_xml, prefix='', disk_images=None): # syntactic nicety - basepath = lambda fname='': os.path.join(FLAGS.instances_path, + basepath = lambda fname='', prefix=prefix: os.path.join( + FLAGS.instances_path, inst['name'], - fname) + prefix + fname) # ensure directories exist and are writable - yield process.simple_execute('mkdir -p %s' % basepath()) - yield process.simple_execute('chmod 0777 %s' % basepath()) + yield process.simple_execute('mkdir -p %s' % basepath(prefix='')) + yield process.simple_execute('chmod 0777 %s' % basepath(prefix='')) # TODO(termie): these are blocking calls, it would be great # if they weren't. @@ -328,12 +389,17 @@ class LibvirtConnection(object): f.write(libvirt_xml) f.close() - os.close(os.open(basepath('console.log'), os.O_CREAT | os.O_WRONLY, - 0660)) + # NOTE(vish): No need add the prefix to console.log + os.close(os.open(basepath('console.log', ''), + os.O_CREAT | os.O_WRONLY, 0660)) user = manager.AuthManager().get_user(inst['user_id']) project = manager.AuthManager().get_project(inst['project_id']) + if not disk_images: + disk_images = {'image_id': inst['image_id'], + 'kernel_id': inst['kernel_id'], + 'ramdisk_id': inst['ramdisk_id']} if not os.path.exists(basepath('disk')): yield images.fetch(inst.image_id, basepath('disk-raw'), user, project) @@ -379,7 +445,9 @@ class LibvirtConnection(object): ['local_gb'] * 1024 * 1024 * 1024) - resize = inst['instance_type'] != 'm1.tiny' + resize = True + if inst['instance_type'] == 'm1.tiny' or prefix == 'rescue-': + resize = False yield disk.partition(basepath('disk-raw'), basepath('disk'), local_bytes, resize, execute=execute) @@ -387,7 +455,7 @@ class LibvirtConnection(object): yield process.simple_execute('sudo chown root %s' % basepath('disk')) - def to_xml(self, instance): + def to_xml(self, instance, rescue=False): # TODO(termie): cache? logging.debug('instance %s: starting toXML method', instance['name']) network = db.project_get_network(context.get_admin_context(), @@ -409,13 +477,19 @@ class LibvirtConnection(object): 'mac_address': instance['mac_address'], 'ip_address': ip_address, 'dhcp_server': dhcp_server} - libvirt_xml = self.libvirt_xml % xml_info + if rescue: + libvirt_xml = self.rescue_xml % xml_info + else: + libvirt_xml = self.libvirt_xml % xml_info logging.debug('instance %s: finished toXML method', instance['name']) return libvirt_xml def get_info(self, instance_name): - virt_dom = self._conn.lookupByName(instance_name) + try: + virt_dom = self._conn.lookupByName(instance_name) + except: + raise exception.NotFound("Instance %s not found" % instance_name) (state, max_mem, mem, num_cpu, cpu_time) = virt_dom.info() return {'state': state, 'max_mem': max_mem, |
