summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVishvananda Ishaya <vishvananda@yahoo.com>2010-10-25 23:08:57 -0700
committerVishvananda Ishaya <vishvananda@yahoo.com>2010-10-25 23:08:57 -0700
commit23ab6ceedd7d552faf2b97c44aadeccc45c9c333 (patch)
treeea1e257180cb9abe31f993e4807150d9b6c80388
parent627a968e79ed21d970225e5ece332d9100abe022 (diff)
parentf0d79d7d602a31fff03d8d934203128a2cd8940d (diff)
downloadnova-23ab6ceedd7d552faf2b97c44aadeccc45c9c333.tar.gz
nova-23ab6ceedd7d552faf2b97c44aadeccc45c9c333.tar.xz
nova-23ab6ceedd7d552faf2b97c44aadeccc45c9c333.zip
merged gundlach's excision
-rw-r--r--nova/api/cloud.py20
-rw-r--r--nova/api/ec2/cloud.py17
-rw-r--r--nova/compute/manager.py53
-rw-r--r--nova/db/sqlalchemy/api.py6
-rw-r--r--nova/db/sqlalchemy/models.py25
-rw-r--r--nova/network/manager.py2
-rw-r--r--nova/test.py156
-rw-r--r--nova/tests/api_unittest.py4
-rw-r--r--nova/tests/virt_unittest.py4
-rw-r--r--nova/utils.py10
-rw-r--r--nova/virt/fake.py20
-rw-r--r--nova/virt/libvirt.rescue.qemu.xml.template37
-rw-r--r--nova/virt/libvirt.rescue.uml.xml.template26
-rw-r--r--nova/virt/libvirt.rescue.xen.xml.template34
-rw-r--r--nova/virt/libvirt_conn.py114
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,