summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustin Santa Barbara <justin@fathomdb.com>2011-03-28 17:04:14 -0700
committerJustin Santa Barbara <justin@fathomdb.com>2011-03-28 17:04:14 -0700
commit012a5878737f8648e99c9ae9b84d3a86ee86131d (patch)
tree988b948ba850011e230826f7ed8aeb414758eff6
parent87bc3bca7904135656ed3a99efc19952be95dcbf (diff)
parente025ede777b0ee6652e035dfde467037f0485de2 (diff)
Merged with trunk
-rwxr-xr-xbin/nova-ajax-console-proxy2
-rwxr-xr-xcontrib/nova.sh33
-rw-r--r--nova/adminclient.py13
-rw-r--r--nova/api/ec2/cloud.py2
-rw-r--r--nova/api/openstack/__init__.py18
-rw-r--r--nova/api/openstack/backup_schedules.py6
-rw-r--r--nova/api/openstack/extensions.py2
-rw-r--r--nova/api/openstack/shared_ip_groups.py6
-rw-r--r--nova/compute/manager.py40
-rw-r--r--nova/tests/api/openstack/extensions/__init__.py15
-rw-r--r--nova/tests/api/openstack/test_images.py13
-rw-r--r--nova/tests/api/openstack/test_servers.py22
-rw-r--r--nova/tests/api/openstack/test_shared_ip_groups.py30
-rw-r--r--nova/tests/test_auth.py8
-rw-r--r--nova/virt/driver.py4
-rw-r--r--nova/virt/libvirt_conn.py81
16 files changed, 225 insertions, 70 deletions
diff --git a/bin/nova-ajax-console-proxy b/bin/nova-ajax-console-proxy
index b4ba157e1..0342c620a 100755
--- a/bin/nova-ajax-console-proxy
+++ b/bin/nova-ajax-console-proxy
@@ -115,7 +115,7 @@ class AjaxConsoleProxy(object):
{'args': data['args'], 'last_activity': time.time()}
conn = rpc.Connection.instance(new=True)
- consumer = rpc.TopicConsumer(
+ consumer = rpc.TopicAdapterConsumer(
connection=conn,
topic=FLAGS.ajax_console_proxy_topic)
consumer.register_callback(Callback())
diff --git a/contrib/nova.sh b/contrib/nova.sh
index 55dfb971c..d7d34dcbd 100755
--- a/contrib/nova.sh
+++ b/contrib/nova.sh
@@ -18,6 +18,9 @@ if [ ! -n "$HOST_IP" ]; then
fi
USE_MYSQL=${USE_MYSQL:-0}
+INTERFACE=${INTERFACE:-eth0}
+FLOATING_RANGE=${FLOATING_RANGE:-10.6.0.0/27}
+FIXED_RANGE=${FIXED_RANGE:-10.0.0.0/24}
MYSQL_PASS=${MYSQL_PASS:-nova}
TEST=${TEST:-0}
USE_LDAP=${USE_LDAP:-0}
@@ -72,11 +75,14 @@ if [ "$CMD" == "install" ]; then
sudo modprobe kvm
sudo /etc/init.d/libvirt-bin restart
sudo modprobe nbd
- sudo apt-get install -y python-twisted python-sqlalchemy python-mox python-greenlet python-carrot
- sudo apt-get install -y python-migrate python-eventlet python-gflags python-ipy python-tempita
- sudo apt-get install -y python-libvirt python-libxml2 python-routes python-cheetah
- sudo apt-get install -y python-netaddr python-paste python-pastedeploy python-glance
- sudo apt-get install -y python-multiprocessing
+ sudo apt-get install -y python-twisted python-mox python-ipy python-paste
+ sudo apt-get install -y python-migrate python-gflags python-greenlet
+ sudo apt-get install -y python-libvirt python-libxml2 python-routes
+ sudo apt-get install -y python-netaddr python-pastedeploy python-eventlet
+ sudo apt-get install -y python-novaclient python-glance python-cheetah
+ sudo apt-get install -y python-carrot python-tempita python-sqlalchemy
+ sudo apt-get install -y python-suds
+
if [ "$USE_IPV6" == 1 ]; then
sudo apt-get install -y radvd
@@ -105,7 +111,7 @@ function screen_it {
screen -S nova -p $1 -X stuff "$2$NL"
}
-if [ "$CMD" == "run" ]; then
+if [ "$CMD" == "run" ] || [ "$CMD" == "run_detached" ]; then
cat >$NOVA_DIR/bin/nova.conf << NOVA_CONF_EOF
--verbose
@@ -113,6 +119,8 @@ if [ "$CMD" == "run" ]; then
--dhcpbridge_flagfile=$NOVA_DIR/bin/nova.conf
--network_manager=nova.network.manager.$NET_MAN
--my_ip=$HOST_IP
+--public_interface=$INTERFACE
+--vlan_interface=$INTERFACE
--sql_connection=$SQL_CONN
--auth_driver=nova.auth.$AUTH
--libvirt_type=$LIBVIRT_TYPE
@@ -168,10 +176,13 @@ NOVA_CONF_EOF
# create a project called 'admin' with project manager of 'admin'
$NOVA_DIR/bin/nova-manage project create admin admin
# create a small network
- $NOVA_DIR/bin/nova-manage network create 10.0.0.0/8 1 32
+ $NOVA_DIR/bin/nova-manage network create $FIXED_RANGE 1 32
# create some floating ips
- $NOVA_DIR/bin/nova-manage floating create `hostname` 10.6.0.0/27
+ $NOVA_DIR/bin/nova-manage floating create `hostname` $FLOATING_RANGE
+
+ # convert old images
+ $NOVA_DIR/bin/nova-manage image convert $DIR/images
# nova api crashes if we start it with a regular screen command,
# so send the start command by forcing text into the window.
@@ -187,8 +198,10 @@ NOVA_CONF_EOF
$NOVA_DIR/bin/nova-manage project zipfile admin admin $NOVA_DIR/nova.zip
unzip -o $NOVA_DIR/nova.zip -d $NOVA_DIR/
- screen_it test ". $NOVA_DIR/novarc"
- screen -S nova -x
+ screen_it test "export PATH=$NOVA_DIR/bin:$PATH;. $NOVA_DIR/novarc"
+ if [ "$CMD" != "run_detached" ]; then
+ screen -S nova -x
+ fi
fi
if [ "$CMD" == "run" ] || [ "$CMD" == "terminate" ]; then
diff --git a/nova/adminclient.py b/nova/adminclient.py
index fc3c5c5fe..f570e12c2 100644
--- a/nova/adminclient.py
+++ b/nova/adminclient.py
@@ -324,14 +324,11 @@ class NovaAdminClient(object):
def get_user(self, name):
"""Grab a single user by name."""
- try:
- return self.apiconn.get_object('DescribeUser',
- {'Name': name},
- UserInfo)
- except boto.exception.BotoServerError, e:
- if e.status == 400 and e.error_code == 'NotFound':
- return None
- raise
+ user = self.apiconn.get_object('DescribeUser',
+ {'Name': name},
+ UserInfo)
+ if user.username != None:
+ return user
def has_user(self, username):
"""Determine if user exists."""
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 0da642318..9e34d3317 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -890,6 +890,8 @@ class CloudController(object):
i['imageOwnerId'] = image['properties'].get('owner_id')
i['imageLocation'] = image['properties'].get('image_location')
i['imageState'] = image['properties'].get('image_state')
+ i['displayName'] = image.get('name')
+ i['description'] = image.get('description')
i['type'] = image_type
i['isPublic'] = str(image['properties'].get('is_public', '')) == 'True'
i['architecture'] = image['properties'].get('architecture')
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index 7f2bb1155..8b9acc11d 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -106,11 +106,6 @@ class APIRouter(wsgi.Router):
controller=accounts.Controller(),
collection={'detail': 'GET'})
- mapper.resource("backup_schedule", "backup_schedule",
- controller=backup_schedules.Controller(),
- parent_resource=dict(member_name='server',
- collection_name='servers'))
-
mapper.resource("console", "consoles",
controller=consoles.Controller(),
parent_resource=dict(member_name='server',
@@ -119,10 +114,6 @@ class APIRouter(wsgi.Router):
mapper.resource("image", "images", controller=images.Controller(),
collection={'detail': 'GET'})
- mapper.resource("shared_ip_group", "shared_ip_groups",
- collection={'detail': 'GET'},
- controller=shared_ip_groups.Controller())
-
_limits = limits.LimitsController()
mapper.resource("limit", "limits", controller=_limits)
@@ -143,6 +134,15 @@ class APIRouterV10(APIRouter):
controller=flavors.ControllerV10(),
collection={'detail': 'GET'})
+ mapper.resource("shared_ip_group", "shared_ip_groups",
+ collection={'detail': 'GET'},
+ controller=shared_ip_groups.Controller())
+
+ mapper.resource("backup_schedule", "backup_schedule",
+ controller=backup_schedules.Controller(),
+ parent_resource=dict(member_name='server',
+ collection_name='servers'))
+
class APIRouterV11(APIRouter):
"""Define routes specific to OpenStack API V1.1."""
diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py
index 7abb5f884..f2d2d86e8 100644
--- a/nova/api/openstack/backup_schedules.py
+++ b/nova/api/openstack/backup_schedules.py
@@ -42,7 +42,11 @@ class Controller(wsgi.Controller):
def index(self, req, server_id):
""" Returns the list of backup schedules for a given instance """
- return _translate_keys({})
+ return faults.Fault(exc.HTTPNotImplemented())
+
+ def show(self, req, server_id, id):
+ """ Returns a single backup schedule for a given instance """
+ return faults.Fault(exc.HTTPNotImplemented())
def create(self, req, server_id):
""" No actual update method required, since the existing API allows
diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py
index 9566d3250..1c39dd0e2 100644
--- a/nova/api/openstack/extensions.py
+++ b/nova/api/openstack/extensions.py
@@ -372,7 +372,7 @@ class ExtensionManager(object):
for f in os.listdir(path):
mod_name, file_ext = os.path.splitext(os.path.split(f)[-1])
- if file_ext.startswith('_'):
+ if mod_name.startswith('_'):
continue
if file_ext.lower() != '.py':
continue
diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py
index 5d78f9377..ee7991d7f 100644
--- a/nova/api/openstack/shared_ip_groups.py
+++ b/nova/api/openstack/shared_ip_groups.py
@@ -42,11 +42,11 @@ class Controller(wsgi.Controller):
def index(self, req):
""" Returns a list of Shared IP Groups for the user """
- return dict(sharedIpGroups=[])
+ raise faults.Fault(exc.HTTPNotImplemented())
def show(self, req, id):
""" Shows in-depth information on a specific Shared IP Group """
- return _translate_keys({})
+ raise faults.Fault(exc.HTTPNotImplemented())
def update(self, req, id):
""" You can't update a Shared IP Group """
@@ -58,7 +58,7 @@ class Controller(wsgi.Controller):
def detail(self, req):
""" Returns a complete list of Shared IP Groups """
- return _translate_detail_keys({})
+ raise faults.Fault(exc.HTTPNotImplemented())
def create(self, req):
""" Creates a new Shared IP group """
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 468771f46..e0a5e2b3f 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -141,12 +141,6 @@ class ComputeManager(manager.SchedulerDependentManager):
"""
self.driver.init_host(host=self.host)
- def periodic_tasks(self, context=None):
- """Tasks to be run at a periodic interval."""
- super(ComputeManager, self).periodic_tasks(context)
- if FLAGS.rescue_timeout > 0:
- self.driver.poll_rescued_instances(FLAGS.rescue_timeout)
-
def _update_state(self, context, instance_id):
"""Update the state of an instance from the driver info."""
# FIXME(ja): include other fields from state?
@@ -1033,11 +1027,20 @@ class ComputeManager(manager.SchedulerDependentManager):
error_list = []
try:
+ if FLAGS.rescue_timeout > 0:
+ self.driver.poll_rescued_instances(FLAGS.rescue_timeout)
+ except Exception as ex:
+ LOG.warning(_("Error during poll_rescued_instances: %s"),
+ unicode(ex))
+ error_list.append(ex)
+
+ try:
self._poll_instance_states(context)
except Exception as ex:
LOG.warning(_("Error during instance poll: %s"),
unicode(ex))
error_list.append(ex)
+
return error_list
def _poll_instance_states(self, context):
@@ -1051,16 +1054,33 @@ class ComputeManager(manager.SchedulerDependentManager):
for db_instance in db_instances:
name = db_instance['name']
+ db_state = db_instance['state']
vm_instance = vm_instances.get(name)
+
if vm_instance is None:
- LOG.info(_("Found instance '%(name)s' in DB but no VM. "
- "Setting state to shutoff.") % locals())
- vm_state = power_state.SHUTOFF
+ # NOTE(justinsb): We have to be very careful here, because a
+ # concurrent operation could be in progress (e.g. a spawn)
+ if db_state == power_state.NOSTATE:
+ # Assume that NOSTATE => spawning
+ # TODO(justinsb): This does mean that if we crash during a
+ # spawn, the machine will never leave the spawning state,
+ # but this is just the way nova is; this function isn't
+ # trying to correct that problem.
+ # We could have a separate task to correct this error.
+ # TODO(justinsb): What happens during a live migration?
+ LOG.info(_("Found instance '%(name)s' in DB but no VM. "
+ "State=%(db_state)s, so assuming spawn is in "
+ "progress.") % locals())
+ vm_state = db_state
+ else:
+ LOG.info(_("Found instance '%(name)s' in DB but no VM. "
+ "State=%(db_state)s, so setting state to "
+ "shutoff.") % 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)s' to '%(vm_state)s'") % locals())
diff --git a/nova/tests/api/openstack/extensions/__init__.py b/nova/tests/api/openstack/extensions/__init__.py
new file mode 100644
index 000000000..848908a95
--- /dev/null
+++ b/nova/tests/api/openstack/extensions/__init__.py
@@ -0,0 +1,15 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack LLC
+#
+# 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.
diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py
index 1cdccadd6..738bdda19 100644
--- a/nova/tests/api/openstack/test_images.py
+++ b/nova/tests/api/openstack/test_images.py
@@ -43,9 +43,14 @@ from nova.tests.api.openstack import fakes
FLAGS = flags.FLAGS
-class BaseImageServiceTests(object):
+class _BaseImageServiceTests(test.TestCase):
"""Tasks to test for all image services"""
+ def __init__(self, *args, **kwargs):
+ super(_BaseImageServiceTests, self).__init__(*args, **kwargs)
+ self.service = None
+ self.context = None
+
def test_create(self):
fixture = self._make_fixture('test image')
num_images = len(self.service.index(self.context))
@@ -116,8 +121,7 @@ class BaseImageServiceTests(object):
return fixture
-class LocalImageServiceTest(test.TestCase,
- BaseImageServiceTests):
+class LocalImageServiceTest(_BaseImageServiceTests):
"""Tests the local image service"""
@@ -147,8 +151,7 @@ class LocalImageServiceTest(test.TestCase,
self.assertEqual(3, len(found_image_ids), len(found_image_ids))
-class GlanceImageServiceTest(test.TestCase,
- BaseImageServiceTests):
+class GlanceImageServiceTest(_BaseImageServiceTests):
"""Tests the Glance image service, in particular that metadata translation
works properly.
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 737b43c7b..989385a8c 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -483,21 +483,31 @@ class ServersTest(test.TestCase):
req.get_response(fakes.wsgi_app())
def test_create_backup_schedules(self):
- req = webob.Request.blank('/v1.0/servers/1/backup_schedules')
+ req = webob.Request.blank('/v1.0/servers/1/backup_schedule')
req.method = 'POST'
res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status, '404 Not Found')
+ self.assertEqual(res.status_int, 501)
def test_delete_backup_schedules(self):
- req = webob.Request.blank('/v1.0/servers/1/backup_schedules')
+ req = webob.Request.blank('/v1.0/servers/1/backup_schedule/1')
req.method = 'DELETE'
res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status, '404 Not Found')
+ self.assertEqual(res.status_int, 501)
def test_get_server_backup_schedules(self):
- req = webob.Request.blank('/v1.0/servers/1/backup_schedules')
+ req = webob.Request.blank('/v1.0/servers/1/backup_schedule')
res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status, '404 Not Found')
+ self.assertEqual(res.status_int, 501)
+
+ def test_get_server_backup_schedule(self):
+ req = webob.Request.blank('/v1.0/servers/1/backup_schedule/1')
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 501)
+
+ def test_server_backup_schedule_deprecated_v11(self):
+ req = webob.Request.blank('/v1.1/servers/1/backup_schedule')
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 404)
def test_get_all_server_details_v1_0(self):
req = webob.Request.blank('/v1.0/servers/detail')
diff --git a/nova/tests/api/openstack/test_shared_ip_groups.py b/nova/tests/api/openstack/test_shared_ip_groups.py
index b4de2ef41..c2bd7e45a 100644
--- a/nova/tests/api/openstack/test_shared_ip_groups.py
+++ b/nova/tests/api/openstack/test_shared_ip_groups.py
@@ -16,25 +16,49 @@
# under the License.
import stubout
+import webob
from nova import test
from nova.api.openstack import shared_ip_groups
+from nova.tests.api.openstack import fakes
class SharedIpGroupsTest(test.TestCase):
def setUp(self):
super(SharedIpGroupsTest, self).setUp()
self.stubs = stubout.StubOutForTesting()
+ fakes.FakeAuthManager.reset_fake_data()
+ fakes.FakeAuthDatabase.data = {}
+ fakes.stub_out_auth(self.stubs)
def tearDown(self):
self.stubs.UnsetAll()
super(SharedIpGroupsTest, self).tearDown()
def test_get_shared_ip_groups(self):
- pass
+ req = webob.Request.blank('/v1.0/shared_ip_groups')
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 501)
def test_create_shared_ip_group(self):
- pass
+ req = webob.Request.blank('/v1.0/shared_ip_groups')
+ req.method = 'POST'
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 501)
+
+ def test_update_shared_ip_group(self):
+ req = webob.Request.blank('/v1.0/shared_ip_groups/12')
+ req.method = 'PUT'
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 501)
def test_delete_shared_ip_group(self):
- pass
+ req = webob.Request.blank('/v1.0/shared_ip_groups/12')
+ req.method = 'DELETE'
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 501)
+
+ def test_deprecated_v11(self):
+ req = webob.Request.blank('/v1.1/shared_ip_groups')
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 404)
diff --git a/nova/tests/test_auth.py b/nova/tests/test_auth.py
index 885596f56..f8a1b1564 100644
--- a/nova/tests/test_auth.py
+++ b/nova/tests/test_auth.py
@@ -80,10 +80,10 @@ class user_and_project_generator(object):
self.manager.delete_project(self.project)
-class AuthManagerTestCase(object):
+class _AuthManagerBaseTestCase(test.TestCase):
def setUp(self):
FLAGS.auth_driver = self.auth_driver
- super(AuthManagerTestCase, self).setUp()
+ super(_AuthManagerBaseTestCase, self).setUp()
self.flags(connection_type='fake')
self.manager = manager.AuthManager(new=True)
@@ -331,11 +331,11 @@ class AuthManagerTestCase(object):
self.assertTrue(user.is_admin())
-class AuthManagerLdapTestCase(AuthManagerTestCase, test.TestCase):
+class AuthManagerLdapTestCase(_AuthManagerBaseTestCase):
auth_driver = 'nova.auth.ldapdriver.FakeLdapDriver'
-class AuthManagerDbTestCase(AuthManagerTestCase, test.TestCase):
+class AuthManagerDbTestCase(_AuthManagerBaseTestCase):
auth_driver = 'nova.auth.dbdriver.DbDriver'
diff --git a/nova/virt/driver.py b/nova/virt/driver.py
index f85796a97..55281b741 100644
--- a/nova/virt/driver.py
+++ b/nova/virt/driver.py
@@ -243,3 +243,7 @@ class ComputeDriver(object):
def inject_network_info(self, instance):
"""inject network info for specified instance"""
raise NotImplementedError()
+
+ def poll_rescued_instances(self, timeout):
+ """Poll for rescued instances"""
+ raise NotImplementedError()
diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py
index 5962507d7..80eb64f3c 100644
--- a/nova/virt/libvirt_conn.py
+++ b/nova/virt/libvirt_conn.py
@@ -38,12 +38,15 @@ Supports KVM, QEMU, UML, and XEN.
import multiprocessing
import os
-import shutil
-import sys
import random
+import shutil
import subprocess
+import sys
+import tempfile
+import time
import uuid
from xml.dom import minidom
+from xml.etree import ElementTree
from eventlet import greenthread
from eventlet import tpool
@@ -111,6 +114,8 @@ flags.DEFINE_string('live_migration_flag',
'Define live migration behavior.')
flags.DEFINE_integer('live_migration_bandwidth', 0,
'Define live migration behavior')
+flags.DEFINE_string('qemu_img', 'qemu-img',
+ 'binary to use for qemu-img commands')
def get_connection(read_only):
@@ -397,10 +402,67 @@ class LibvirtConnection(driver.ComputeDriver):
@exception.wrap_exception
def snapshot(self, instance, image_id):
- """ Create snapshot from a running VM instance """
- raise NotImplementedError(
- _("Instance snapshotting is not supported for libvirt"
- "at this time"))
+ """Create snapshot from a running VM instance.
+
+ This command only works with qemu 0.14+, the qemu_img flag is
+ provided so that a locally compiled binary of qemu-img can be used
+ to support this command.
+
+ """
+ image_service = utils.import_object(FLAGS.image_service)
+ virt_dom = self._conn.lookupByName(instance['name'])
+ elevated = context.get_admin_context()
+
+ base = image_service.show(elevated, instance['image_id'])
+
+ metadata = {'disk_format': base['disk_format'],
+ 'container_format': base['container_format'],
+ 'is_public': False,
+ 'properties': {'architecture': base['architecture'],
+ 'type': base['type'],
+ 'name': '%s.%s' % (base['name'], image_id),
+ 'kernel_id': instance['kernel_id'],
+ 'image_location': 'snapshot',
+ 'image_state': 'available',
+ 'owner_id': instance['project_id'],
+ 'ramdisk_id': instance['ramdisk_id'],
+ }
+ }
+
+ # Make the snapshot
+ snapshot_name = uuid.uuid4().hex
+ snapshot_xml = """
+ <domainsnapshot>
+ <name>%s</name>
+ </domainsnapshot>
+ """ % snapshot_name
+ snapshot_ptr = virt_dom.snapshotCreateXML(snapshot_xml, 0)
+
+ # Find the disk
+ xml_desc = virt_dom.XMLDesc(0)
+ domain = ElementTree.fromstring(xml_desc)
+ source = domain.find('devices/disk/source')
+ disk_path = source.get('file')
+
+ # Export the snapshot to a raw image
+ temp_dir = tempfile.mkdtemp()
+ out_path = os.path.join(temp_dir, snapshot_name)
+ qemu_img_cmd = '%s convert -f qcow2 -O raw -s %s %s %s' % (
+ FLAGS.qemu_img,
+ snapshot_name,
+ disk_path,
+ out_path)
+ utils.execute(qemu_img_cmd)
+
+ # Upload that image to the image service
+ with open(out_path) as image_file:
+ image_service.update(elevated,
+ image_id,
+ metadata,
+ image_file)
+
+ # Clean up
+ shutil.rmtree(temp_dir)
@exception.wrap_exception
def reboot(self, instance):
@@ -760,11 +822,12 @@ class LibvirtConnection(driver.ComputeDriver):
'dns': network_ref['dns'],
'address_v6': address_v6,
'gateway_v6': network_ref['gateway_v6'],
- 'netmask_v6': network_ref['netmask_v6'],
- 'use_ipv6': FLAGS.use_ipv6}
+ 'netmask_v6': network_ref['netmask_v6']}
nets.append(net_info)
- net = str(Template(ifc_template, searchList=[{'interfaces': nets}]))
+ net = str(Template(ifc_template,
+ searchList=[{'interfaces': nets,
+ 'use_ipv6': FLAGS.use_ipv6}]))
if key or net:
inst_name = inst['name']