From 92f076c981343e09c240533acf49a6fdd0384555 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 22 Mar 2011 16:39:04 -0700 Subject: Set XML namespace when returning XML --- nova/wsgi.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'nova') diff --git a/nova/wsgi.py b/nova/wsgi.py index ba0819466..f0a60582d 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -478,6 +478,9 @@ class Serializer(object): root_key = data.keys()[0] doc = minidom.Document() node = self._to_xml_node(doc, metadata, root_key, data[root_key]) + node.setAttribute('xmlns', + 'http://docs.rackspacecloud.com/servers/api/v1.0') + return node.toprettyxml(indent=' ') def _to_xml_node(self, doc, metadata, nodename, data): -- cgit From 5c31b423ba5b5347aac62559c4e5c0a02f264213 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 22 Mar 2011 16:59:03 -0700 Subject: Support setting the xmlns intelligently --- nova/wsgi.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/wsgi.py b/nova/wsgi.py index f0a60582d..5d286bb3b 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -355,7 +355,8 @@ class Controller(object): if type(result) is dict: content_type = req.best_match_content_type() - body = self._serialize(result, content_type) + default_xmlns = self.get_default_xmlns(req) + body = self._serialize(result, content_type, default_xmlns) response = webob.Response() response.headers["Content-Type"] = content_type @@ -365,14 +366,15 @@ class Controller(object): else: return result - def _serialize(self, data, content_type): + def _serialize(self, data, content_type, default_xmlns): """ Serialize the given dict to the provided content_type. Uses self._serialization_metadata if it exists, which is a dict mapping MIME types to information needed to serialize to that type. """ _metadata = getattr(type(self), "_serialization_metadata", {}) - serializer = Serializer(_metadata) + + serializer = Serializer(_metadata, default_xmlns) try: return serializer.serialize(data, content_type) except exception.InvalidContentType: @@ -388,19 +390,23 @@ class Controller(object): serializer = Serializer(_metadata) return serializer.deserialize(data, content_type) + def get_default_xmlns(self, req): + return 'http://docs.rackspacecloud.com/servers/api/v1.0' + class Serializer(object): """ Serializes and deserializes dictionaries to certain MIME types. """ - def __init__(self, metadata=None): + def __init__(self, metadata=None, default_xmlns=None): """ Create a serializer based on the given WSGI environment. 'metadata' is an optional dict mapping MIME types to information needed to serialize a dictionary to that type. """ self.metadata = metadata or {} + self.default_xmlns = default_xmlns def _get_serialize_handler(self, content_type): handlers = { @@ -478,14 +484,23 @@ class Serializer(object): root_key = data.keys()[0] doc = minidom.Document() node = self._to_xml_node(doc, metadata, root_key, data[root_key]) - node.setAttribute('xmlns', - 'http://docs.rackspacecloud.com/servers/api/v1.0') + + xmlns = node.getAttribute('xmlns') + if not xmlns and self.default_xmlns: + node.setAttribute('xmlns', self.default_xmlns) return node.toprettyxml(indent=' ') def _to_xml_node(self, doc, metadata, nodename, data): """Recursive method to convert data members to XML nodes.""" result = doc.createElement(nodename) + + # Set the xml namespace if one is specified + # TODO(justinsb): We could also use prefixes on the keys + xmlns = metadata.get('xmlns', None) + if xmlns: + result.setAttribute('xmlns', xmlns) + if type(data) is list: singular = metadata.get('plurals', {}).get(nodename, None) if singular is None: -- cgit From 45d28dfb035b4e219845d44e00073d70211e8175 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 22 Mar 2011 21:14:26 -0700 Subject: Fixed up unit tests and direct api that was also calling _serialize (naughty!) --- nova/api/direct.py | 4 +++- nova/tests/api/openstack/test_limits.py | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/api/direct.py b/nova/api/direct.py index dfca250e0..153871e9f 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -206,7 +206,9 @@ class ServiceWrapper(wsgi.Controller): params = dict([(str(k), v) for (k, v) in params.iteritems()]) result = method(context, **params) if type(result) is dict or type(result) is list: - return self._serialize(result, req.best_match_content_type()) + content_type = req.best_match_content_type() + default_xmlns = self.get_default_xmlns(req) + return self._serialize(result, content_type, default_xmlns) else: return result diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py index 05cfacc60..df367005d 100644 --- a/nova/tests/api/openstack/test_limits.py +++ b/nova/tests/api/openstack/test_limits.py @@ -136,10 +136,17 @@ class LimitsControllerTest(BaseLimitTestSuite): request = self._get_index_request("application/xml") response = request.get_response(self.controller) - expected = "" - body = response.body.replace("\n", "").replace(" ", "") + expected = parseString(""" + + + + + """.replace(" ", "")) - self.assertEqual(expected, body) + body = parseString(response.body.replace(" ", "")) + + self.assertEqual(expected.toxml(), body.toxml()) def test_index_xml(self): """Test getting limit details in XML.""" @@ -148,7 +155,8 @@ class LimitsControllerTest(BaseLimitTestSuite): response = request.get_response(self.controller) expected = parseString(""" - + -- cgit From 9686b3a296c53486a64a949ae2f7430e25df2dcb Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 22 Mar 2011 21:18:31 -0700 Subject: Added note agreeing with Brian Lamar that the namespace doesn't belong in wsgi --- nova/wsgi.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nova') diff --git a/nova/wsgi.py b/nova/wsgi.py index 5d286bb3b..1bcc08f7f 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -391,6 +391,10 @@ class Controller(object): return serializer.deserialize(data, content_type) def get_default_xmlns(self, req): + # NOTE(justinsb): This doesn't really belong here.. + # We'll probably end up moving this into a new OpenstackApiController + # class or something like that, once we know what's going to happen + # with v1.1 return 'http://docs.rackspacecloud.com/servers/api/v1.0' -- cgit From 5c74862a08a82b7db3e11fbcbec63293ea903e00 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Mon, 28 Mar 2011 23:11:42 -0700 Subject: make all openstack status uppercase --- nova/api/openstack/views/servers.py | 4 ++-- nova/tests/api/openstack/test_servers.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 4e7f62eb3..6b471a0f4 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -72,12 +72,12 @@ class ViewBuilder(object): 'id': int(inst['id']), 'name': inst['display_name'], 'addresses': self.addresses_builder.build(inst), - 'status': power_mapping[inst.get('state')]} + 'status': power_mapping[inst.get('state')].upper()} ctxt = nova.context.get_admin_context() compute_api = nova.compute.API() if compute_api.has_finished_migration(ctxt, inst['id']): - inst_dict['status'] = 'resize-confirm' + inst_dict['status'] = 'resize-confirm'.upper() # Return the metadata as a dictionary metadata = {} diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 989385a8c..27cbd28c1 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -738,7 +738,7 @@ class ServersTest(test.TestCase): fake_migration_get) res = req.get_response(fakes.wsgi_app()) body = json.loads(res.body) - self.assertEqual(body['server']['status'], 'resize-confirm') + self.assertEqual(body['server']['status'], 'RESIZE-CONFIRM') def test_confirm_resize_server(self): req = self.webreq('/1/action', 'POST', dict(confirmResize=None)) -- cgit From 3987547248e07719dbc63752100b695ef0be1a9c Mon Sep 17 00:00:00 2001 From: John Tran Date: Tue, 29 Mar 2011 13:44:38 -0700 Subject: initial unit test for describe images --- nova/tests/test_cloud.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'nova') diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 00803d0ad..791498f89 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -216,6 +216,16 @@ class CloudTestCase(test.TestCase): db.service_destroy(self.context, comp1['id']) db.service_destroy(self.context, comp2['id']) + def test_describe_images(self): + def fake_detail(meh, context): + return [{'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, + 'type':'machine'}}] + + self.stubs.Set(local.LocalImageService, 'detail', fake_detail) + result = self.cloud.describe_images(self.context) + result = result['imagesSet'][0] + self.assertEqual(result['imageId'], 'ami-00000001') + def test_console_output(self): instance_type = FLAGS.default_instance_type max_count = 1 -- cgit From 5e6c69bc7a7e5ddaa1bf0fa83f64da116343dba8 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 29 Mar 2011 16:35:39 -0700 Subject: Narrowly focused bugfix - don't lose libvirt instances on host reboot or if they crash --- nova/compute/manager.py | 14 ++++++----- nova/virt/libvirt_conn.py | 59 +++++++++++++++++++++++++++++++++++++---------- 2 files changed, 55 insertions(+), 18 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index e0a5e2b3f..72f04ecb1 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1088,12 +1088,14 @@ class ComputeManager(manager.SchedulerDependentManager): 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']) + # NOTE(justinsb): We no longer auto-remove SHUTOFF instances + # It's quite hard to get them back when we do. + #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: diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index c144e827e..533ff9394 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -116,6 +116,8 @@ 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') +flags.DEFINE_bool('start_guests_on_host_boot', False, + 'Whether to restart guests when the host reboots') def get_connection(read_only): @@ -230,12 +232,14 @@ class LibvirtConnection(driver.ComputeDriver): {'name': instance['name'], 'state': state}) db.instance_set_state(ctxt, instance['id'], state) - if 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. - db.instance_destroy(ctxt, instance['id']) + # NOTE(justinsb): We no longer delete these instances, + # the user may want to power them back on + #if 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. + # db.instance_destroy(ctxt, instance['id']) if state != power_state.RUNNING: continue @@ -474,7 +478,7 @@ class LibvirtConnection(driver.ComputeDriver): xml = self.to_xml(instance) self.firewall_driver.setup_basic_filtering(instance) self.firewall_driver.prepare_instance_filter(instance) - self._conn.createXML(xml, 0) + self._create_new_domain(xml) self.firewall_driver.apply_instance_filter(instance) timer = utils.LoopingCall(f=None) @@ -522,7 +526,7 @@ class LibvirtConnection(driver.ComputeDriver): 'kernel_id': FLAGS.rescue_kernel_id, 'ramdisk_id': FLAGS.rescue_ramdisk_id} self._create_image(instance, xml, '.rescue', rescue_images) - self._conn.createXML(xml, 0) + self._create_new_domain(xml) timer = utils.LoopingCall(f=None) @@ -565,10 +569,15 @@ class LibvirtConnection(driver.ComputeDriver): self.firewall_driver.setup_basic_filtering(instance, network_info) self.firewall_driver.prepare_instance_filter(instance, network_info) self._create_image(instance, xml, network_info) - self._conn.createXML(xml, 0) + domain = self._create_new_domain(xml) LOG.debug(_("instance %s: is running"), instance['name']) self.firewall_driver.apply_instance_filter(instance) + if FLAGS.start_guests_on_host_boot: + LOG.debug(_("instance %s: setting autostart ON") % + instance['name']) + domain.setAutostart(1) + timer = utils.LoopingCall(f=None) def _wait_for_boot(): @@ -964,11 +973,19 @@ class LibvirtConnection(driver.ComputeDriver): return xml def get_info(self, instance_name): + # NOTE(justinsb): When libvirt isn't running / can't connect, we get: + # libvir: Remote error : unable to connect to + # '/var/run/libvirt/libvirt-sock', libvirtd may need to be started: + # No such file or directory try: virt_dom = self._conn.lookupByName(instance_name) - except: - raise exception.NotFound(_("Instance %s not found") - % instance_name) + except libvirt.libvirtError as e: + if e.get_error_code() == libvirt.VIR_ERR_UNKNOWN_HOST: + raise exception.NotFound(_("Instance %s not found") + % instance_name) + LOG.warning(_("Error from libvirt during lookup: %s") % e) + raise + (state, max_mem, mem, num_cpu, cpu_time) = virt_dom.info() return {'state': state, 'max_mem': max_mem, @@ -976,6 +993,24 @@ class LibvirtConnection(driver.ComputeDriver): 'num_cpu': num_cpu, 'cpu_time': cpu_time} + def _create_new_domain(self, xml, persistent=True, launch_flags=0): + # NOTE(justinsb): libvirt has two types of domain: + # * a transient domain disappears when the guest is shutdown + # or the host is rebooted. + # * a permanent domain is not automatically deleted + # NOTE(justinsb): Even for ephemeral instances, transient seems risky + + if persistent: + # To create a persistent domain, first define it, then launch it. + domain = self._conn.defineXML(xml) + + domain.createWithFlags(launch_flags) + else: + # createXML call creates a transient domain + domain = self._conn.createXML(xml, launch_flags) + + return domain + def get_diagnostics(self, instance_name): raise exception.ApiError(_("diagnostics are not supported " "for libvirt")) -- cgit From 399056300a0be228ec4c56587ec0d9c0d09d927c Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 29 Mar 2011 16:54:37 -0700 Subject: Fix unit test to reflect fact that instance is no longer deleted, just marked SHUTOFF --- nova/tests/test_compute.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index d1ef68de4..b7f08dfbe 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -654,4 +654,5 @@ class ComputeTestCase(test.TestCase): instances = db.instance_get_all(context.get_admin_context()) LOG.info(_("After force-killing instances: %s"), instances) - self.assertEqual(len(instances), 0) + self.assertEqual(len(instances), 1) + self.assertEqual(power_state.SHUTOFF, instances[0]['state']) -- cgit From d92322400c31f1cad933da5117b24376d60a5798 Mon Sep 17 00:00:00 2001 From: John Tran Date: Tue, 29 Mar 2011 17:07:59 -0700 Subject: adding unit tests for describe_images --- nova/tests/test_cloud.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 791498f89..5cb969979 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -41,6 +41,7 @@ from nova.compute import power_state from nova.api.ec2 import cloud from nova.api.ec2 import ec2utils from nova.image import local +from nova.exception import NotFound FLAGS = flags.FLAGS @@ -71,7 +72,8 @@ class CloudTestCase(test.TestCase): host = self.network.get_network_host(self.context.elevated()) def fake_show(meh, context, id): - return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}} + return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, + 'type': 'machine'}} self.stubs.Set(local.LocalImageService, 'show', fake_show) self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show) @@ -217,14 +219,33 @@ class CloudTestCase(test.TestCase): db.service_destroy(self.context, comp2['id']) def test_describe_images(self): + describe_images = self.cloud.describe_images + def fake_detail(meh, context): return [{'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, - 'type':'machine'}}] + 'type': 'machine'}}] + + def fake_show_none(meh, context, id): + raise NotFound self.stubs.Set(local.LocalImageService, 'detail', fake_detail) - result = self.cloud.describe_images(self.context) - result = result['imagesSet'][0] - self.assertEqual(result['imageId'], 'ami-00000001') + # list all + result1 = describe_images(self.context) + result1 = result1['imagesSet'][0] + self.assertEqual(result1['imageId'], 'ami-00000001') + # provided a valid image_id + result2 = describe_images(self.context, ['ami-00000001']) + self.assertEqual(1, len(result2['imagesSet'])) + # provide more than 1 valid image_id + result3 = describe_images(self.context, ['ami-00000001', + 'ami-00000002']) + self.assertEqual(2, len(result3['imagesSet'])) + # provide an non-existing image_id + self.stubs.UnsetAll() + self.stubs.Set(local.LocalImageService, 'show', fake_show_none) + self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show_none) + self.assertRaises(NotFound, describe_images, + self.context, ['ami-fake']) def test_console_output(self): instance_type = FLAGS.default_instance_type -- cgit From 7aa0102b2d451ffa87a095ac4471a65260aff3fe Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 29 Mar 2011 22:55:16 -0700 Subject: make sure that flag is there in compute api --- nova/compute/api.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 7977b07a2..7f358fdfd 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -39,6 +39,7 @@ from nova.db import base FLAGS = flags.FLAGS LOG = logging.getLogger('nova.compute.api') +flags.DECLARE('vncproxy_topic', 'nova.vnc') def generate_default_hostname(instance_id): -- cgit From 12505c6bae61e72b6c75ac647323c3ec15997df1 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Wed, 30 Mar 2011 10:05:06 -0700 Subject: Add XML namespaces to the OpenStack API --- nova/api/openstack/accounts.py | 5 ++- nova/api/openstack/backup_schedules.py | 4 +-- nova/api/openstack/common.py | 11 +++++++ nova/api/openstack/consoles.py | 4 +-- nova/api/openstack/extensions.py | 7 +++-- nova/api/openstack/flavors.py | 7 +++-- nova/api/openstack/image_metadata.py | 3 +- nova/api/openstack/images.py | 5 ++- nova/api/openstack/limits.py | 4 +-- nova/api/openstack/server_metadata.py | 3 +- nova/api/openstack/servers.py | 5 ++- nova/api/openstack/shared_ip_groups.py | 4 +-- nova/api/openstack/users.py | 3 +- nova/api/openstack/zones.py | 6 ++-- nova/tests/integrated/test_xml.py | 56 ++++++++++++++++++++++++++++++++++ nova/wsgi.py | 8 ++--- 16 files changed, 103 insertions(+), 32 deletions(-) create mode 100644 nova/tests/integrated/test_xml.py (limited to 'nova') diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py index 86066fa20..6e3763e47 100644 --- a/nova/api/openstack/accounts.py +++ b/nova/api/openstack/accounts.py @@ -13,15 +13,14 @@ # License for the specific language governing permissions and limitations # under the License. -import common import webob.exc from nova import exception from nova import flags from nova import log as logging -from nova import wsgi from nova.auth import manager +from nova.api.openstack import common from nova.api.openstack import faults FLAGS = flags.FLAGS @@ -35,7 +34,7 @@ def _translate_keys(account): manager=account.project_manager_id) -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): _serialization_metadata = { 'application/xml': { diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index f2d2d86e8..4bf744046 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -19,7 +19,7 @@ import time from webob import exc -from nova import wsgi +from nova.api.openstack import common from nova.api.openstack import faults import nova.image.service @@ -29,7 +29,7 @@ def _translate_keys(inst): return dict(backupSchedule=inst) -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): """ The backup schedule API controller for the Openstack API """ _serialization_metadata = { diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 75aeb0a5f..234f921ab 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -22,6 +22,7 @@ import webob from nova import exception from nova import flags from nova import log as logging +from nova import wsgi LOG = logging.getLogger('common') @@ -30,6 +31,10 @@ LOG = logging.getLogger('common') FLAGS = flags.FLAGS +XML_NS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0' +XML_NS_V11 = 'http://docs.openstack.org/compute/api/v1.1' + + def limited(items, request, max_limit=FLAGS.osapi_max_limit): """ Return a slice of items according to requested offset and limit. @@ -128,3 +133,9 @@ def get_id_from_href(href): except: LOG.debug(_("Error extracting id from href: %s") % href) raise webob.exc.HTTPBadRequest(_('could not parse id from href')) + + +class OpenstackController(wsgi.Controller): + def get_default_xmlns(self, req): + # Use V10 by default + return XML_NS_V10 diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index 8c291c2eb..1a77f25d7 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -19,7 +19,7 @@ from webob import exc from nova import console from nova import exception -from nova import wsgi +from nova.api.openstack import common from nova.api.openstack import faults @@ -43,7 +43,7 @@ def _translate_detail_keys(cons): return dict(console=info) -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): """The Consoles Controller for the Openstack API""" _serialization_metadata = { diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index fb1dccb28..75f369cac 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -28,6 +28,7 @@ from nova import exception from nova import flags from nova import log as logging from nova import wsgi +from nova.api.openstack import common from nova.api.openstack import faults @@ -115,7 +116,7 @@ class ExtensionDescriptor(object): return response_exts -class ActionExtensionController(wsgi.Controller): +class ActionExtensionController(common.OpenstackController): def __init__(self, application): @@ -136,7 +137,7 @@ class ActionExtensionController(wsgi.Controller): return res -class ResponseExtensionController(wsgi.Controller): +class ResponseExtensionController(common.OpenstackController): def __init__(self, application): self.application = application @@ -163,7 +164,7 @@ class ResponseExtensionController(wsgi.Controller): return res -class ExtensionController(wsgi.Controller): +class ExtensionController(common.OpenstackController): def __init__(self, extension_manager): self.extension_manager = extension_manager diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index 5b99b5a6f..40787bd17 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -19,11 +19,11 @@ import webob from nova import db from nova import exception -from nova import wsgi +from nova.api.openstack import common from nova.api.openstack import views -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): """Flavor controller for the OpenStack API.""" _serialization_metadata = { @@ -76,3 +76,6 @@ class ControllerV11(Controller): def _get_view_builder(self, req): base_url = req.application_url return views.flavors.ViewBuilderV11(base_url) + + def get_default_xmlns(self, req): + return common.XML_NS_V11 diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index c9d6ac532..e673e5f7b 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -20,13 +20,14 @@ from webob import exc from nova import flags from nova import utils from nova import wsgi +from nova.api.openstack import common from nova.api.openstack import faults FLAGS = flags.FLAGS -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): """The image metadata API controller for the Openstack API""" def __init__(self): diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index e77100d7b..5550ee532 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -32,7 +32,7 @@ LOG = log.getLogger('nova.api.openstack.images') FLAGS = flags.FLAGS -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): """Base `wsgi.Controller` for retrieving/displaying images.""" _serialization_metadata = { @@ -153,3 +153,6 @@ class ControllerV11(Controller): """Property to get the ViewBuilder class we need to use.""" base_url = request.application_url return images_view.ViewBuilderV11(base_url) + + def get_default_xmlns(self, req): + return common.XML_NS_V11 diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index efc7d193d..9877af191 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -31,8 +31,8 @@ from collections import defaultdict from webob.dec import wsgify from nova import wsgi +from nova.api.openstack import common from nova.api.openstack import faults -from nova.wsgi import Controller from nova.wsgi import Middleware @@ -43,7 +43,7 @@ PER_HOUR = 60 * 60 PER_DAY = 60 * 60 * 24 -class LimitsController(Controller): +class LimitsController(common.OpenstackController): """ Controller for accessing limits in the OpenStack API. """ diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index 45bbac99d..5c1390b9c 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -19,10 +19,11 @@ from webob import exc from nova import compute from nova import wsgi +from nova.api.openstack import common from nova.api.openstack import faults -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): """ The server metadata API controller for the Openstack API """ def __init__(self): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f7696d918..64090d830 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -44,7 +44,7 @@ LOG = logging.getLogger('server') FLAGS = flags.FLAGS -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): """ The Server API controller for the OpenStack API """ _serialization_metadata = { @@ -632,6 +632,9 @@ class ControllerV11(Controller): def _limit_items(self, items, req): return common.limited_by_marker(items, req) + def get_default_xmlns(self, req): + return common.XML_NS_V11 + class ServerCreateRequestXMLDeserializer(object): """ diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py index ee7991d7f..996db3648 100644 --- a/nova/api/openstack/shared_ip_groups.py +++ b/nova/api/openstack/shared_ip_groups.py @@ -17,7 +17,7 @@ from webob import exc -from nova import wsgi +from nova.api.openstack import common from nova.api.openstack import faults @@ -32,7 +32,7 @@ def _translate_detail_keys(inst): return dict(sharedIpGroups=inst) -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): """ The Shared IP Groups Controller for the Openstack API """ _serialization_metadata = { diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py index d3ab3d553..077ccfc79 100644 --- a/nova/api/openstack/users.py +++ b/nova/api/openstack/users.py @@ -18,7 +18,6 @@ from webob import exc from nova import exception from nova import flags from nova import log as logging -from nova import wsgi from nova.api.openstack import common from nova.api.openstack import faults from nova.auth import manager @@ -35,7 +34,7 @@ def _translate_keys(user): admin=user.admin) -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): _serialization_metadata = { 'application/xml': { diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 846cb48a1..227ffecdc 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -13,12 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. -import common - from nova import db from nova import flags from nova import log as logging -from nova import wsgi +from nova.api.openstack import common from nova.scheduler import api @@ -43,7 +41,7 @@ def _scrub_zone(zone): 'deleted', 'deleted_at', 'updated_at')) -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): _serialization_metadata = { 'application/xml': { diff --git a/nova/tests/integrated/test_xml.py b/nova/tests/integrated/test_xml.py new file mode 100644 index 000000000..8a9754777 --- /dev/null +++ b/nova/tests/integrated/test_xml.py @@ -0,0 +1,56 @@ +# 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. + +from nova import flags +from nova.log import logging +from nova.tests.integrated import integrated_helpers +from nova.api.openstack import common + + +LOG = logging.getLogger('nova.tests.integrated') + + +FLAGS = flags.FLAGS +FLAGS.verbose = True + + +class XmlTests(integrated_helpers._IntegratedTestBase): + """"Some basic XML sanity checks.""" + + def test_namespace_limits(self): + """/limits should have v1.0 namespace (hasn't changed in 1.1).""" + headers = {} + headers['Accept'] = 'application/xml' + + response = self.api.api_request('/limits', headers=headers) + data = response.read() + LOG.debug("data: %s" % data) + + prefix = ' Date: Wed, 30 Mar 2011 10:41:22 -0700 Subject: Some tests actually tested for the lack of a namespace :-) --- nova/api/openstack/extensions.py | 3 ++- nova/api/openstack/faults.py | 5 +++-- nova/api/openstack/images.py | 3 --- nova/tests/api/openstack/test_images.py | 16 +++++++++++----- 4 files changed, 16 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 75f369cac..7ea7afef6 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -156,7 +156,8 @@ class ResponseExtensionController(common.OpenstackController): body = res.body headers = res.headers except AttributeError: - body = self._serialize(res, content_type) + default_xmlns = None + body = self._serialize(res, content_type, default_xmlns) headers = {"Content-Type": content_type} res = webob.Response() res.body = body diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index 0e9c4b26f..a05e97021 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -20,10 +20,10 @@ import webob.dec import webob.exc from nova import wsgi +from nova.api.openstack import common class Fault(webob.exc.HTTPException): - """An RS API fault response.""" _fault_names = { @@ -57,7 +57,8 @@ class Fault(webob.exc.HTTPException): fault_data[fault_name]['retryAfter'] = retry # 'code' is an attribute on the fault tag itself metadata = {'application/xml': {'attributes': {fault_name: 'code'}}} - serializer = wsgi.Serializer(metadata) + default_xmlns = common.XML_NS_V10 + serializer = wsgi.Serializer(metadata, default_xmlns) content_type = req.best_match_content_type() self.wrapped_exc.body = serializer.serialize(fault_data, content_type) return self.wrapped_exc diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 5550ee532..77baf5947 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -13,8 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -import datetime - import webob.exc from nova import compute @@ -22,7 +20,6 @@ from nova import exception from nova import flags from nova import log from nova import utils -from nova import wsgi from nova.api.openstack import common from nova.api.openstack import faults from nova.api.openstack.views import images as images_view diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 57e447dce..ff3fe8976 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -146,7 +146,7 @@ class LocalImageServiceTest(_BaseImageServiceTests): for x in [1, 2, 3]: tempfile.mkstemp(prefix='ami-', dir=self.tempdir) # create some valid image directories names - for x in ["1485baed", "1a60f0ee", "3123a73d"]: + for x in ["1485baed", "1a60f0ee", "3123a73d"]: os.makedirs(os.path.join(self.tempdir, x)) found_image_ids = self.service._ids() self.assertEqual(True, isinstance(found_image_ids, list)) @@ -334,7 +334,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): name="public image" updated="%(expected_now)s" created="%(expected_now)s" - status="ACTIVE" /> + status="ACTIVE" + xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" /> """ % (locals())) self.assertEqual(expected_image.toxml(), actual_image.toxml()) @@ -353,7 +354,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): name="public image" updated="%(expected_now)s" created="%(expected_now)s" - status="ACTIVE"> + status="ACTIVE" + xmlns="http://docs.openstack.org/compute/api/v1.1"> + Image not found. @@ -422,8 +425,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): response = request.get_response(fakes.wsgi_app()) self.assertEqual(404, response.status_int) + # NOTE(justinsb): I believe this should still use the v1.0 XSD, + # because the element hasn't changed definition expected = minidom.parseString(""" - + Image not found. -- cgit From d2aa73efed6f4eb731c4fe8dca3f4ceb5b36caeb Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Wed, 30 Mar 2011 10:49:09 -0700 Subject: More tests that were checking for no-namespace --- nova/tests/api/openstack/test_faults.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_faults.py b/nova/tests/api/openstack/test_faults.py index 7667753f4..505e7e308 100644 --- a/nova/tests/api/openstack/test_faults.py +++ b/nova/tests/api/openstack/test_faults.py @@ -20,6 +20,7 @@ import webob.dec import webob.exc from nova import test +from nova.api.openstack import common from nova.api.openstack import faults @@ -30,8 +31,11 @@ class TestFaults(test.TestCase): f = faults.Fault(webob.exc.HTTPBadRequest(explanation='scram')) resp = req.get_response(f) - first_two_words = resp.body.strip().split()[:2] - self.assertEqual(first_two_words, ['']) + first_three_words = resp.body.strip().split()[:3] + self.assertEqual(first_three_words, + ['' % common.XML_NS_V10]) body_without_spaces = ''.join(resp.body.split()) self.assertTrue('scram' in body_without_spaces) @@ -41,8 +45,11 @@ class TestFaults(test.TestCase): headers={'Retry-After': 4}) f = faults.Fault(exc) resp = req.get_response(f) - first_two_words = resp.body.strip().split()[:2] - self.assertEqual(first_two_words, ['']) + first_three_words = resp.body.strip().split()[:3] + self.assertEqual(first_three_words, + ['' % common.XML_NS_V10]) body_sans_spaces = ''.join(resp.body.split()) self.assertTrue('sorry' in body_sans_spaces) self.assertTrue('4' in body_sans_spaces) -- cgit From 168fdb09f369287f3ec8c19ef73915280ff79d20 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Wed, 30 Mar 2011 11:15:16 -0700 Subject: Refixed unit test to check XML ns --- nova/tests/api/openstack/test_faults.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_faults.py b/nova/tests/api/openstack/test_faults.py index b3433f45f..4d86ffb26 100644 --- a/nova/tests/api/openstack/test_faults.py +++ b/nova/tests/api/openstack/test_faults.py @@ -48,10 +48,10 @@ class TestFaults(test.TestCase): response = request.get_response(fault) expected = self._prepare_xml(""" - + scram - """) + """ % common.XML_NS_V10) actual = self._prepare_xml(response.body) self.assertEqual(response.content_type, "application/xml") @@ -92,11 +92,11 @@ class TestFaults(test.TestCase): response = request.get_response(fault) expected = self._prepare_xml(""" - + sorry 4 - """) + """ % common.XML_NS_V10) actual = self._prepare_xml(response.body) self.assertEqual(expected, actual) -- cgit From 323eb60884cf8736448d997068d32f252d22e7f3 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 30 Mar 2011 12:05:25 -0700 Subject: Key type values in ec2_api off of container format --- nova/api/ec2/cloud.py | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 7ba8dfbea..1e6c4d1db 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -146,7 +146,7 @@ class CloudController(object): floating_ip = db.instance_get_floating_address(ctxt, instance_ref['id']) ec2_id = ec2utils.id_to_ec2_id(instance_ref['id']) - image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'machine') + image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'ami') data = { 'user-data': base64.b64decode(instance_ref['user_data']), 'meta-data': { @@ -176,7 +176,7 @@ class CloudController(object): for image_type in ['kernel', 'ramdisk']: if '%s_id' % image_type in instance_ref: ec2_id = self._image_ec2_id(instance_ref['%s_id' % image_type], - image_type) + self._image_type(image_type)) data['meta-data']['%s-id' % image_type] = ec2_id if False: # TODO(vish): store ancestor ids @@ -862,13 +862,27 @@ class CloudController(object): self.compute_api.update(context, instance_id=instance_id, **kwargs) return True - _type_prefix_map = {'machine': 'ami', - 'kernel': 'aki', - 'ramdisk': 'ari'} + @staticmethod + def _image_type(image_type): + """Converts to a three letter image type. - def _image_ec2_id(self, image_id, image_type='machine'): - prefix = self._type_prefix_map[image_type] - template = prefix + '-%08x' + aki, kernel => aki + ari, ramdisk => ari + anything else => ami + + """ + if image_type == 'kernel': + return 'aki' + if image_type == 'ramdisk': + return 'ari' + if image_type not in ['aki', 'ari']: + return 'ami' + return image_type + + @staticmethod + def _image_ec2_id(image_id, image_type='ami'): + """Returns image ec2_id using id and three letter type""" + template = image_type + '-%08x' return ec2utils.id_to_ec2_id(int(image_id), template=template) def _get_image(self, context, ec2_id): @@ -881,7 +895,7 @@ class CloudController(object): def _format_image(self, image): """Convert from format defined by BaseImageService to S3 format.""" i = {} - image_type = image['properties'].get('type') + image_type = self._image_type(image.get('container_format')) ec2_id = self._image_ec2_id(image.get('id'), image_type) name = image.get('name') if name: @@ -890,16 +904,19 @@ class CloudController(object): i['imageId'] = ec2_id kernel_id = image['properties'].get('kernel_id') if kernel_id: - i['kernelId'] = self._image_ec2_id(kernel_id, 'kernel') + i['kernelId'] = self._image_ec2_id(kernel_id, 'aki') ramdisk_id = image['properties'].get('ramdisk_id') if ramdisk_id: - i['ramdiskId'] = self._image_ec2_id(ramdisk_id, 'ramdisk') + i['ramdiskId'] = self._image_ec2_id(ramdisk_id, 'ari') 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 + display_mapping = {'aki': 'kernel', + 'ari': 'ramdisk', + 'ami': 'machine'} + i['type'] = display_mapping.get(image_type) i['isPublic'] = str(image['properties'].get('is_public', '')) == 'True' i['architecture'] = image['properties'].get('architecture') return i @@ -932,8 +949,9 @@ class CloudController(object): image_location = kwargs['name'] metadata = {'properties': {'image_location': image_location}} image = self.image_service.create(context, metadata) + image_type = self._image_type(image.get('container_format')) image_id = self._image_ec2_id(image['id'], - image['properties']['type']) + image_type) msg = _("Registered image %(image_location)s with" " id %(image_id)s") % locals() LOG.audit(msg, context=context) -- cgit From 1703592992ebdd5bbf19952f79f05022a4cdc849 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 30 Mar 2011 12:34:10 -0700 Subject: remove all references to image_type and change nova-manage upload to set container format more intelligently --- nova/api/openstack/servers.py | 2 +- nova/image/fake.py | 6 +++--- nova/image/s3.py | 1 - nova/tests/api/openstack/test_image_metadata.py | 2 -- nova/virt/libvirt_conn.py | 1 - 5 files changed, 4 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f7696d918..d640e8684 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -562,7 +562,7 @@ class Controller(wsgi.Controller): _("Cannot build from image %(image_id)s, status not active") % locals()) - if image_meta['properties']['disk_format'] != 'ami': + if image_meta.get('container_format') != 'ami': return None, None try: diff --git a/nova/image/fake.py b/nova/image/fake.py index 08302d6eb..d1c62757f 100644 --- a/nova/image/fake.py +++ b/nova/image/fake.py @@ -44,10 +44,10 @@ class FakeImageService(service.BaseImageService): 'created_at': timestamp, 'updated_at': timestamp, 'status': 'active', - 'type': 'machine', + 'container_format': 'ami', + 'disk_format': 'raw', 'properties': {'kernel_id': FLAGS.null_kernel, - 'ramdisk_id': FLAGS.null_kernel, - 'disk_format': 'ami'} + 'ramdisk_id': FLAGS.null_kernel} } self.create(None, image) super(FakeImageService, self).__init__() diff --git a/nova/image/s3.py b/nova/image/s3.py index ddec5f3aa..203bedc49 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -177,7 +177,6 @@ class S3ImageService(service.BaseImageService): properties['ramdisk_id'] = ec2utils.ec2_id_to_id(ramdisk_id) properties['is_public'] = False - properties['type'] = image_type metadata.update({'disk_format': image_format, 'container_format': image_format, 'status': 'queued', diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py index 9be753f84..7c3287006 100644 --- a/nova/tests/api/openstack/test_image_metadata.py +++ b/nova/tests/api/openstack/test_image_metadata.py @@ -45,7 +45,6 @@ class ImageMetaDataTest(unittest.TestCase): 'is_public': True, 'deleted_at': None, 'properties': { - 'type': 'ramdisk', 'key1': 'value1', 'key2': 'value2' }, @@ -62,7 +61,6 @@ class ImageMetaDataTest(unittest.TestCase): 'is_public': True, 'deleted_at': None, 'properties': { - 'type': 'ramdisk', 'key1': 'value1', 'key2': 'value2' }, diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index f34ea7225..adcb2ffa3 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -424,7 +424,6 @@ class LibvirtConnection(driver.ComputeDriver): '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', -- cgit From c29dc77c6f42c5a345ee6b510a373236d7988440 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 30 Mar 2011 12:46:22 -0700 Subject: fixed ordering and spacing --- nova/compute/api.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index a5f6afb02..996955fe3 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -37,8 +37,11 @@ from nova.compute import instance_types from nova.scheduler import api as scheduler_api from nova.db import base -FLAGS = flags.FLAGS + LOG = logging.getLogger('nova.compute.api') + + +FLAGS = flags.FLAGS flags.DECLARE('vncproxy_topic', 'nova.vnc') -- cgit From b54b6c200092054e38af1fa1e5885fe915e53149 Mon Sep 17 00:00:00 2001 From: John Tran Date: Wed, 30 Mar 2011 13:10:11 -0700 Subject: submitting a unit test for terminate_instance --- nova/tests/test_cloud.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'nova') diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 00803d0ad..be1939193 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -36,6 +36,7 @@ from nova import rpc from nova import service from nova import test from nova import utils +from nova import exception from nova.auth import manager from nova.compute import power_state from nova.api.ec2 import cloud @@ -310,6 +311,19 @@ class CloudTestCase(test.TestCase): LOG.debug(_("Terminating instance %s"), instance_id) rv = self.compute.terminate_instance(instance_id) + def test_terminate_instances(self): + inst1 = db.instance_create(self.context, {'reservation_id': 'a', + 'image_id': 1, + 'host': 'host1'}) + terminate_instances = self.cloud.terminate_instances + # valid instance_id + result = terminate_instances(self.context, ['i-00000001']) + self.assertTrue(result) + # non-existing instance_id + self.assertRaises(exception.InstanceNotFound, terminate_instances, + self.context, ['i-2']) + db.instance_destroy(self.context, inst1['id']) + def test_update_of_instance_display_fields(self): inst = db.instance_create(self.context, {}) ec2_id = ec2utils.id_to_ec2_id(inst['id']) -- cgit From b8173ba9fe80045665064208e66f46cca75fd3ba Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 30 Mar 2011 13:48:07 -0700 Subject: review cleanup --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 1e6c4d1db..3541e49ca 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -881,7 +881,7 @@ class CloudController(object): @staticmethod def _image_ec2_id(image_id, image_type='ami'): - """Returns image ec2_id using id and three letter type""" + """Returns image ec2_id using id and three letter type.""" template = image_type + '-%08x' return ec2utils.id_to_ec2_id(int(image_id), template=template) -- cgit From ce5ad4acbc81c8c444d7b6a02400d6bc0ef6b233 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Wed, 30 Mar 2011 20:33:56 -0700 Subject: Removed adminclient and referred to pypi nova_adminclient module --- nova/adminclient.py | 473 ---------------------------------------------------- 1 file changed, 473 deletions(-) delete mode 100644 nova/adminclient.py (limited to 'nova') diff --git a/nova/adminclient.py b/nova/adminclient.py deleted file mode 100644 index f570e12c2..000000000 --- a/nova/adminclient.py +++ /dev/null @@ -1,473 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# 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. -""" -Nova User API client library. -""" - -import base64 -import boto -import boto.exception -import httplib -import re -import string - -from boto.ec2.regioninfo import RegionInfo - - -DEFAULT_CLC_URL = 'http://127.0.0.1:8773' -DEFAULT_REGION = 'nova' - - -class UserInfo(object): - """ - Information about a Nova user, as parsed through SAX. - - **Fields Include** - - * username - * accesskey - * secretkey - * file (optional) containing zip of X509 cert & rc file - - """ - - def __init__(self, connection=None, username=None, endpoint=None): - self.connection = connection - self.username = username - self.endpoint = endpoint - - def __repr__(self): - return 'UserInfo:%s' % self.username - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == 'username': - self.username = str(value) - elif name == 'file': - self.file = base64.b64decode(str(value)) - elif name == 'accesskey': - self.accesskey = str(value) - elif name == 'secretkey': - self.secretkey = str(value) - - -class UserRole(object): - """ - Information about a Nova user's role, as parsed through SAX. - - **Fields include** - - * role - - """ - - def __init__(self, connection=None): - self.connection = connection - self.role = None - - def __repr__(self): - return 'UserRole:%s' % self.role - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == 'role': - self.role = value - else: - setattr(self, name, str(value)) - - -class ProjectInfo(object): - """ - Information about a Nova project, as parsed through SAX. - - **Fields include** - - * projectname - * description - * projectManagerId - * memberIds - - """ - - def __init__(self, connection=None): - self.connection = connection - self.projectname = None - self.description = None - self.projectManagerId = None - self.memberIds = [] - - def __repr__(self): - return 'ProjectInfo:%s' % self.projectname - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == 'projectname': - self.projectname = value - elif name == 'description': - self.description = value - elif name == 'projectManagerId': - self.projectManagerId = value - elif name == 'memberId': - self.memberIds.append(value) - else: - setattr(self, name, str(value)) - - -class ProjectMember(object): - """ - Information about a Nova project member, as parsed through SAX. - - **Fields include** - - * memberId - - """ - - def __init__(self, connection=None): - self.connection = connection - self.memberId = None - - def __repr__(self): - return 'ProjectMember:%s' % self.memberId - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == 'member': - self.memberId = value - else: - setattr(self, name, str(value)) - - -class HostInfo(object): - """ - Information about a Nova Host, as parsed through SAX. - - **Fields Include** - - * Hostname - * Compute service status - * Volume service status - * Instance count - * Volume count - """ - - def __init__(self, connection=None): - self.connection = connection - self.hostname = None - self.compute = None - self.volume = None - self.instance_count = 0 - self.volume_count = 0 - - def __repr__(self): - return 'Host:%s' % self.hostname - - # this is needed by the sax parser, so ignore the ugly name - def startElement(self, name, attrs, connection): - return None - - # this is needed by the sax parser, so ignore the ugly name - def endElement(self, name, value, connection): - fixed_name = string.lower(re.sub(r'([A-Z])', r'_\1', name)) - setattr(self, fixed_name, value) - - -class Vpn(object): - """ - Information about a Vpn, as parsed through SAX - - **Fields Include** - - * instance_id - * project_id - * public_ip - * public_port - * created_at - * internal_ip - * state - """ - - def __init__(self, connection=None): - self.connection = connection - self.instance_id = None - self.project_id = None - - def __repr__(self): - return 'Vpn:%s:%s' % (self.project_id, self.instance_id) - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - fixed_name = string.lower(re.sub(r'([A-Z])', r'_\1', name)) - setattr(self, fixed_name, value) - - -class InstanceType(object): - """ - Information about a Nova instance type, as parsed through SAX. - - **Fields include** - - * name - * vcpus - * disk_gb - * memory_mb - * flavor_id - - """ - - def __init__(self, connection=None): - self.connection = connection - self.name = None - self.vcpus = None - self.disk_gb = None - self.memory_mb = None - self.flavor_id = None - - def __repr__(self): - return 'InstanceType:%s' % self.name - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == "memoryMb": - self.memory_mb = str(value) - elif name == "flavorId": - self.flavor_id = str(value) - elif name == "diskGb": - self.disk_gb = str(value) - else: - setattr(self, name, str(value)) - - -class NovaAdminClient(object): - - def __init__( - self, - clc_url=DEFAULT_CLC_URL, - region=DEFAULT_REGION, - access_key=None, - secret_key=None, - **kwargs): - parts = self.split_clc_url(clc_url) - - self.clc_url = clc_url - self.region = region - self.access = access_key - self.secret = secret_key - self.apiconn = boto.connect_ec2(aws_access_key_id=access_key, - aws_secret_access_key=secret_key, - is_secure=parts['is_secure'], - region=RegionInfo(None, - region, - parts['ip']), - port=parts['port'], - path='/services/Admin', - **kwargs) - self.apiconn.APIVersion = 'nova' - - def connection_for(self, username, project, clc_url=None, region=None, - **kwargs): - """Returns a boto ec2 connection for the given username.""" - if not clc_url: - clc_url = self.clc_url - if not region: - region = self.region - parts = self.split_clc_url(clc_url) - user = self.get_user(username) - access_key = '%s:%s' % (user.accesskey, project) - return boto.connect_ec2(aws_access_key_id=access_key, - aws_secret_access_key=user.secretkey, - is_secure=parts['is_secure'], - region=RegionInfo(None, - self.region, - parts['ip']), - port=parts['port'], - path='/services/Cloud', - **kwargs) - - def split_clc_url(self, clc_url): - """Splits a cloud controller endpoint url.""" - parts = httplib.urlsplit(clc_url) - is_secure = parts.scheme == 'https' - ip, port = parts.netloc.split(':') - return {'ip': ip, 'port': int(port), 'is_secure': is_secure} - - def get_users(self): - """Grabs the list of all users.""" - return self.apiconn.get_list('DescribeUsers', {}, [('item', UserInfo)]) - - def get_user(self, name): - """Grab a single user by name.""" - user = self.apiconn.get_object('DescribeUser', - {'Name': name}, - UserInfo) - if user.username != None: - return user - - def has_user(self, username): - """Determine if user exists.""" - return self.get_user(username) != None - - def create_user(self, username): - """Creates a new user, returning the userinfo object with - access/secret.""" - return self.apiconn.get_object('RegisterUser', {'Name': username}, - UserInfo) - - def delete_user(self, username): - """Deletes a user.""" - return self.apiconn.get_object('DeregisterUser', {'Name': username}, - UserInfo) - - def get_roles(self, project_roles=True): - """Returns a list of available roles.""" - return self.apiconn.get_list('DescribeRoles', - {'ProjectRoles': project_roles}, - [('item', UserRole)]) - - def get_user_roles(self, user, project=None): - """Returns a list of roles for the given user. - - Omitting project will return any global roles that the user has. - Specifying project will return only project specific roles. - - """ - params = {'User': user} - if project: - params['Project'] = project - return self.apiconn.get_list('DescribeUserRoles', - params, - [('item', UserRole)]) - - def add_user_role(self, user, role, project=None): - """Add a role to a user either globally or for a specific project.""" - return self.modify_user_role(user, role, project=project, - operation='add') - - def remove_user_role(self, user, role, project=None): - """Remove a role from a user either globally or for a specific - project.""" - return self.modify_user_role(user, role, project=project, - operation='remove') - - def modify_user_role(self, user, role, project=None, operation='add', - **kwargs): - """Add or remove a role for a user and project.""" - params = {'User': user, - 'Role': role, - 'Project': project, - 'Operation': operation} - return self.apiconn.get_status('ModifyUserRole', params) - - def get_projects(self, user=None): - """Returns a list of all projects.""" - if user: - params = {'User': user} - else: - params = {} - return self.apiconn.get_list('DescribeProjects', - params, - [('item', ProjectInfo)]) - - def get_project(self, name): - """Returns a single project with the specified name.""" - project = self.apiconn.get_object('DescribeProject', - {'Name': name}, - ProjectInfo) - - if project.projectname != None: - return project - - def create_project(self, projectname, manager_user, description=None, - member_users=None): - """Creates a new project.""" - params = {'Name': projectname, - 'ManagerUser': manager_user, - 'Description': description, - 'MemberUsers': member_users} - return self.apiconn.get_object('RegisterProject', params, ProjectInfo) - - def modify_project(self, projectname, manager_user=None, description=None): - """Modifies an existing project.""" - params = {'Name': projectname, - 'ManagerUser': manager_user, - 'Description': description} - return self.apiconn.get_status('ModifyProject', params) - - def delete_project(self, projectname): - """Permanently deletes the specified project.""" - return self.apiconn.get_object('DeregisterProject', - {'Name': projectname}, - ProjectInfo) - - def get_project_members(self, name): - """Returns a list of members of a project.""" - return self.apiconn.get_list('DescribeProjectMembers', - {'Name': name}, - [('item', ProjectMember)]) - - def add_project_member(self, user, project): - """Adds a user to a project.""" - return self.modify_project_member(user, project, operation='add') - - def remove_project_member(self, user, project): - """Removes a user from a project.""" - return self.modify_project_member(user, project, operation='remove') - - def modify_project_member(self, user, project, operation='add'): - """Adds or removes a user from a project.""" - params = {'User': user, - 'Project': project, - 'Operation': operation} - return self.apiconn.get_status('ModifyProjectMember', params) - - def get_zip(self, user, project): - """Returns the content of a zip file containing novarc and access - credentials.""" - params = {'Name': user, 'Project': project} - zip = self.apiconn.get_object('GenerateX509ForUser', params, UserInfo) - return zip.file - - def start_vpn(self, project): - """ - Starts the vpn for a user - """ - return self.apiconn.get_object('StartVpn', {'Project': project}, Vpn) - - def get_vpns(self): - """Return a list of vpn with project name""" - return self.apiconn.get_list('DescribeVpns', {}, [('item', Vpn)]) - - def get_hosts(self): - return self.apiconn.get_list('DescribeHosts', {}, [('item', HostInfo)]) - - def get_instance_types(self): - """Grabs the list of all users.""" - return self.apiconn.get_list('DescribeInstanceTypes', {}, - [('item', InstanceType)]) -- cgit From 7f98416fdba59c4305f88a30a457bbf6c68ef372 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 31 Mar 2011 09:20:47 -0400 Subject: Limit image metadata to the configured metadata quota for a project. --- nova/api/openstack/image_metadata.py | 12 ++++++++ nova/tests/api/openstack/test_image_metadata.py | 39 +++++++++++++++++++++++++ 2 files changed, 51 insertions(+) (limited to 'nova') diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index c9d6ac532..76abdaa7e 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -18,6 +18,7 @@ from webob import exc from nova import flags +from nova import quota from nova import utils from nova import wsgi from nova.api.openstack import faults @@ -39,6 +40,15 @@ class Controller(wsgi.Controller): metadata = image.get('properties', {}) return metadata + def _check_quota_limit(self, context, metadata): + if metadata is None: + return + num_metadata = len(metadata) + quota_metadata = quota.allowed_metadata_items(context, num_metadata) + if quota_metadata < num_metadata: + expl = ("Image metadata limit exceeded") + raise exc.HTTPBadRequest(explanation=expl) + def index(self, req, image_id): """Returns the list of metadata for a given instance""" context = req.environ['nova.context'] @@ -61,6 +71,7 @@ class Controller(wsgi.Controller): if 'metadata' in body: for key, value in body['metadata'].iteritems(): metadata[key] = value + self._check_quota_limit(context, metadata) img['properties'] = metadata self.image_service.update(context, image_id, img, None) return dict(metadata=metadata) @@ -77,6 +88,7 @@ class Controller(wsgi.Controller): img = self.image_service.show(context, image_id) metadata = self._get_metadata(context, image_id, img) metadata[id] = body[id] + self._check_quota_limit(context, metadata) img['properties'] = metadata self.image_service.update(context, image_id, img, None) diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py index 9be753f84..51f64680b 100644 --- a/nova/tests/api/openstack/test_image_metadata.py +++ b/nova/tests/api/openstack/test_image_metadata.py @@ -67,6 +67,19 @@ class ImageMetaDataTest(unittest.TestCase): 'key2': 'value2' }, 'size': 5882349}, + {'status': 'active', + 'name': 'image3', + 'deleted': False, + 'container_format': None, + 'created_at': '2011-03-22T17:40:15', + 'disk_format': None, + 'updated_at': '2011-03-22T17:40:15', + 'id': '3', + 'location': 'file:///var/lib/glance/images/2', + 'is_public': True, + 'deleted_at': None, + 'properties': {}, + 'size': 5882349}, ] def setUp(self): @@ -77,6 +90,10 @@ class ImageMetaDataTest(unittest.TestCase): fakes.FakeAuthManager.auth_data = {} fakes.FakeAuthDatabase.data = {} fakes.stub_out_auth(self.stubs) + # NOTE(dprince) max out properties/metadata in image 3 for testing + img3 = self.IMAGE_FIXTURES[2] + for num in range(FLAGS.quota_metadata_items): + img3['properties']['key%i' % num] = "blah" fakes.stub_out_glance(self.stubs, self.IMAGE_FIXTURES) def tearDown(self): @@ -164,3 +181,25 @@ class ImageMetaDataTest(unittest.TestCase): req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) + + def test_too_many_metadata_items_on_create(self): + data = {"metadata": {}} + for num in range(FLAGS.quota_metadata_items + 1): + data['metadata']['key%i' % num] = "blah" + json_string = str(data).replace("\'", "\"") + req = webob.Request.blank('/v1.1/images/2/meta') + req.environ['api.version'] = '1.1' + req.method = 'POST' + req.body = json_string + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) + + def test_too_many_metadata_items_on_put(self): + req = webob.Request.blank('/v1.1/images/3/meta/blah') + req.environ['api.version'] = '1.1' + req.method = 'PUT' + req.body = '{"blah": "blah"}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) -- cgit From 1654afaabba498ab690c07ccc6de858783dc1742 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 31 Mar 2011 12:38:05 -0700 Subject: makes sure s3 filtering works even without metadata set properly --- nova/image/s3.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/image/s3.py b/nova/image/s3.py index 203bedc49..def49f682 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -80,9 +80,10 @@ class S3ImageService(service.BaseImageService): @classmethod def _is_visible(cls, context, image): + return (context.is_admin - or context.project_id == image['properties']['owner_id'] - or image['properties']['is_public'] == 'True') + or context.project_id == image['properties'].get('owner_id') + or str(image['properties'].get('is_public')) == 'True') @classmethod def _filter(cls, context, images): -- cgit From 032acaab1fd9a3823e203ddaf9c50857acd25ac7 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 31 Mar 2011 15:32:31 -0500 Subject: Don't include first 4 chars of instance name in adminPass --- nova/api/openstack/servers.py | 3 +-- nova/tests/api/openstack/test_servers.py | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f7696d918..d47ea5788 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -180,8 +180,7 @@ class Controller(wsgi.Controller): builder = self._get_view_builder(req) server = builder.build(inst, is_detail=True) - password = "%s%s" % (server['server']['name'][:4], - utils.generate_password(12)) + password = utils.generate_password(16) server['server']['adminPass'] = password self.compute_api.set_admin_password(context, server['server']['id'], password) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 130b8c5d5..5fbc9837b 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -377,7 +377,6 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) server = json.loads(res.body)['server'] - self.assertEqual('serv', server['adminPass'][:4]) self.assertEqual(16, len(server['adminPass'])) self.assertEqual('server_test', server['name']) self.assertEqual(1, server['id']) @@ -486,7 +485,6 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) server = json.loads(res.body)['server'] - self.assertEqual('serv', server['adminPass'][:4]) self.assertEqual(16, len(server['adminPass'])) self.assertEqual('server_test', server['name']) self.assertEqual(1, server['id']) @@ -1426,7 +1424,7 @@ class TestServerInstanceCreation(test.TestCase): self.assertEquals(response.status_int, 200) response = json.loads(response.body) self.assertTrue('adminPass' in response['server']) - self.assertTrue(response['server']['adminPass'].startswith('fake')) + self.assertEqual(16, len(response['server']['adminPass'])) def test_create_instance_admin_pass_xml(self): request, response, dummy = \ @@ -1435,7 +1433,7 @@ class TestServerInstanceCreation(test.TestCase): dom = minidom.parseString(response.body) server = dom.childNodes[0] self.assertEquals(server.nodeName, 'server') - self.assertTrue(server.getAttribute('adminPass').startswith('fake')) + self.assertEqual(16, len(server.getAttribute('adminPass'))) class TestGetKernelRamdiskFromImage(test.TestCase): -- cgit From ec30c42b3b4532743c8353c5e9fa04ae00803db3 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Mon, 4 Apr 2011 18:31:35 +0400 Subject: network injection check fixed --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index f34ea7225..c952f6f47 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -842,7 +842,7 @@ class LibvirtConnection(driver.ComputeDriver): for (network_ref, mapping) in network_info: ifc_num += 1 - if not 'injected' in network_ref: + if not network_ref.injected: continue have_injected_networks = True -- cgit From bd64c4f6bebb50528b87bf6e3f64d7d1cba053df Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 4 Apr 2011 10:59:44 -0400 Subject: Fixes error which occurs when no name is specified for an image. --- nova/api/openstack/views/images.py | 2 +- nova/tests/api/openstack/test_images.py | 57 +++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 3807fa95f..d8578ebdd 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -61,7 +61,7 @@ class ViewBuilder(object): image = { "id": image_obj["id"], - "name": image_obj["name"], + "name": image_obj.get("name"), } if "instance_id" in properties: diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 57e447dce..69cc3116d 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -263,7 +263,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): {'id': 124, 'name': 'queued backup'}, {'id': 125, 'name': 'saving backup'}, {'id': 126, 'name': 'active backup'}, - {'id': 127, 'name': 'killed backup'}] + {'id': 127, 'name': 'killed backup'}, + {'id': 129, 'name': None}] self.assertDictListMatch(response_list, expected) @@ -339,6 +340,24 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertEqual(expected_image.toxml(), actual_image.toxml()) + def test_get_image_xml_no_name(self): + request = webob.Request.blank('/v1.0/images/129') + request.accept = "application/xml" + response = request.get_response(fakes.wsgi_app()) + + actual_image = minidom.parseString(response.body.replace(" ", "")) + + expected_now = self.NOW_API_FORMAT + expected_image = minidom.parseString(""" + + """ % (locals())) + + self.assertEqual(expected_image.toxml(), actual_image.toxml()) + def test_get_image_v1_1_xml(self): request = webob.Request.blank('/v1.1/images/123') request.accept = "application/xml" @@ -516,6 +535,13 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'FAILED', + }, + { + 'id': 129, + 'name': None, + 'updated': self.NOW_API_FORMAT, + 'created': self.NOW_API_FORMAT, + 'status': 'ACTIVE', }] self.assertDictListMatch(expected, response_list) @@ -635,7 +661,29 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): "type": "application/xml", "href": "http://localhost/v1.1/images/127", }], - }] + }, + { + 'id': 129, + 'name': None, + 'updated': self.NOW_API_FORMAT, + 'created': self.NOW_API_FORMAT, + 'status': 'ACTIVE', + "links": [{ + "rel": "self", + "href": "http://localhost/v1.1/images/129", + }, + { + "rel": "bookmark", + "type": "application/json", + "href": "http://localhost/v1.1/images/129", + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": "http://localhost/v1.1/images/129", + }], + }, + ] self.assertDictListMatch(expected, response_list) @@ -694,4 +742,9 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): status='active', properties=other_backup_properties) image_id += 1 + # Image without a name + add_fixture(id=image_id, is_public=True, status='active', + properties={}) + image_id += 1 + return fixtures -- cgit From 59d46ada05f47bf477427b932a47c1cf1d91811e Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 4 Apr 2011 11:19:20 -0400 Subject: Ensure no errors for improper responses from image service. --- nova/api/openstack/views/images.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index d8578ebdd..16195b050 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -60,7 +60,7 @@ class ViewBuilder(object): self._format_status(image_obj) image = { - "id": image_obj["id"], + "id": image_obj.get("id"), "name": image_obj.get("name"), } @@ -72,9 +72,9 @@ class ViewBuilder(object): if detail: image.update({ - "created": image_obj["created_at"], - "updated": image_obj["updated_at"], - "status": image_obj["status"], + "created": image_obj.get("created_at"), + "updated": image_obj.get("updated_at"), + "status": image_obj.get("status"), }) if image["status"] == "SAVING": -- cgit From fc53de592fb903f8a7e3741fa98d03140aca9066 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Mon, 4 Apr 2011 09:45:26 -0700 Subject: corrected capitalization of openstack api status and added tests --- nova/api/openstack/views/servers.py | 24 ++++++++++++------------ nova/tests/api/openstack/test_servers.py | 2 ++ 2 files changed, 14 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 6b471a0f4..d24c025be 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -57,27 +57,27 @@ class ViewBuilder(object): def _build_detail(self, inst): """Returns a detailed model of a server.""" power_mapping = { - None: 'build', - power_state.NOSTATE: 'build', - power_state.RUNNING: 'active', - power_state.BLOCKED: 'active', - power_state.SUSPENDED: 'suspended', - power_state.PAUSED: 'paused', - power_state.SHUTDOWN: 'active', - power_state.SHUTOFF: 'active', - power_state.CRASHED: 'error', - power_state.FAILED: 'error'} + None: 'BUILD', + power_state.NOSTATE: 'BUILD', + power_state.RUNNING: 'ACTIVE', + power_state.BLOCKED: 'ACTIVE', + power_state.SUSPENDED: 'SUSPENDED', + power_state.PAUSED: 'PAUSED', + power_state.SHUTDOWN: 'ACTIVE', + power_state.SHUTOFF: 'ACTIVE', + power_state.CRASHED: 'ERROR', + power_state.FAILED: 'ERROR'} inst_dict = { 'id': int(inst['id']), 'name': inst['display_name'], 'addresses': self.addresses_builder.build(inst), - 'status': power_mapping[inst.get('state')].upper()} + 'status': power_mapping[inst.get('state')]} ctxt = nova.context.get_admin_context() compute_api = nova.compute.API() if compute_api.has_finished_migration(ctxt, inst['id']): - inst_dict['status'] = 'resize-confirm'.upper() + inst_dict['status'] = 'RESIZE-CONFIRM' # Return the metadata as a dictionary metadata = {} diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index d32e8eea8..cf55c8383 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -631,6 +631,7 @@ class ServersTest(test.TestCase): self.assertEqual(s['name'], 'server%d' % i) self.assertEqual(s['imageId'], '10') self.assertEqual(s['flavorId'], '1') + self.assertEqual(s['status'], 'BUILD') self.assertEqual(s['metadata']['seq'], i) def test_get_all_server_details_v1_1(self): @@ -644,6 +645,7 @@ class ServersTest(test.TestCase): self.assertEqual(s['name'], 'server%d' % i) self.assertEqual(s['imageRef'], 'http://localhost/v1.1/images/10') self.assertEqual(s['flavorRef'], 'http://localhost/v1.1/flavors/1') + self.assertEqual(s['status'], 'BUILD') self.assertEqual(s['metadata']['seq'], i) def test_get_all_server_details_with_host(self): -- cgit From 95fe2026e869e2da29196f8bf3e48ae2a2560e46 Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Tue, 5 Apr 2011 02:04:58 +0900 Subject: Moved 'name' property from to , corrected and fixes bug # 750482. --- nova/api/ec2/cloud.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 425784e8a..ecf144452 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -886,10 +886,7 @@ class CloudController(object): image_type = image['properties'].get('type') ec2_id = self._image_ec2_id(image.get('id'), image_type) name = image.get('name') - if name: - i['imageId'] = "%s (%s)" % (ec2_id, name) - else: - i['imageId'] = ec2_id + i['imageId'] = ec2_id kernel_id = image['properties'].get('kernel_id') if kernel_id: i['kernelId'] = self._image_ec2_id(kernel_id, 'kernel') @@ -897,11 +894,15 @@ class CloudController(object): if ramdisk_id: i['ramdiskId'] = self._image_ec2_id(ramdisk_id, 'ramdisk') i['imageOwnerId'] = image['properties'].get('owner_id') - i['imageLocation'] = image['properties'].get('image_location') + if name: + i['imageLocation'] = "%s (%s)" % (image['properties']. + get('image_location'), name) + else: + i['imageLocation'] = image['properties'].get('image_location') i['imageState'] = image['properties'].get('image_state') - i['displayName'] = image.get('name') + i['displayName'] = name i['description'] = image.get('description') - i['type'] = image_type + i['imageType'] = image_type i['isPublic'] = str(image['properties'].get('is_public', '')) == 'True' i['architecture'] = image['properties'].get('architecture') return i -- cgit From 350aaa819c8f97e0bcbd9e4d0f6f0da41784b630 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Mon, 4 Apr 2011 22:39:39 +0400 Subject: working with network_ref like with mapping --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index c952f6f47..babbc610d 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -842,7 +842,7 @@ class LibvirtConnection(driver.ComputeDriver): for (network_ref, mapping) in network_info: ifc_num += 1 - if not network_ref.injected: + if not network_ref['injected']: continue have_injected_networks = True -- cgit From 38b4cd9e68d7e1c262b08474b277573440ec3e87 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 4 Apr 2011 16:17:04 -0400 Subject: Refactor so that instances.instance_type is now instances.instance_type_id. Update the Openstack API to return the correct flavor_id. --- nova/api/ec2/cloud.py | 7 +- nova/api/openstack/servers.py | 6 +- nova/api/openstack/views/servers.py | 4 +- nova/compute/api.py | 18 +++-- nova/compute/instance_types.py | 46 ++++++------ nova/db/api.py | 5 ++ nova/db/sqlalchemy/api.py | 22 ++++++ .../014_add_instance_type_id_to_instances.py | 86 ++++++++++++++++++++++ nova/db/sqlalchemy/models.py | 11 ++- nova/tests/api/openstack/test_servers.py | 24 +++--- nova/tests/db/fakes.py | 22 ++++-- nova/tests/test_compute.py | 18 +++-- nova/tests/test_console.py | 2 +- nova/tests/test_instance_types.py | 6 +- nova/tests/test_quota.py | 17 +++-- nova/tests/test_scheduler.py | 2 +- nova/tests/test_virt.py | 2 +- nova/tests/test_volume.py | 2 +- nova/tests/test_xenapi.py | 12 +-- nova/virt/libvirt_conn.py | 22 +++--- nova/virt/xenapi/vm_utils.py | 8 +- nova/virt/xenapi/vmops.py | 8 +- 22 files changed, 255 insertions(+), 95 deletions(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 7ba8dfbea..cd1195502 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -722,7 +722,10 @@ class CloudController(object): instance['project_id'], instance['host']) i['productCodesSet'] = self._convert_to_set([], 'product_codes') - i['instanceType'] = instance['instance_type'] + if instance['instance_type']: + i['instanceType'] = instance['instance_type'].get('name', None) + else: + i['instanceType'] = None i['launchTime'] = instance['created_at'] i['amiLaunchIndex'] = instance['launch_index'] i['displayName'] = instance['display_name'] @@ -805,7 +808,7 @@ class CloudController(object): ramdisk = self._get_image(context, kwargs['ramdisk_id']) kwargs['ramdisk_id'] = ramdisk['id'] instances = self.compute_api.create(context, - instance_type=instance_types.get_by_type( + instance_type=instance_types.get_instance_type_by_name( kwargs.get('instance_type', None)), image_id=self._get_image(context, kwargs['image_id'])['id'], min_count=int(kwargs.get('min_count', max_count)), diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 4e2ebb2bd..327911f6f 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -160,9 +160,11 @@ class Controller(wsgi.Controller): name = name.strip() try: + inst_type = \ + instance_types.get_instance_type_by_flavor_id(flavor_id) (inst,) = self.compute_api.create( context, - instance_types.get_by_flavor_id(flavor_id), + inst_type, image_id, kernel_id=kernel_id, ramdisk_id=ramdisk_id, @@ -175,7 +177,7 @@ class Controller(wsgi.Controller): except quota.QuotaError as error: self._handle_quota_error(error) - inst['instance_type'] = flavor_id + inst['instance_type'] = inst_type inst['image_id'] = requested_image_id builder = self._get_view_builder(req) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 4e7f62eb3..2f400eef6 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -115,7 +115,7 @@ class ViewBuilderV10(ViewBuilder): def _build_flavor(self, response, inst): if 'instance_type' in dict(inst): - response['flavorId'] = inst['instance_type'] + response['flavorId'] = inst['instance_type']['flavorid'] class ViewBuilderV11(ViewBuilder): @@ -134,7 +134,7 @@ class ViewBuilderV11(ViewBuilder): def _build_flavor(self, response, inst): if "instance_type" in dict(inst): - flavor_id = inst["instance_type"] + flavor_id = inst["instance_type"]['flavorid'] flavor_ref = self.flavor_builder.generate_href(flavor_id) response["flavorRef"] = flavor_ref diff --git a/nova/compute/api.py b/nova/compute/api.py index 1dbd73f8f..363d61f29 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -110,8 +110,11 @@ class API(base.Base): """Create the number of instances requested if quota and other arguments check out ok.""" - type_data = instance_types.get_instance_type(instance_type) - num_instances = quota.allowed_instances(context, max_count, type_data) + if not instance_type: + instance_type = instance_types.get_default_instance_type() + + num_instances = quota.allowed_instances(context, max_count, + instance_type) if num_instances < min_count: pid = context.project_id LOG.warn(_("Quota exceeeded for %(pid)s," @@ -197,10 +200,10 @@ class API(base.Base): 'user_id': context.user_id, 'project_id': context.project_id, 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), - 'instance_type': instance_type, - 'memory_mb': type_data['memory_mb'], - 'vcpus': type_data['vcpus'], - 'local_gb': type_data['local_gb'], + 'instance_type_id': instance_type['id'], + 'memory_mb': instance_type['memory_mb'], + 'vcpus': instance_type['vcpus'], + 'local_gb': instance_type['local_gb'], 'display_name': display_name, 'display_description': display_description, 'user_data': user_data or '', @@ -517,8 +520,7 @@ class API(base.Base): def resize(self, context, instance_id, flavor_id): """Resize a running instance.""" instance = self.db.instance_get(context, instance_id) - current_instance_type = self.db.instance_type_get_by_name( - context, instance['instance_type']) + current_instance_type = instance['instance_type'] new_instance_type = self.db.instance_type_get_by_flavor_id( context, flavor_id) diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index fa02a5dfa..5b1d92e29 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -101,41 +101,43 @@ def get_all_flavors(): return get_all_types(context.get_admin_context()) -def get_instance_type(name): - """Retrieves single instance type by name""" - if name is None: - return FLAGS.default_instance_type +def get_default_instance_type(): + name = FLAGS.default_instance_type try: - ctxt = context.get_admin_context() - inst_type = db.instance_type_get_by_name(ctxt, name) - return inst_type + return get_instance_type_by_name(name) except exception.DBError: raise exception.ApiError(_("Unknown instance type: %s" % name)) -def get_by_type(instance_type): - """retrieve instance type name""" - if instance_type is None: - return FLAGS.default_instance_type +def get_instance_type(id): + """Retrieves single instance type by id""" + if id is None: + return get_default_instance_type() + try: + ctxt = context.get_admin_context() + return db.instance_type_get_by_id(ctxt, id) + except exception.DBError: + raise exception.ApiError(_("Unknown instance type: %s" % name)) + +def get_instance_type_by_name(name): + """Retrieves single instance type by name""" + if name is None: + return get_default_instance_type() try: ctxt = context.get_admin_context() - inst_type = db.instance_type_get_by_name(ctxt, instance_type) - return inst_type['name'] - except exception.DBError, e: - LOG.exception(_('DB error: %s' % e)) - raise exception.ApiError(_("Unknown instance type: %s" %\ - instance_type)) + return db.instance_type_get_by_name(ctxt, name) + except exception.DBError: + raise exception.ApiError(_("Unknown instance type: %s" % name)) -def get_by_flavor_id(flavor_id): - """retrieve instance type's name by flavor_id""" +def get_instance_type_by_flavor_id(flavor_id): + """retrieve instance type by flavor_id""" if flavor_id is None: - return FLAGS.default_instance_type + return get_default_instance_type() try: ctxt = context.get_admin_context() - flavor = db.instance_type_get_by_flavor_id(ctxt, flavor_id) - return flavor['name'] + return db.instance_type_get_by_flavor_id(ctxt, flavor_id) except exception.DBError, e: LOG.exception(_('DB error: %s' % e)) raise exception.ApiError(_("Unknown flavor: %s" % flavor_id)) diff --git a/nova/db/api.py b/nova/db/api.py index fd3c63b76..63901e94d 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1124,6 +1124,11 @@ def instance_type_get_all(context, inactive=False): return IMPL.instance_type_get_all(context, inactive) +def instance_type_get_by_id(context, id): + """Get instance type by id""" + return IMPL.instance_type_get_by_id(context, id) + + def instance_type_get_by_name(context, name): """Get instance type by name""" return IMPL.instance_type_get_by_name(context, name) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index b2a13a01b..9f600b236 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -829,6 +829,7 @@ def instance_get(context, instance_id, session=None): options(joinedload('volumes')).\ options(joinedload_all('fixed_ip.network')).\ options(joinedload('metadata')).\ + options(joinedload('instance_type')).\ filter_by(id=instance_id).\ filter_by(deleted=can_read_deleted(context)).\ first() @@ -838,6 +839,7 @@ def instance_get(context, instance_id, session=None): options(joinedload_all('security_groups.rules')).\ options(joinedload('volumes')).\ options(joinedload('metadata')).\ + options(joinedload('instance_type')).\ filter_by(project_id=context.project_id).\ filter_by(id=instance_id).\ filter_by(deleted=False).\ @@ -857,6 +859,7 @@ def instance_get_all(context): options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ip.network')).\ + options(joinedload('instance_type')).\ filter_by(deleted=can_read_deleted(context)).\ all() @@ -868,6 +871,7 @@ def instance_get_all_by_user(context, user_id): options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ip.network')).\ + options(joinedload('instance_type')).\ filter_by(deleted=can_read_deleted(context)).\ filter_by(user_id=user_id).\ all() @@ -880,6 +884,7 @@ def instance_get_all_by_host(context, host): options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ip.network')).\ + options(joinedload('instance_type')).\ filter_by(host=host).\ filter_by(deleted=can_read_deleted(context)).\ all() @@ -894,6 +899,7 @@ def instance_get_all_by_project(context, project_id): options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ip.network')).\ + options(joinedload('instance_type')).\ filter_by(project_id=project_id).\ filter_by(deleted=can_read_deleted(context)).\ all() @@ -908,6 +914,7 @@ def instance_get_all_by_reservation(context, reservation_id): options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ip.network')).\ + options(joinedload('instance_type')).\ filter_by(reservation_id=reservation_id).\ filter_by(deleted=can_read_deleted(context)).\ all() @@ -916,6 +923,7 @@ def instance_get_all_by_reservation(context, reservation_id): options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ip.network')).\ + options(joinedload('instance_type')).\ filter_by(project_id=context.project_id).\ filter_by(reservation_id=reservation_id).\ filter_by(deleted=False).\ @@ -928,6 +936,7 @@ def instance_get_project_vpn(context, project_id): return session.query(models.Instance).\ options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ + options(joinedload('instance_type')).\ filter_by(project_id=project_id).\ filter_by(image_id=FLAGS.vpn_image_id).\ filter_by(deleted=can_read_deleted(context)).\ @@ -2368,6 +2377,19 @@ def instance_type_get_all(context, inactive=False): raise exception.NotFound +@require_context +def instance_type_get_by_id(context, id): + """Returns a dict describing specific instance_type""" + session = get_session() + inst_type = session.query(models.InstanceTypes).\ + filter_by(id=id).\ + first() + if not inst_type: + raise exception.NotFound(_("No instance type with id %s") % id) + else: + return dict(inst_type) + + @require_context def instance_type_get_by_name(context, name): """Returns a dict describing specific instance_type""" diff --git a/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py new file mode 100644 index 000000000..813e57e10 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py @@ -0,0 +1,86 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 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. + +from sqlalchemy import * +from sqlalchemy.sql import text +from migrate import * + +#from nova import log as logging + + +meta = MetaData() + + +c_instance_type = Column('instance_type', + String(length=255, convert_unicode=False, + assert_unicode=None, unicode_error=None, + _warn_on_bytestring=False), + nullable=True) + +c_instance_type_id = Column('instance_type_id', + String(length=255, convert_unicode=False, + assert_unicode=None, unicode_error=None, + _warn_on_bytestring=False), + nullable=True) + +instance_types = Table('instance_types', meta, + Column('id', Integer(), primary_key=True, nullable=False), + Column('name', + String(length=255, convert_unicode=False, assert_unicode=None, + unicode_error=None, _warn_on_bytestring=False), + unique=True)) + + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; + # bind migrate_engine to your metadata + meta.bind = migrate_engine + + instances = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + instances.create_column(c_instance_type_id) + + recs = migrate_engine.execute(instance_types.select()) + for row in recs: + type_id = row[0] + type_name = row[1] + migrate_engine.execute(instances.update()\ + .where(instances.c.instance_type == type_name)\ + .values(instance_type_id=type_id)) + + instances.c.instance_type.drop() + #instances.c.instance_type_id.alter(nullable=False) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + + instances = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + instances.create_column(c_instance_type) + + recs = migrate_engine.execute(instance_types.select()) + for row in recs: + type_id = row[0] + type_name = row[1] + migrate_engine.execute(instances.update()\ + .where(instances.c.instance_type_id == type_id)\ + .values(instance_type=type_name)) + + instances.c.instance_type_id.drop() + #instances.c.instance_type.alter(nullable=False) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 3b95ac23e..9d4c6cdef 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -209,7 +209,7 @@ class Instance(BASE, NovaBase): hostname = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) - instance_type = Column(String(255)) + instance_type_id = Column(String(255)) user_data = Column(Text) @@ -258,7 +258,8 @@ class InstanceActions(BASE, NovaBase): class InstanceTypes(BASE, NovaBase): """Represent possible instance_types or flavor of VM offered""" __tablename__ = "instance_types" - id = Column(Integer, primary_key=True) + id = Column(Integer, ForeignKey('instances.instance_type_id'), + primary_key=True) name = Column(String(255), unique=True) memory_mb = Column(Integer) vcpus = Column(Integer) @@ -268,6 +269,12 @@ class InstanceTypes(BASE, NovaBase): rxtx_quota = Column(Integer, nullable=False, default=0) rxtx_cap = Column(Integer, nullable=False, default=0) + instances = relationship(Instance, + backref=backref('instance_type', uselist=False), + foreign_keys=id, + primaryjoin='and_(Instance.instance_type_id == ' + 'InstanceTypes.id)') + class Volume(BASE, NovaBase): """Represents a block storage device that can be attached to a vm.""" diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index cb5047da9..899dcf7f7 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -32,6 +32,7 @@ from nova import test import nova.api.openstack from nova.api.openstack import servers import nova.compute.api +from nova.compute import instance_types import nova.db.api from nova.db.sqlalchemy.models import Instance from nova.db.sqlalchemy.models import InstanceMetadata @@ -71,13 +72,19 @@ def instance_address(context, instance_id): return None -def stub_instance(id, user_id=1, private_address=None, public_addresses=None): +def stub_instance(id, user_id=1, private_address=None, public_addresses=None, + host=None): metadata = [] metadata.append(InstanceMetadata(key='seq', value=id)) + inst_type = instance_types.get_instance_type_by_flavor_id(1) + if public_addresses == None: public_addresses = list() + if host != None: + host = str(host) + instance = { "id": id, "admin_pass": "", @@ -95,8 +102,8 @@ def stub_instance(id, user_id=1, private_address=None, public_addresses=None): "vcpus": 0, "local_gb": 0, "hostname": "", - "host": None, - "instance_type": "1", + "host": host, + "instance_type": dict(inst_type), "user_data": "", "reservation_id": "", "mac_address": "", @@ -630,7 +637,7 @@ class ServersTest(test.TestCase): self.assertEqual(s['hostId'], '') self.assertEqual(s['name'], 'server%d' % i) self.assertEqual(s['imageId'], '10') - self.assertEqual(s['flavorId'], '1') + self.assertEqual(s['flavorId'], 1) self.assertEqual(s['metadata']['seq'], i) def test_get_all_server_details_v1_1(self): @@ -654,12 +661,8 @@ class ServersTest(test.TestCase): instances - 2 on one host and 3 on another. ''' - def stub_instance(id, user_id=1): - return Instance(id=id, state=0, image_id=10, user_id=user_id, - display_name='server%s' % id, host='host%s' % (id % 2)) - def return_servers_with_host(context, user_id=1): - return [stub_instance(i) for i in xrange(5)] + return [stub_instance(i, 1, None, None, i % 2) for i in xrange(5)] self.stubs.Set(nova.db.api, 'instance_get_all_by_user', return_servers_with_host) @@ -677,7 +680,8 @@ class ServersTest(test.TestCase): self.assertEqual(s['id'], i) self.assertEqual(s['hostId'], host_ids[i % 2]) self.assertEqual(s['name'], 'server%d' % i) - self.assertEqual(s['imageId'], 10) + self.assertEqual(s['imageId'], '10') + self.assertEqual(s['flavorId'], 1) def test_server_pause(self): FLAGS.allow_admin_api = True diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py index 7ddfe377a..58d251b1e 100644 --- a/nova/tests/db/fakes.py +++ b/nova/tests/db/fakes.py @@ -28,29 +28,34 @@ def stub_out_db_instance_api(stubs, injected=True): """Stubs out the db API for creating Instances.""" INSTANCE_TYPES = { - 'm1.tiny': dict(memory_mb=512, + 'm1.tiny': dict(id=2, + memory_mb=512, vcpus=1, local_gb=0, flavorid=1, rxtx_cap=1), - 'm1.small': dict(memory_mb=2048, + 'm1.small': dict(id=5, + memory_mb=2048, vcpus=1, local_gb=20, flavorid=2, rxtx_cap=2), 'm1.medium': - dict(memory_mb=4096, + dict(id=1, + memory_mb=4096, vcpus=2, local_gb=40, flavorid=3, rxtx_cap=3), - 'm1.large': dict(memory_mb=8192, + 'm1.large': dict(id=3, + memory_mb=8192, vcpus=4, local_gb=80, flavorid=4, rxtx_cap=4), 'm1.xlarge': - dict(memory_mb=16384, + dict(id=4, + memory_mb=16384, vcpus=8, local_gb=160, flavorid=5, @@ -107,6 +112,12 @@ def stub_out_db_instance_api(stubs, injected=True): def fake_instance_type_get_by_name(context, name): return INSTANCE_TYPES[name] + def fake_instance_type_get_by_id(context, id): + for name, inst_type in INSTANCE_TYPES.iteritems(): + if str(inst_type['id']) == str(id): + return inst_type + return None + def fake_network_get_by_instance(context, instance_id): # Even instance numbers are on vlan networks if instance_id % 2 == 0: @@ -136,6 +147,7 @@ def stub_out_db_instance_api(stubs, injected=True): fake_network_get_all_by_instance) stubs.Set(db, 'instance_type_get_all', fake_instance_type_get_all) stubs.Set(db, 'instance_type_get_by_name', fake_instance_type_get_by_name) + stubs.Set(db, 'instance_type_get_by_id', fake_instance_type_get_by_id) stubs.Set(db, 'instance_get_fixed_address', fake_instance_get_fixed_address) stubs.Set(db, 'instance_get_fixed_address_v6', diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 1b0f426d2..1917dff3e 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -84,7 +84,8 @@ class ComputeTestCase(test.TestCase): inst['launch_time'] = '10' inst['user_id'] = self.user.id inst['project_id'] = self.project.id - inst['instance_type'] = 'm1.tiny' + type_id = instance_types.get_instance_type_by_name('m1.tiny')['id'] + inst['instance_type_id'] = type_id inst['mac_address'] = utils.generate_mac() inst['ami_launch_index'] = 0 inst.update(params) @@ -132,7 +133,7 @@ class ComputeTestCase(test.TestCase): cases = [dict(), dict(display_name=None)] for instance in cases: ref = self.compute_api.create(self.context, - FLAGS.default_instance_type, None, **instance) + instance_types.get_default_instance_type(), None, **instance) try: self.assertNotEqual(ref[0]['display_name'], None) finally: @@ -143,7 +144,7 @@ class ComputeTestCase(test.TestCase): group = self._create_group() ref = self.compute_api.create( self.context, - instance_type=FLAGS.default_instance_type, + instance_type=instance_types.get_default_instance_type(), image_id=None, security_group=['testgroup']) try: @@ -161,7 +162,7 @@ class ComputeTestCase(test.TestCase): ref = self.compute_api.create( self.context, - instance_type=FLAGS.default_instance_type, + instance_type=instance_types.get_default_instance_type(), image_id=None, security_group=['testgroup']) try: @@ -177,7 +178,7 @@ class ComputeTestCase(test.TestCase): ref = self.compute_api.create( self.context, - instance_type=FLAGS.default_instance_type, + instance_type=instance_types.get_default_instance_type(), image_id=None, security_group=['testgroup']) @@ -359,8 +360,9 @@ class ComputeTestCase(test.TestCase): instance_id = self._create_instance() self.compute.run_instance(self.context, instance_id) + inst_type = instance_types.get_instance_type_by_name('m1.xlarge') db.instance_update(self.context, instance_id, - {'instance_type': 'm1.xlarge'}) + {'instance_type_id': inst_type['id']}) self.assertRaises(exception.ApiError, self.compute_api.resize, context, instance_id, 1) @@ -380,8 +382,8 @@ class ComputeTestCase(test.TestCase): self.compute.terminate_instance(context, instance_id) def test_get_by_flavor_id(self): - type = instance_types.get_by_flavor_id(1) - self.assertEqual(type, 'm1.tiny') + type = instance_types.get_instance_type_by_flavor_id(1) + self.assertEqual(type['name'], 'm1.tiny') def test_resize_same_source_fails(self): """Ensure instance fails to migrate when source and destination are diff --git a/nova/tests/test_console.py b/nova/tests/test_console.py index d47c70d88..1a9a867ee 100644 --- a/nova/tests/test_console.py +++ b/nova/tests/test_console.py @@ -62,7 +62,7 @@ class ConsoleTestCase(test.TestCase): inst['launch_time'] = '10' inst['user_id'] = self.user.id inst['project_id'] = self.project.id - inst['instance_type'] = 'm1.tiny' + inst['instance_type_id'] = 1 inst['mac_address'] = utils.generate_mac() inst['ami_launch_index'] = 0 return db.instance_create(self.context, inst)['id'] diff --git a/nova/tests/test_instance_types.py b/nova/tests/test_instance_types.py index edc538879..5d6d5e1f4 100644 --- a/nova/tests/test_instance_types.py +++ b/nova/tests/test_instance_types.py @@ -40,7 +40,11 @@ class InstanceTypeTestCase(test.TestCase): max_flavorid = session.query(models.InstanceTypes).\ order_by("flavorid desc").\ first() + max_id = session.query(models.InstanceTypes).\ + order_by("id desc").\ + first() self.flavorid = max_flavorid["flavorid"] + 1 + self.id = max_id["id"] + 1 self.name = str(int(time.time())) def test_instance_type_create_then_delete(self): @@ -53,7 +57,7 @@ class InstanceTypeTestCase(test.TestCase): 'instance type was not created') instance_types.destroy(self.name) self.assertEqual(1, - instance_types.get_instance_type(self.name)["deleted"]) + instance_types.get_instance_type(self.id)["deleted"]) self.assertEqual(starting_inst_list, instance_types.get_all_types()) instance_types.purge(self.name) self.assertEqual(len(starting_inst_list), diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py index c65bc459d..39a123158 100644 --- a/nova/tests/test_quota.py +++ b/nova/tests/test_quota.py @@ -67,7 +67,7 @@ class QuotaTestCase(test.TestCase): inst['reservation_id'] = 'r-fakeres' inst['user_id'] = self.user.id inst['project_id'] = self.project.id - inst['instance_type'] = 'm1.large' + inst['instance_type_id'] = '3' # m1.large inst['vcpus'] = cores inst['mac_address'] = utils.generate_mac() return db.instance_create(self.context, inst)['id'] @@ -124,11 +124,12 @@ class QuotaTestCase(test.TestCase): for i in range(FLAGS.quota_instances): instance_id = self._create_instance() instance_ids.append(instance_id) + inst_type = instance_types.get_instance_type_by_name('m1.small') self.assertRaises(quota.QuotaError, compute.API().create, self.context, min_count=1, max_count=1, - instance_type='m1.small', + instance_type=inst_type, image_id=1) for instance_id in instance_ids: db.instance_destroy(self.context, instance_id) @@ -137,11 +138,12 @@ class QuotaTestCase(test.TestCase): instance_ids = [] instance_id = self._create_instance(cores=4) instance_ids.append(instance_id) + inst_type = instance_types.get_instance_type_by_name('m1.small') self.assertRaises(quota.QuotaError, compute.API().create, self.context, min_count=1, max_count=1, - instance_type='m1.small', + instance_type=inst_type, image_id=1) for instance_id in instance_ids: db.instance_destroy(self.context, instance_id) @@ -192,11 +194,12 @@ class QuotaTestCase(test.TestCase): metadata = {} for i in range(FLAGS.quota_metadata_items + 1): metadata['key%s' % i] = 'value%s' % i + inst_type = instance_types.get_instance_type_by_name('m1.small') self.assertRaises(quota.QuotaError, compute.API().create, self.context, min_count=1, max_count=1, - instance_type='m1.small', + instance_type=inst_type, image_id='fake', metadata=metadata) @@ -207,13 +210,15 @@ class QuotaTestCase(test.TestCase): def _create_with_injected_files(self, files): api = compute.API(image_service=self.StubImageService()) + inst_type = instance_types.get_instance_type_by_name('m1.small') api.create(self.context, min_count=1, max_count=1, - instance_type='m1.small', image_id='fake', + instance_type=inst_type, image_id='fake', injected_files=files) def test_no_injected_files(self): api = compute.API(image_service=self.StubImageService()) - api.create(self.context, instance_type='m1.small', image_id='fake') + inst_type = instance_types.get_instance_type_by_name('m1.small') + api.create(self.context, instance_type=inst_type, image_id='fake') def test_max_injected_files(self): files = [] diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index 6df74dd61..ae56a1a16 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -263,7 +263,7 @@ class SimpleDriverTestCase(test.TestCase): inst['reservation_id'] = 'r-fakeres' inst['user_id'] = self.user.id inst['project_id'] = self.project.id - inst['instance_type'] = 'm1.tiny' + inst['instance_type_id'] = '1' inst['mac_address'] = utils.generate_mac() inst['vcpus'] = kwargs.get('vcpus', 1) inst['ami_launch_index'] = 0 diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 958c8e3e2..c13cbf043 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -140,7 +140,7 @@ class LibvirtConnTestCase(test.TestCase): 'vcpus': 2, 'project_id': 'fake', 'bridge': 'br101', - 'instance_type': 'm1.small'} + 'instance_type_id': '5'} # m1.small def lazy_load_library_exists(self): """check if libvirt is available.""" diff --git a/nova/tests/test_volume.py b/nova/tests/test_volume.py index d71b75f3f..e9d8289aa 100644 --- a/nova/tests/test_volume.py +++ b/nova/tests/test_volume.py @@ -106,7 +106,7 @@ class VolumeTestCase(test.TestCase): inst['launch_time'] = '10' inst['user_id'] = 'fake' inst['project_id'] = 'fake' - inst['instance_type'] = 'm1.tiny' + inst['instance_type_id'] = '2' # m1.tiny inst['mac_address'] = utils.generate_mac() inst['ami_launch_index'] = 0 instance_id = db.instance_create(self.context, inst)['id'] diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 17e3f55e9..665ec068e 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -80,7 +80,7 @@ class XenAPIVolumeTestCase(test.TestCase): 'image_id': 1, 'kernel_id': 2, 'ramdisk_id': 3, - 'instance_type': 'm1.large', + 'instance_type_id': '3', # m1.large 'mac_address': 'aa:bb:cc:dd:ee:ff', 'os_type': 'linux'} @@ -328,7 +328,7 @@ class XenAPIVMTestCase(test.TestCase): self.assertEquals(self.vm['HVM_boot_policy'], '') def _test_spawn(self, image_id, kernel_id, ramdisk_id, - instance_type="m1.large", os_type="linux", + instance_type_id="3", os_type="linux", instance_id=1, check_injection=False): stubs.stubout_loopingcall_start(self.stubs) values = {'id': instance_id, @@ -337,7 +337,7 @@ class XenAPIVMTestCase(test.TestCase): 'image_id': image_id, 'kernel_id': kernel_id, 'ramdisk_id': ramdisk_id, - 'instance_type': instance_type, + 'instance_type_id': instance_type_id, 'mac_address': 'aa:bb:cc:dd:ee:ff', 'os_type': os_type} instance = db.instance_create(self.context, values) @@ -349,7 +349,7 @@ class XenAPIVMTestCase(test.TestCase): FLAGS.xenapi_image_service = 'glance' self.assertRaises(Exception, self._test_spawn, - 1, 2, 3, "m1.xlarge") + 1, 2, 3, "4") # m1.xlarge def test_spawn_raw_objectstore(self): FLAGS.xenapi_image_service = 'objectstore' @@ -523,7 +523,7 @@ class XenAPIVMTestCase(test.TestCase): 'image_id': 1, 'kernel_id': 2, 'ramdisk_id': 3, - 'instance_type': 'm1.large', + 'instance_type_id': '3', # m1.large 'mac_address': 'aa:bb:cc:dd:ee:ff', 'os_type': 'linux'} instance = db.instance_create(self.context, values) @@ -580,7 +580,7 @@ class XenAPIMigrateInstance(test.TestCase): 'kernel_id': None, 'ramdisk_id': None, 'local_gb': 5, - 'instance_type': 'm1.large', + 'instance_type_id': '3', # m1.large 'mac_address': 'aa:bb:cc:dd:ee:ff', 'os_type': 'linux'} diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index f34ea7225..6b7fce634 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -797,7 +797,10 @@ class LibvirtConnection(driver.ComputeDriver): root_fname = '%08x' % int(disk_images['image_id']) size = FLAGS.minimum_root_size - if inst['instance_type'] == 'm1.tiny' or suffix == '.rescue': + + inst_type_id = instance['instance_type_id'] + inst_type = instance_types.get_instance_type(inst_type_id) + if inst_type['name'] == 'm1.tiny' or suffix == '.rescue': size = None root_fname += "_sm" @@ -809,14 +812,13 @@ class LibvirtConnection(driver.ComputeDriver): user=user, project=project, size=size) - type_data = instance_types.get_instance_type(inst['instance_type']) - if type_data['local_gb']: + if inst_type['local_gb']: self._cache_image(fn=self._create_local, target=basepath('disk.local'), - fname="local_%s" % type_data['local_gb'], + fname="local_%s" % inst_type['local_gb'], cow=FLAGS.use_cow_images, - local_gb=type_data['local_gb']) + local_gb=inst_type['local_gb']) # For now, we assume that if we're not using a kernel, we're using a # partitioned disk image where the target partition is the first @@ -950,8 +952,8 @@ class LibvirtConnection(driver.ComputeDriver): nics.append(self._get_nic_for_xml(network, mapping)) # FIXME(vish): stick this in db - instance_type_name = instance['instance_type'] - instance_type = instance_types.get_instance_type(instance_type_name) + inst_type_id = instance['instance_type_id'] + inst_type = instance_types.get_instance_type(inst_type_id) if FLAGS.use_cow_images: driver_type = 'qcow2' @@ -962,10 +964,10 @@ class LibvirtConnection(driver.ComputeDriver): 'name': instance['name'], 'basepath': os.path.join(FLAGS.instances_path, instance['name']), - 'memory_kb': instance_type['memory_mb'] * 1024, - 'vcpus': instance_type['vcpus'], + 'memory_kb': inst_type['memory_mb'] * 1024, + 'vcpus': inst_type['vcpus'], 'rescue': rescue, - 'local': instance_type['local_gb'], + 'local': inst_type['local_gb'], 'driver_type': driver_type, 'nics': nics} diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index d07d60800..fd4f3705a 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -101,8 +101,8 @@ class VMHelper(HelperBase): 3. Using hardware virtualization """ - instance_type = instance_types.\ - get_instance_type(instance.instance_type) + inst_type_id = instance.instance_type_id + instance_type = instance_types.get_instance_type(inst_type_id) mem = str(long(instance_type['memory_mb']) * 1024 * 1024) vcpus = str(instance_type['vcpus']) rec = { @@ -169,8 +169,8 @@ class VMHelper(HelperBase): @classmethod def ensure_free_mem(cls, session, instance): - instance_type = instance_types.get_instance_type( - instance.instance_type) + inst_type_id = instance.instance_type_id + instance_type = instance_types.get_instance_type(inst_type_id) mem = long(instance_type['memory_mb']) * 1024 * 1024 #get free memory from host host = session.get_xenapi_host() diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index c96c35a6e..c26965c9a 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -802,8 +802,10 @@ class VMOps(object): instance['id']) networks = db.network_get_all_by_instance(admin_context, instance['id']) - flavor = db.instance_type_get_by_name(admin_context, - instance['instance_type']) + + inst_type = db.instance_type_get_by_id(admin_context, + instance['instance_type_id']) + network_info = [] for network in networks: network_IPs = [ip for ip in IPs if ip.network_id == network.id] @@ -827,7 +829,7 @@ class VMOps(object): 'gateway': network['gateway'], 'broadcast': network['broadcast'], 'mac': instance.mac_address, - 'rxtx_cap': flavor['rxtx_cap'], + 'rxtx_cap': inst_type['rxtx_cap'], 'dns': [network['dns']], 'ips': [ip_dict(ip) for ip in network_IPs]} if network['cidr_v6']: -- cgit From 5c67809e4b9a1546c48316ea52676dfeba8f1a75 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Mon, 4 Apr 2011 15:58:41 -0700 Subject: openstack api requires uppercase image format status responses --- nova/api/openstack/views/images.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 16195b050..9dec8a355 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -34,11 +34,11 @@ class ViewBuilder(object): def _format_status(self, image): """Update the status field to standardize format.""" status_mapping = { - 'pending': 'queued', - 'decrypting': 'preparing', - 'untarring': 'saving', - 'available': 'active', - 'killed': 'failed', + 'pending': 'QUEUED', + 'decrypting': 'PREPARING', + 'untarring': 'SAVING', + 'available': 'ACTIVE', + 'killed': 'FAILED', } try: -- cgit From a6c283f00d67a5172d8271d7e48bd58484ac6b96 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Mon, 4 Apr 2011 16:00:59 -0700 Subject: openstack api metadata responses must be strings --- nova/api/openstack/views/servers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index d24c025be..baa911590 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -82,7 +82,8 @@ class ViewBuilder(object): # Return the metadata as a dictionary metadata = {} for item in inst.get('metadata', []): - metadata[item['key']] = item['value'] + # metadata values must be strings per API + metadata[item['key']] = str(item['value']) inst_dict['metadata'] = metadata inst_dict['hostId'] = '' -- cgit From 868288b9705ea765b5515fd0ac9e0858f37239d0 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Mon, 4 Apr 2011 16:07:39 -0700 Subject: correct test for numeric/string metadata value conversion --- nova/tests/api/openstack/test_servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 313676e72..08a3f5d5a 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -630,7 +630,7 @@ class ServersTest(test.TestCase): self.assertEqual(s['imageId'], '10') self.assertEqual(s['flavorId'], '1') self.assertEqual(s['status'], 'BUILD') - self.assertEqual(s['metadata']['seq'], i) + self.assertEqual(s['metadata']['seq'], str(i)) def test_get_all_server_details_v1_1(self): req = webob.Request.blank('/v1.1/servers/detail') @@ -644,7 +644,7 @@ class ServersTest(test.TestCase): self.assertEqual(s['imageRef'], 'http://localhost/v1.1/images/10') self.assertEqual(s['flavorRef'], 'http://localhost/v1.1/flavors/1') self.assertEqual(s['status'], 'BUILD') - self.assertEqual(s['metadata']['seq'], i) + self.assertEqual(s['metadata']['seq'], str(i)) def test_get_all_server_details_with_host(self): ''' -- cgit From a18fece993c21f2ae1cbb44d8a0dea92d58d3b44 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 4 Apr 2011 22:16:53 -0400 Subject: Correct variable name. --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index d4cef8d7c..099ef647c 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -798,7 +798,7 @@ class LibvirtConnection(driver.ComputeDriver): root_fname = '%08x' % int(disk_images['image_id']) size = FLAGS.minimum_root_size - inst_type_id = instance['instance_type_id'] + inst_type_id = inst['instance_type_id'] inst_type = instance_types.get_instance_type(inst_type_id) if inst_type['name'] == 'm1.tiny' or suffix == '.rescue': size = None -- cgit From ff23dd2a3b86c816da04eddc903de0c8c3141954 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 5 Apr 2011 11:42:14 +0200 Subject: Allow CA code and state to be separated, and make sure CA code gets installed by setup.py install. --- nova/CA/.gitignore | 11 ++++++ nova/CA/geninter.sh | 39 +++++++++++++++++++ nova/CA/genrootca.sh | 29 ++++++++++++++ nova/CA/genvpn.sh | 36 +++++++++++++++++ nova/CA/newcerts/.placeholder | 0 nova/CA/openssl.cnf.tmpl | 90 +++++++++++++++++++++++++++++++++++++++++++ nova/CA/private/.placeholder | 0 nova/CA/projects/.gitignore | 1 + nova/CA/projects/.placeholder | 0 nova/CA/reqs/.gitignore | 1 + nova/CA/reqs/.placeholder | 0 nova/api/ec2/cloud.py | 8 +++- nova/crypto.py | 10 ++++- 13 files changed, 222 insertions(+), 3 deletions(-) create mode 100644 nova/CA/.gitignore create mode 100755 nova/CA/geninter.sh create mode 100755 nova/CA/genrootca.sh create mode 100755 nova/CA/genvpn.sh create mode 100644 nova/CA/newcerts/.placeholder create mode 100644 nova/CA/openssl.cnf.tmpl create mode 100644 nova/CA/private/.placeholder create mode 100644 nova/CA/projects/.gitignore create mode 100644 nova/CA/projects/.placeholder create mode 100644 nova/CA/reqs/.gitignore create mode 100644 nova/CA/reqs/.placeholder (limited to 'nova') diff --git a/nova/CA/.gitignore b/nova/CA/.gitignore new file mode 100644 index 000000000..fae0922bf --- /dev/null +++ b/nova/CA/.gitignore @@ -0,0 +1,11 @@ +index.txt +index.txt.old +index.txt.attr +index.txt.attr.old +cacert.pem +serial +serial.old +openssl.cnf +private/* +newcerts/* + diff --git a/nova/CA/geninter.sh b/nova/CA/geninter.sh new file mode 100755 index 000000000..1fbcc9e73 --- /dev/null +++ b/nova/CA/geninter.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# 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. + +# $1 is the id of the project and $2 is the subject of the cert +NAME=$1 +SUBJ=$2 +mkdir -p projects/$NAME +cd projects/$NAME +cp ../../openssl.cnf.tmpl openssl.cnf +sed -i -e s/%USERNAME%/$NAME/g openssl.cnf +mkdir certs crl newcerts private +openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes +echo "10" > serial +touch index.txt +# NOTE(vish): Disabling intermediate ca's because we don't actually need them. +# It makes more sense to have each project have its own root ca. +# openssl genrsa -out private/cakey.pem 1024 -config ./openssl.cnf -batch -nodes +# openssl req -new -sha256 -key private/cakey.pem -out ../../reqs/inter$NAME.csr -batch -subj "$SUBJ" +openssl ca -gencrl -config ./openssl.cnf -out crl.pem +if [ "`id -u`" != "`grep nova /etc/passwd | cut -d':' -f3`" ]; then + sudo chown -R nova:nogroup . +fi +# cd ../../ +# openssl ca -extensions v3_ca -days 365 -out INTER/$NAME/cacert.pem -in reqs/inter$NAME.csr -config openssl.cnf -batch diff --git a/nova/CA/genrootca.sh b/nova/CA/genrootca.sh new file mode 100755 index 000000000..8f2c3ee3f --- /dev/null +++ b/nova/CA/genrootca.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# 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. + +if [ -f "cacert.pem" ]; +then + echo "Not installing, it's already done." +else + cp openssl.cnf.tmpl openssl.cnf + sed -i -e s/%USERNAME%/ROOT/g openssl.cnf + openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes + touch index.txt + echo "10" > serial + openssl ca -gencrl -config ./openssl.cnf -out crl.pem +fi diff --git a/nova/CA/genvpn.sh b/nova/CA/genvpn.sh new file mode 100755 index 000000000..7e7db185d --- /dev/null +++ b/nova/CA/genvpn.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# 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. + +# This gets zipped and run on the cloudpipe-managed OpenVPN server +NAME=$1 +SUBJ=$2 + +mkdir -p projects/$NAME +cd projects/$NAME + +# generate a server priv key +openssl genrsa -out server.key 2048 + +# generate a server CSR +openssl req -new -key server.key -out server.csr -batch -subj "$SUBJ" + +novauid=`getent passwd nova | awk -F: '{print $3}'` +if [ ! -z "${novauid}" ] && [ "`id -u`" != "${novauid}" ]; then + sudo chown -R nova:nogroup . +fi diff --git a/nova/CA/newcerts/.placeholder b/nova/CA/newcerts/.placeholder new file mode 100644 index 000000000..e69de29bb diff --git a/nova/CA/openssl.cnf.tmpl b/nova/CA/openssl.cnf.tmpl new file mode 100644 index 000000000..dd81f1c2b --- /dev/null +++ b/nova/CA/openssl.cnf.tmpl @@ -0,0 +1,90 @@ +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# 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. + +# +# OpenSSL configuration file. +# + +# Establish working directory. + +dir = . + +[ ca ] +default_ca = CA_default + +[ CA_default ] +serial = $dir/serial +database = $dir/index.txt +new_certs_dir = $dir/newcerts +certificate = $dir/cacert.pem +private_key = $dir/private/cakey.pem +unique_subject = no +default_crl_days = 365 +default_days = 365 +default_md = md5 +preserve = no +email_in_dn = no +nameopt = default_ca +certopt = default_ca +policy = policy_match + +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + + +[ req ] +default_bits = 1024 # Size of keys +default_keyfile = key.pem # name of generated keys +default_md = md5 # message digest algorithm +string_mask = nombstr # permitted characters +distinguished_name = req_distinguished_name + +[ req_distinguished_name ] +# Variable name Prompt string +#---------------------- ---------------------------------- +0.organizationName = Organization Name (company) +organizationalUnitName = Organizational Unit Name (department, division) +emailAddress = Email Address +emailAddress_max = 40 +localityName = Locality Name (city, district) +stateOrProvinceName = State or Province Name (full name) +countryName = Country Name (2 letter code) +countryName_min = 2 +countryName_max = 2 +commonName = Common Name (hostname, IP, or your name) +commonName_max = 64 + +# Default values for the above, for consistency and less typing. +# Variable name Value +#------------------------------ ------------------------------ +0.organizationName_default = NOVA %USERNAME% +localityName_default = Mountain View +stateOrProvinceName_default = California +countryName_default = US + +[ v3_ca ] +basicConstraints = CA:TRUE +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always + +[ v3_req ] +basicConstraints = CA:FALSE +subjectKeyIdentifier = hash diff --git a/nova/CA/private/.placeholder b/nova/CA/private/.placeholder new file mode 100644 index 000000000..e69de29bb diff --git a/nova/CA/projects/.gitignore b/nova/CA/projects/.gitignore new file mode 100644 index 000000000..72e8ffc0d --- /dev/null +++ b/nova/CA/projects/.gitignore @@ -0,0 +1 @@ +* diff --git a/nova/CA/projects/.placeholder b/nova/CA/projects/.placeholder new file mode 100644 index 000000000..e69de29bb diff --git a/nova/CA/reqs/.gitignore b/nova/CA/reqs/.gitignore new file mode 100644 index 000000000..72e8ffc0d --- /dev/null +++ b/nova/CA/reqs/.gitignore @@ -0,0 +1 @@ +* diff --git a/nova/CA/reqs/.placeholder b/nova/CA/reqs/.placeholder new file mode 100644 index 000000000..e69de29bb diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 425784e8a..f119bd75c 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -103,10 +103,16 @@ class CloudController(object): # Gen root CA, if we don't have one root_ca_path = os.path.join(FLAGS.ca_path, FLAGS.ca_file) if not os.path.exists(root_ca_path): + genrootca_sh_path = os.path.join(os.path.dirname(__file__), + os.path.pardir, + os.path.pardir, + 'CA', + 'genrootca.sh') + start = os.getcwd() os.chdir(FLAGS.ca_path) # TODO(vish): Do this with M2Crypto instead - utils.runthis(_("Generating root CA: %s"), "sh", "genrootca.sh") + utils.runthis(_("Generating root CA: %s"), "sh", genrootca_sh_path) os.chdir(start) def _get_mpi_data(self, context, project_id): diff --git a/nova/crypto.py b/nova/crypto.py index b112e5b92..2b122e560 100644 --- a/nova/crypto.py +++ b/nova/crypto.py @@ -215,9 +215,12 @@ def generate_x509_cert(user_id, project_id, bits=1024): def _ensure_project_folder(project_id): if not os.path.exists(ca_path(project_id)): + geninter_sh_path = os.path.join(os.path.dirname(__file__), + 'CA', + 'geninter.sh') start = os.getcwd() os.chdir(ca_folder()) - utils.execute('sh', 'geninter.sh', project_id, + utils.execute('sh', geninter_sh_path, project_id, _project_cert_subject(project_id)) os.chdir(start) @@ -227,13 +230,16 @@ def generate_vpn_files(project_id): csr_fn = os.path.join(project_folder, "server.csr") crt_fn = os.path.join(project_folder, "server.crt") + genvpn_sh_path = os.path.join(os.path.dirname(__file__), + 'CA', + 'geninter.sh') if os.path.exists(crt_fn): return _ensure_project_folder(project_id) start = os.getcwd() os.chdir(ca_folder()) # TODO(vish): the shell scripts could all be done in python - utils.execute('sh', 'genvpn.sh', + utils.execute('sh', genvpn_sh_path, project_id, _vpn_cert_subject(project_id)) with open(csr_fn, "r") as csrfile: csr_text = csrfile.read() -- cgit From 7702affdbf09127e3e7cdfafcb6382673914438e Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Tue, 5 Apr 2011 18:43:11 +0900 Subject: Remove and from AllocateAddress response, and fix bug #751176. --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 425784e8a..3b333437b 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -777,7 +777,7 @@ class CloudController(object): def allocate_address(self, context, **kwargs): LOG.audit(_("Allocate address"), context=context) public_ip = self.network_api.allocate_floating_ip(context) - return {'addressSet': [{'publicIp': public_ip}]} + return {'publicIp': public_ip} def release_address(self, context, public_ip, **kwargs): LOG.audit(_("Release address %s"), public_ip, context=context) -- cgit From 2b3aea4be35f370c68ac3c24ab15d4851aa28e94 Mon Sep 17 00:00:00 2001 From: Kei Masumoto Date: Tue, 5 Apr 2011 20:10:10 +0900 Subject: fix bug 746821 --- nova/tests/test_virt.py | 3 ++- nova/virt/libvirt_conn.py | 38 +++++++++++++++++++++++++++----------- 2 files changed, 29 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 958c8e3e2..2e9eac0d5 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -479,7 +479,7 @@ class LibvirtConnTestCase(test.TestCase): fake_timer = FakeTime() - self.create_fake_libvirt_mock(nwfilterLookupByName=fake_raise) + self.create_fake_libvirt_mock() instance_ref = db.instance_create(self.context, self.test_instance) # Start test @@ -488,6 +488,7 @@ class LibvirtConnTestCase(test.TestCase): conn = libvirt_conn.LibvirtConnection(False) conn.firewall_driver.setattr('setup_basic_filtering', fake_none) conn.firewall_driver.setattr('prepare_instance_filter', fake_none) + conn.firewall_driver.setattr('instance_filter_exists', fake_none) conn.ensure_filtering_rules_for_instance(instance_ref, time=fake_timer) except exception.Error, e: diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index babbc610d..bdf577825 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1401,18 +1401,13 @@ class LibvirtConnection(driver.ComputeDriver): # wait for completion timeout_count = range(FLAGS.live_migration_retry_count) while timeout_count: - try: - filter_name = 'nova-instance-%s' % instance_ref.name - self._conn.nwfilterLookupByName(filter_name) + if self.firewall_driver.instance_filter_exists(instance_ref): break - except libvirt.libvirtError: - timeout_count.pop() - if len(timeout_count) == 0: - ec2_id = instance_ref['hostname'] - iname = instance_ref.name - msg = _('Timeout migrating for %(ec2_id)s(%(iname)s)') - raise exception.Error(msg % locals()) - time.sleep(1) + timeout_count.pop() + if len(timeout_count) == 0: + msg = _('Timeout migrating for %s. nwfilter not found.') + raise exception.Error(msg % instance_ref.name) + time.sleep(1) def live_migration(self, ctxt, instance_ref, dest, post_method, recover_method): @@ -1541,6 +1536,10 @@ class FirewallDriver(object): """ raise NotImplementedError() + def instance_filter_exists(self, instance): + """Check nova-instance-instance-xxx exists""" + raise NotImplementedError() + class NWFilterFirewall(FirewallDriver): """ @@ -1848,6 +1847,19 @@ class NWFilterFirewall(FirewallDriver): return 'nova-instance-%s' % (instance['name']) return 'nova-instance-%s-%s' % (instance['name'], nic_id) + def instance_filter_exists(self, instance): + """Check nova-instance-instance-xxx exists""" + + network_info = _get_network_info(instance) + for (network, mapping) in network_info: + nic_id = mapping['mac'].replace(':', '') + instance_filter_name = self._instance_filter_name(instance, nic_id) + try: + self._conn.nwfilterLookupByName(instance_filter_name) + except libvirt.libvirtError: + return False + return True + class IptablesFirewallDriver(FirewallDriver): def __init__(self, execute=None, **kwargs): @@ -2037,6 +2049,10 @@ class IptablesFirewallDriver(FirewallDriver): return ipv4_rules, ipv6_rules + def instance_filter_exists(self, instance): + """Check nova-instance-instance-xxx exists""" + return self.nwfilter.instance_filter_exists(instance) + def refresh_security_group_members(self, security_group): pass -- cgit From d7013c9617d0740976a78ba87b1214c2b15ee702 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 5 Apr 2011 13:16:12 +0200 Subject: Automatically create CA state dir, and make sure the CA scripts look for the templates in the right places. --- nova/CA/geninter.sh | 2 +- nova/CA/genrootca.sh | 3 ++- nova/api/ec2/cloud.py | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/CA/geninter.sh b/nova/CA/geninter.sh index 1fbcc9e73..4b7f5a55c 100755 --- a/nova/CA/geninter.sh +++ b/nova/CA/geninter.sh @@ -23,7 +23,7 @@ mkdir -p projects/$NAME cd projects/$NAME cp ../../openssl.cnf.tmpl openssl.cnf sed -i -e s/%USERNAME%/$NAME/g openssl.cnf -mkdir certs crl newcerts private +mkdir -p certs crl newcerts private openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes echo "10" > serial touch index.txt diff --git a/nova/CA/genrootca.sh b/nova/CA/genrootca.sh index 8f2c3ee3f..091cf17fc 100755 --- a/nova/CA/genrootca.sh +++ b/nova/CA/genrootca.sh @@ -20,8 +20,9 @@ if [ -f "cacert.pem" ]; then echo "Not installing, it's already done." else - cp openssl.cnf.tmpl openssl.cnf + cp "$(dirname $0)/openssl.cnf.tmpl" openssl.cnf sed -i -e s/%USERNAME%/ROOT/g openssl.cnf + mkdir -p certs crl newcerts private openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes touch index.txt echo "10" > serial diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index f119bd75c..5d6d9537a 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -110,6 +110,7 @@ class CloudController(object): 'genrootca.sh') start = os.getcwd() + os.makedirs(FLAGS.ca_path) os.chdir(FLAGS.ca_path) # TODO(vish): Do this with M2Crypto instead utils.runthis(_("Generating root CA: %s"), "sh", genrootca_sh_path) -- cgit From 4d1f60df049e11623df7d6174eda19e5e27d004e Mon Sep 17 00:00:00 2001 From: Kei Masumoto Date: Tue, 5 Apr 2011 20:20:32 +0900 Subject: fix bug lp751231 --- nova/compute/manager.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 08b772517..37a904b1e 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1090,6 +1090,15 @@ class ComputeManager(manager.SchedulerDependentManager): vm_state = vm_instance.state vms_not_found_in_db.remove(name) + if db_instance['state_description'] == 'migrating': + # A situation which db record exists, but no instance" + # sometimes occurs while live-migration at src compute, + # this case should be ignored. + LOG.info(_("the instance '%(name)s' is not found in hypervisor" + ", while db record is found. But not synchronize " + "since it is migrating." % locals())) + continue + if vm_state != db_state: LOG.info(_("DB/VM state mismatch. Changing state from " "'%(db_state)s' to '%(vm_state)s'") % locals()) -- cgit From ca2ce6dc4a96c7bded2c30c258b7becaee5a1ed7 Mon Sep 17 00:00:00 2001 From: Kei Masumoto Date: Tue, 5 Apr 2011 20:39:22 +0900 Subject: fix bug lp751242 --- nova/compute/api.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 996955fe3..f67aa47e8 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -372,6 +372,10 @@ class API(base.Base): instance_id) return + if (instance['state_description'] == 'migrating'): + LOG.warning(_("Instance %s is being migrated"), instance_id) + return + self.update(context, instance['id'], state_description='terminating', -- cgit From d8ab404fd74facd181eaaee7cbc78b39a82afd1c Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 5 Apr 2011 08:26:16 -0400 Subject: Add missing underscore. --- nova/api/openstack/image_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index 76abdaa7e..c733172ae 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -46,7 +46,7 @@ class Controller(wsgi.Controller): num_metadata = len(metadata) quota_metadata = quota.allowed_metadata_items(context, num_metadata) if quota_metadata < num_metadata: - expl = ("Image metadata limit exceeded") + expl = _("Image metadata limit exceeded") raise exc.HTTPBadRequest(explanation=expl) def index(self, req, image_id): -- cgit From 6c8c454826263b334eb674c9ab1c8d62e5fd491f Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 5 Apr 2011 14:55:19 +0200 Subject: Help paste_config_file find the api config now that we moved it. --- nova/wsgi.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/wsgi.py b/nova/wsgi.py index ba0819466..05e7d5924 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -532,6 +532,7 @@ def paste_config_file(basename): """ configfiles = [basename, + os.path.join(FLAGS.state_path, 'etc', 'nova', basename), os.path.join(FLAGS.state_path, 'etc', basename), os.path.join(FLAGS.state_path, basename), '/etc/nova/%s' % basename] -- cgit From f1f8e00dc420bbc78b8d143c56375afc721c4c7d Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Tue, 5 Apr 2011 10:17:29 -0400 Subject: Dont configure vnc if we are using lxc --- nova/virt/libvirt_conn.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index babbc610d..2be190256 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -970,7 +970,8 @@ class LibvirtConnection(driver.ComputeDriver): 'nics': nics} if FLAGS.vnc_enabled: - xml_info['vncserver_host'] = FLAGS.vncserver_host + if FLAGS.libvirt_type != 'lxc': + xml_info['vncserver_host'] = FLAGS.vncserver_host if not rescue: if instance['kernel_id']: xml_info['kernel'] = xml_info['basepath'] + "/kernel" -- cgit From e6505fd78cc28a5e91ec6fbf55e2e8de07679c7c Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Tue, 5 Apr 2011 10:56:24 -0400 Subject: Fixed the addresses and metadata collections in xml responses. Added corresponding tests. --- nova/api/openstack/servers.py | 7 +++++++ nova/tests/api/openstack/test_servers.py | 36 ++++++++++++++++++++++++++++++++ nova/wsgi.py | 18 ++++++++++++++++ 3 files changed, 61 insertions(+) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 6704a68ae..cada92813 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -55,6 +55,13 @@ class Controller(wsgi.Controller): "imageRef"], "link": ["rel", "type", "href"], }, + "dict_collections": { + "metadata": {"item_name": "meta", "item_key": "key"}, + }, + "list_collections": { + "public": {"item_name": "ip", "item_key": "addr"}, + "private": {"item_name": "ip", "item_key": "addr"}, + }, }, } diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 313676e72..ce9821587 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -192,6 +192,26 @@ class ServersTest(test.TestCase): print res_dict['server'] self.assertEqual(res_dict['server']['links'], expected_links) + def test_get_server_by_id_with_addresses_xml(self): + private = "192.168.0.3" + public = ["1.2.3.4"] + new_return_server = return_server_with_addresses(private, public) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1') + req.headers['Accept'] = 'application/xml' + res = req.get_response(fakes.wsgi_app()) + dom = minidom.parseString(res.body) + server = dom.childNodes[0] + self.assertEquals(server.nodeName, 'server') + self.assertEquals(server.getAttribute('id'), '1') + self.assertEquals(server.getAttribute('name'), 'server1') + (public,) = server.getElementsByTagName('public'); + (ip,) = public.getElementsByTagName('ip') + self.assertEquals(ip.getAttribute('addr'), '1.2.3.4') + (private,) = server.getElementsByTagName('private') + (ip,) = private.getElementsByTagName('ip') + self.assertEquals(ip.getAttribute('addr'), '192.168.0.3') + def test_get_server_by_id_with_addresses(self): private = "192.168.0.3" public = ["1.2.3.4"] @@ -618,6 +638,22 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 404) + def test_get_all_server_details_xml_v1_0(self): + req = webob.Request.blank('/v1.0/servers/detail') + req.headers['Accept'] = 'application/xml' + res = req.get_response(fakes.wsgi_app()) + print res.body + dom = minidom.parseString(res.body) + for i, server in enumerate(dom.getElementsByTagName('server')): + self.assertEqual(server.getAttribute('id'), str(i)) + self.assertEqual(server.getAttribute('hostId'), '') + self.assertEqual(server.getAttribute('name'), 'server%d' % i) + self.assertEqual(server.getAttribute('imageId'), '10') + self.assertEqual(server.getAttribute('status'), 'BUILD') + (meta,) = server.getElementsByTagName('meta') + self.assertEqual(meta.getAttribute('key'), 'seq') + self.assertEqual(meta.firstChild.data.strip(), str(i)) + def test_get_all_server_details_v1_0(self): req = webob.Request.blank('/v1.0/servers/detail') res = req.get_response(fakes.wsgi_app()) diff --git a/nova/wsgi.py b/nova/wsgi.py index ba0819466..a550a33b0 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -484,6 +484,14 @@ class Serializer(object): """Recursive method to convert data members to XML nodes.""" result = doc.createElement(nodename) if type(data) is list: + collections = metadata.get('list_collections', {}) + if nodename in collections: + metadata = collections[nodename] + for item in data: + node = doc.createElement(metadata['item_name']) + node.setAttribute(metadata['item_key'], str(item)) + result.appendChild(node) + return result singular = metadata.get('plurals', {}).get(nodename, None) if singular is None: if nodename.endswith('s'): @@ -494,6 +502,16 @@ class Serializer(object): node = self._to_xml_node(doc, metadata, singular, item) result.appendChild(node) elif type(data) is dict: + collections = metadata.get('dict_collections', {}) + if nodename in collections: + metadata = collections[nodename] + for k, v in data.items(): + node = doc.createElement(metadata['item_name']) + node.setAttribute(metadata['item_key'], str(k)) + text = doc.createTextNode(str(v)) + node.appendChild(text) + result.appendChild(node) + return result attrs = metadata.get('attributes', {}).get(nodename, {}) for k, v in data.items(): if k in attrs: -- cgit From 3bb6e627fd99a307825f88ff8882e974bcf1b365 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 5 Apr 2011 13:02:42 -0400 Subject: Remove comments. --- .../migrate_repo/versions/014_add_instance_type_id_to_instances.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py index 813e57e10..b12a0a801 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py @@ -63,7 +63,6 @@ def upgrade(migrate_engine): .values(instance_type_id=type_id)) instances.c.instance_type.drop() - #instances.c.instance_type_id.alter(nullable=False) def downgrade(migrate_engine): @@ -83,4 +82,3 @@ def downgrade(migrate_engine): .values(instance_type=type_name)) instances.c.instance_type_id.drop() - #instances.c.instance_type.alter(nullable=False) -- cgit From 3c487493de2a0f827780d54080797bfe70183ba6 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Tue, 5 Apr 2011 17:49:38 -0400 Subject: Removing naughty semicolon. --- nova/tests/api/openstack/test_servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index ce9821587..a424a8105 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -205,7 +205,7 @@ class ServersTest(test.TestCase): self.assertEquals(server.nodeName, 'server') self.assertEquals(server.getAttribute('id'), '1') self.assertEquals(server.getAttribute('name'), 'server1') - (public,) = server.getElementsByTagName('public'); + (public,) = server.getElementsByTagName('public') (ip,) = public.getElementsByTagName('ip') self.assertEquals(ip.getAttribute('addr'), '1.2.3.4') (private,) = server.getElementsByTagName('private') -- cgit From 56eb682a169c20a9a8275b3b9bfd423f0d562cbc Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 5 Apr 2011 15:01:33 -0700 Subject: unite the filtering done by glance client and s3 --- nova/api/ec2/cloud.py | 2 +- nova/image/glance.py | 28 ---------------------------- nova/image/local.py | 4 +++- nova/image/s3.py | 45 +++------------------------------------------ nova/image/service.py | 27 +++++++++++++++++++++++++++ 5 files changed, 34 insertions(+), 72 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 02a30220a..b4705820e 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -927,7 +927,7 @@ class CloudController(object): 'ari': 'ramdisk', 'ami': 'machine'} i['imageType'] = display_mapping.get(image_type) - i['isPublic'] = str(image['properties'].get('is_public', '')) == 'True' + i['isPublic'] = image.get('is_public') == True i['architecture'] = image['properties'].get('architecture') return i diff --git a/nova/image/glance.py b/nova/image/glance.py index fdf468594..e583c504d 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -186,34 +186,6 @@ class GlanceImageService(service.BaseImageService): image_meta = _convert_timestamps_to_datetimes(image_meta) return image_meta - @staticmethod - def _is_image_available(context, image_meta): - """ - Images are always available if they are public or if the user is an - admin. - - Otherwise, we filter by project_id (if present) and then fall-back to - images owned by user. - """ - # FIXME(sirp): We should be filtering by user_id on the Glance side - # for security; however, we can't do that until we get authn/authz - # sorted out. Until then, filtering in Nova. - if image_meta['is_public'] or context.is_admin: - return True - - properties = image_meta['properties'] - - if context.project_id and ('project_id' in properties): - return str(properties['project_id']) == str(project_id) - - try: - user_id = properties['user_id'] - except KeyError: - return False - - return str(user_id) == str(context.user_id) - - # utility functions def _convert_timestamps_to_datetimes(image_meta): """ diff --git a/nova/image/local.py b/nova/image/local.py index 1fb6e1f13..92d3dabd4 100644 --- a/nova/image/local.py +++ b/nova/image/local.py @@ -84,7 +84,9 @@ class LocalImageService(service.BaseImageService): def show(self, context, image_id): try: with open(self._path_to(image_id)) as metadata_file: - return json.load(metadata_file) + image_meta = json.load(metadata_file) + if not self._is_image_available(context, image_meta): + raise exception.NotFound except (IOError, ValueError): raise exception.NotFound diff --git a/nova/image/s3.py b/nova/image/s3.py index def49f682..93af21022 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -58,54 +58,16 @@ class S3ImageService(service.BaseImageService): return image def delete(self, context, image_id): - # FIXME(vish): call to show is to check filter + # FIXME(vish): call to show is to check visibility self.show(context, image_id) self.service.delete(context, image_id) def update(self, context, image_id, metadata, data=None): - # FIXME(vish): call to show is to check filter + # FIXME(vish): call to show is to check visibility self.show(context, image_id) image = self.service.update(context, image_id, metadata, data) return image - def index(self, context): - images = self.service.index(context) - # FIXME(vish): index doesn't filter so we do it manually - return self._filter(context, images) - - def detail(self, context): - images = self.service.detail(context) - # FIXME(vish): detail doesn't filter so we do it manually - return self._filter(context, images) - - @classmethod - def _is_visible(cls, context, image): - - return (context.is_admin - or context.project_id == image['properties'].get('owner_id') - or str(image['properties'].get('is_public')) == 'True') - - @classmethod - def _filter(cls, context, images): - filtered = [] - for image in images: - if not cls._is_visible(context, image): - continue - filtered.append(image) - return filtered - - def show(self, context, image_id): - image = self.service.show(context, image_id) - if not self._is_visible(context, image): - raise exception.NotFound - return image - - def show_by_name(self, context, name): - image = self.service.show_by_name(context, name) - if not self._is_visible(context, image): - raise exception.NotFound - return image - @staticmethod def _conn(context): # TODO(vish): is there a better way to get creds to sign @@ -168,7 +130,7 @@ class S3ImageService(service.BaseImageService): arch = 'x86_64' properties = metadata['properties'] - properties['owner_id'] = context.project_id + properties['project_id'] = context.project_id properties['architecture'] = arch if kernel_id: @@ -177,7 +139,6 @@ class S3ImageService(service.BaseImageService): if ramdisk_id: properties['ramdisk_id'] = ec2utils.ec2_id_to_id(ramdisk_id) - properties['is_public'] = False metadata.update({'disk_format': image_format, 'container_format': image_format, 'status': 'queued', diff --git a/nova/image/service.py b/nova/image/service.py index b9897ecae..fddc72409 100644 --- a/nova/image/service.py +++ b/nova/image/service.py @@ -136,6 +136,33 @@ class BaseImageService(object): """ raise NotImplementedError + @staticmethod + def _is_image_available(context, image_meta): + """ + Images are always available if they are public or if the user is an + admin. + + Otherwise, we filter by project_id (if present) and then fall-back to + images owned by user. + """ + # FIXME(sirp): We should be filtering by user_id on the Glance side + # for security; however, we can't do that until we get authn/authz + # sorted out. Until then, filtering in Nova. + if image_meta['is_public'] or context.is_admin: + return True + + properties = image_meta['properties'] + + if context.project_id and ('project_id' in properties): + return str(properties['project_id']) == str(context.project_id) + + try: + user_id = properties['user_id'] + except KeyError: + return False + + return str(user_id) == str(context.user_id) + @classmethod def _translate_to_base(cls, metadata): """Return a metadata dictionary that is BaseImageService compliant. -- cgit From 8eff0a91bbc475d8559b39fb81e1a62beda841f3 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 5 Apr 2011 15:35:17 -0700 Subject: update and fix tests --- nova/image/local.py | 1 + nova/image/s3.py | 13 +++++++++++++ nova/tests/api/openstack/test_servers.py | 16 +++++++--------- 3 files changed, 21 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/image/local.py b/nova/image/local.py index 92d3dabd4..bad4c2d85 100644 --- a/nova/image/local.py +++ b/nova/image/local.py @@ -87,6 +87,7 @@ class LocalImageService(service.BaseImageService): image_meta = json.load(metadata_file) if not self._is_image_available(context, image_meta): raise exception.NotFound + return image_meta except (IOError, ValueError): raise exception.NotFound diff --git a/nova/image/s3.py b/nova/image/s3.py index 93af21022..a7301eefd 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -46,6 +46,7 @@ flags.DEFINE_string('image_decryption_dir', '/tmp', class S3ImageService(service.BaseImageService): + """Wraps an existing image service to support s3 based register""" def __init__(self, service=None, *args, **kwargs): if service == None: service = utils.import_object(FLAGS.image_service) @@ -68,6 +69,18 @@ class S3ImageService(service.BaseImageService): image = self.service.update(context, image_id, metadata, data) return image + def index(self, context): + return self.service.index(context) + + def detail(self, context): + return self.service.detail(context) + + def show(self, context, image_id): + return self.service.show(context, image_id) + + def show_by_name(self, context, name): + return self.service.show(context, name) + @staticmethod def _conn(context): # TODO(vish): is there a better way to get creds to sign diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 313676e72..1ec01bb4c 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1525,29 +1525,27 @@ class TestGetKernelRamdiskFromImage(test.TestCase): def test_not_ami(self): """Anything other than ami should return no kernel and no ramdisk""" - image_meta = {'id': 1, 'status': 'active', - 'properties': {'disk_format': 'vhd'}} + image_meta = {'id': 1, 'status': 'active', 'container_format': 'vhd'} kernel_id, ramdisk_id = self._get_k_r(image_meta) self.assertEqual(kernel_id, None) self.assertEqual(ramdisk_id, None) def test_ami_no_kernel(self): """If an ami is missing a kernel it should raise NotFound""" - image_meta = {'id': 1, 'status': 'active', - 'properties': {'disk_format': 'ami', 'ramdisk_id': 1}} + image_meta = {'id': 1, 'status': 'active', 'container_format': 'ami', + 'properties': {'ramdisk_id': 1}} self.assertRaises(exception.NotFound, self._get_k_r, image_meta) def test_ami_no_ramdisk(self): """If an ami is missing a ramdisk it should raise NotFound""" - image_meta = {'id': 1, 'status': 'active', - 'properties': {'disk_format': 'ami', 'kernel_id': 1}} + image_meta = {'id': 1, 'status': 'active', 'container_format': 'ami', + 'properties': {'kernel_id': 1}} self.assertRaises(exception.NotFound, self._get_k_r, image_meta) def test_ami_kernel_ramdisk_present(self): """Return IDs if both kernel and ramdisk are present""" - image_meta = {'id': 1, 'status': 'active', - 'properties': {'disk_format': 'ami', 'kernel_id': 1, - 'ramdisk_id': 2}} + image_meta = {'id': 1, 'status': 'active', 'container_format': 'ami', + 'properties': {'kernel_id': 1, 'ramdisk_id': 2}} kernel_id, ramdisk_id = self._get_k_r(image_meta) self.assertEqual(kernel_id, 1) self.assertEqual(ramdisk_id, 2) -- cgit From 5f414311fb581ba612cf152f3ec9983d5f39c2e4 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 5 Apr 2011 15:45:01 -0700 Subject: fallback to status if image_state is not set --- nova/api/ec2/cloud.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index b4705820e..4e5b8bfc6 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -920,7 +920,11 @@ class CloudController(object): get('image_location'), name) else: i['imageLocation'] = image['properties'].get('image_location') - i['imageState'] = image['properties'].get('image_state') + # NOTE(vish): fallback status if image_state isn't set + state = image.get('status') + if state == 'active': + state = 'available' + i['imageState'] = image['properties'].get('image_state', state) i['displayName'] = name i['description'] = image.get('description') display_mapping = {'aki': 'kernel', -- cgit From 75082c44cdc0319c56805b5558927760636eaf4b Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 5 Apr 2011 15:49:43 -0700 Subject: pep8 --- nova/image/glance.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index e583c504d..f848f40d8 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -186,6 +186,7 @@ class GlanceImageService(service.BaseImageService): image_meta = _convert_timestamps_to_datetimes(image_meta) return image_meta + # utility functions def _convert_timestamps_to_datetimes(image_meta): """ -- cgit From a254fd9b63c48f64a62fd38df3a2caae81ce63c7 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Tue, 5 Apr 2011 18:29:53 -0500 Subject: typo --- nova/virt/xenapi/vmops.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index c96c35a6e..ef2ab09b9 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -814,12 +814,11 @@ class VMOps(object): "netmask": network["netmask"], "enabled": "1"} - def ip6_dict(ip6): + def ip6_dict(): return { "ip": utils.to_global_ipv6(network['cidr_v6'], instance['mac_address']), "netmask": network['netmask_v6'], - "gateway": network['gateway_v6'], "enabled": "1"} info = { @@ -831,7 +830,9 @@ class VMOps(object): 'dns': [network['dns']], 'ips': [ip_dict(ip) for ip in network_IPs]} if network['cidr_v6']: - info['ip6s'] = [ip6_dict(ip) for ip in network_IPs] + info['ip6s'] = [ip6_dict()] + if network['gateway_v6']: + info['gateway6'] = network['gateway_v6'], network_info.append((network, info)) return network_info -- cgit From 44eefb1eefda5e42a286ee1aa689c1c93e72aae4 Mon Sep 17 00:00:00 2001 From: Kei Masumoto Date: Wed, 6 Apr 2011 14:44:19 +0900 Subject: fixed based on reviwer's comment --- nova/compute/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index f67aa47e8..db04143c1 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -367,12 +367,12 @@ class API(base.Base): instance_id) raise - if (instance['state_description'] == 'terminating'): + if instance['state_description'] == 'terminating': LOG.warning(_("Instance %s is already being terminated"), instance_id) return - if (instance['state_description'] == 'migrating'): + if instance['state_description'] == 'migrating': LOG.warning(_("Instance %s is being migrated"), instance_id) return -- cgit From d99a40d0a34a0d7ec05dcaa188a58535f0e41953 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Wed, 6 Apr 2011 14:05:12 +0200 Subject: Only create ca_path directory if it does not already exist. --- nova/api/ec2/cloud.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 99520b302..58effd134 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -110,7 +110,8 @@ class CloudController(object): 'genrootca.sh') start = os.getcwd() - os.makedirs(FLAGS.ca_path) + if not os.path.exists(FLAGS.ca_path): + os.makedirs(FLAGS.ca_path) os.chdir(FLAGS.ca_path) # TODO(vish): Do this with M2Crypto instead utils.runthis(_("Generating root CA: %s"), "sh", genrootca_sh_path) -- cgit From dbeab47bbdeceab0bef896c9d7646ae346c9dd3a Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 6 Apr 2011 09:05:12 -0700 Subject: Wait for device node to be created after mounting image VDI --- nova/virt/xenapi/vm_utils.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index d07d60800..d29456ec6 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -49,6 +49,8 @@ LOG = logging.getLogger("nova.virt.xenapi.vm_utils") FLAGS = flags.FLAGS flags.DEFINE_string('default_os_type', 'linux', 'Default OS type') +flags.DEFINE_integer('timeout_block', 10, + 'time to wait for a block device to be created') XENAPI_POWER_STATE = { 'Halted': power_state.SHUTDOWN, @@ -896,6 +898,16 @@ def remap_vbd_dev(dev): return remapped_dev +def _wait_for_device(dev): + """Wait for device node to appear""" + for i in xrange(0, FLAGS.timeout_block): + if os.path.exists('/dev/%s' % dev): + return + time.sleep(1) + + raise StorageError(_('Timeout waiting for device %s to be created') % dev) + + def with_vdi_attached_here(session, vdi_ref, read_only, f): this_vm_ref = get_this_vm_ref(session) vbd_rec = {} @@ -924,6 +936,7 @@ def with_vdi_attached_here(session, vdi_ref, read_only, f): if dev != orig_dev: LOG.debug(_('VBD %(vbd_ref)s plugged into wrong dev, ' 'remapping to %(dev)s') % locals()) + _wait_for_device(dev) return f(dev) finally: LOG.debug(_('Destroying VBD for VDI %s ... '), vdi_ref) -- cgit From 0a8ca1bb7f123718ae48bb842b1c532b07f03890 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Wed, 6 Apr 2011 18:10:42 +0200 Subject: Create ca_folder if it does not already exist. --- nova/crypto.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova') diff --git a/nova/crypto.py b/nova/crypto.py index 2b122e560..9b1897926 100644 --- a/nova/crypto.py +++ b/nova/crypto.py @@ -269,6 +269,8 @@ def _sign_csr(csr_text, ca_folder): LOG.debug(_("Flags path: %s"), ca_folder) start = os.getcwd() # Change working dir to CA + if not os.path.exists(ca_folder): + os.makedirs(ca_folder) os.chdir(ca_folder) utils.execute('openssl', 'ca', '-batch', '-out', outbound, '-config', './openssl.cnf', '-infiles', inbound) -- cgit From 481a77134a4e0e1d668fa488d7c5b1d7e1bc5429 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 6 Apr 2011 11:15:35 -0500 Subject: modified behavior of inject_network_info and reset_network related to a vm_ref not being passed in --- nova/virt/xenapi/vmops.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index ef2ab09b9..f02beda24 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -176,7 +176,7 @@ class VMOps(object): vdi_ref, network_info) self.create_vifs(vm_ref, network_info) - self.inject_network_info(instance, vm_ref, network_info) + self.inject_network_info(instance, network_info, vm_ref) return vm_ref def _spawn(self, instance, vm_ref): @@ -836,15 +836,31 @@ class VMOps(object): network_info.append((network, info)) return network_info - def inject_network_info(self, instance, vm_ref, network_info): + #TODO{tr3buchet) remove this shim with nova-multi-nic + def inject_network_info(self, instance, network_info=None, vm_ref=None): + """ + shim in place which makes inject_network_info work without being + passed network_info. + shim goes away after nova-multi-nic + """ + if not network_info: + network_info = self._get_network_info(instance) + self._inject_network_info(instance, network_info, vm_ref) + + def _inject_network_info(self, instance, network_info, vm_ref=None): """ Generate the network info and make calls to place it into the xenstore and the xenstore param list. + vm_ref can be passed in because it will sometimes be different than + what VMHelper.lookup(session, instance.name) will find (ex: rescue) """ logging.debug(_("injecting network info to xs for vm: |%s|"), vm_ref) - # this function raises if vm_ref is not a vm_opaque_ref - self._session.get_xenapi().VM.get_record(vm_ref) + if vm_ref: + # this function raises if vm_ref is not a vm_opaque_ref + self._session.get_xenapi().VM.get_record(vm_ref) + else: + vm_ref = VMHelper.lookup(self._session, instance.name) for (network, info) in network_info: location = 'vm-data/networking/%s' % info['mac'].replace(':', '') @@ -876,8 +892,10 @@ class VMOps(object): VMHelper.create_vif(self._session, vm_ref, network_ref, mac_address, device, rxtx_cap) - def reset_network(self, instance, vm_ref): + def reset_network(self, instance, vm_ref=None): """Creates uuid arg to pass to make_agent_call and calls it.""" + if not vm_ref: + vm_ref = VMHelper.lookup(self._session, instance.name) args = {'id': str(uuid.uuid4())} # TODO(tr3buchet): fix function call after refactor #resp = self._make_agent_call('resetnetwork', instance, '', args) -- cgit From e46d78218eec77f8502579496ee0922ce401e84a Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 6 Apr 2011 12:33:07 -0500 Subject: updated _prepare_injectables() to use info[gateway6] instead of looking inside the ip6 address dict for the gateway6 information --- nova/virt/xenapi/vm_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index d07d60800..886f1ec88 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -1130,7 +1130,7 @@ def _prepare_injectables(inst, networks_info): 'dns': dns, 'address_v6': ip_v6 and ip_v6['ip'] or '', 'netmask_v6': ip_v6 and ip_v6['netmask'] or '', - 'gateway_v6': ip_v6 and ip_v6['gateway'] or '', + 'gateway_v6': ip_v6 and info['gateway6'] or '', 'use_ipv6': FLAGS.use_ipv6} interfaces_info.append(interface_info) -- cgit From c18bf716f08e6b9fbdc259755cf172b5a6cf096a Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 6 Apr 2011 12:52:25 -0500 Subject: updated get_network_info in libvirt_conn to correctly insert ip6s and gateway6 into the network info, also small style fixes --- nova/virt/libvirt_conn.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 2be190256..f1fa859ed 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -169,34 +169,34 @@ def _get_network_info(instance): instance['id']) network_info = [] - def ip_dict(ip): - return { - "ip": ip.address, - "netmask": network["netmask"], - "enabled": "1"} - - def ip6_dict(ip6): - prefix = ip6.network.cidr_v6 - mac = instance.mac_address - return { - "ip": utils.to_global_ipv6(prefix, mac), - "netmask": ip6.network.netmask_v6, - "gateway": ip6.network.gateway_v6, - "enabled": "1"} - for network in networks: network_ips = [ip for ip in ip_addresses - if ip.network_id == network.id] + if ip['network_id'] == network['id']] + + def ip_dict(ip): + return { + 'ip': ip['address'], + 'netmask': network['netmask'], + 'enabled': '1'} + + def ip6_dict(): + prefix = network['cidr_v6'] + mac = instance['mac_address'] + return { + 'ip': utils.to_global_ipv6(prefix, mac), + 'netmask': network['netmask_v6'], + 'enabled': '1'} mapping = { 'label': network['label'], 'gateway': network['gateway'], - 'mac': instance.mac_address, + 'mac': instance['mac_address'], 'dns': [network['dns']], 'ips': [ip_dict(ip) for ip in network_ips]} if FLAGS.use_ipv6: - mapping['ip6s'] = [ip6_dict(ip) for ip in network_ips] + mapping['ip6s'] = [ip6_dict()] + mapping['gateway6'] = network['gateway_v6'], network_info.append((network, mapping)) return network_info -- cgit From d5c077131e00f41a38fa03fdbea46aa4351f95b5 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 6 Apr 2011 13:05:39 -0500 Subject: updated check_vm_record in test_xenapi to check the gateway6 correctly --- nova/tests/test_xenapi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 17e3f55e9..42fe8f3fa 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -289,11 +289,11 @@ class XenAPIVMTestCase(test.TestCase): 'enabled':'1'}], 'ip6s': [{'ip': 'fe80::a8bb:ccff:fedd:eeff', 'netmask': '120', - 'enabled': '1', - 'gateway': 'fe80::a00:1'}], + 'enabled': '1'}], 'mac': 'aa:bb:cc:dd:ee:ff', 'dns': ['10.0.0.2'], - 'gateway': '10.0.0.1'}) + 'gateway': '10.0.0.1', + 'gateway6': 'fe80::a00:1'}) def check_vm_params_for_windows(self): self.assertEquals(self.vm['platform']['nx'], 'true') -- cgit From d3fec5c2c3de2d3a1ef0fd1fd809ff248b6df5a8 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 6 Apr 2011 13:31:51 -0500 Subject: syntax error --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index f1fa859ed..50b09d19b 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -196,7 +196,7 @@ def _get_network_info(instance): if FLAGS.use_ipv6: mapping['ip6s'] = [ip6_dict()] - mapping['gateway6'] = network['gateway_v6'], + mapping['gateway6'] = network['gateway_v6'] network_info.append((network, mapping)) return network_info -- cgit From c649c8d5febab7d0dfa329bc5d78f0147383c5ee Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 6 Apr 2011 11:36:08 -0700 Subject: Use a more descriptive name for the flag to make it easier to understand the purpose --- nova/virt/xenapi/vm_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index d29456ec6..cf2adb44c 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -49,7 +49,7 @@ LOG = logging.getLogger("nova.virt.xenapi.vm_utils") FLAGS = flags.FLAGS flags.DEFINE_string('default_os_type', 'linux', 'Default OS type') -flags.DEFINE_integer('timeout_block', 10, +flags.DEFINE_integer('block_device_creation_timeout', 10, 'time to wait for a block device to be created') XENAPI_POWER_STATE = { @@ -900,7 +900,7 @@ def remap_vbd_dev(dev): def _wait_for_device(dev): """Wait for device node to appear""" - for i in xrange(0, FLAGS.timeout_block): + for i in xrange(0, FLAGS.block_device_creation_timeout): if os.path.exists('/dev/%s' % dev): return time.sleep(1) -- cgit From 560d36e7ad87ca7e8f8619e146ed4965f33dd391 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 6 Apr 2011 13:43:02 -0500 Subject: another syntax error --- nova/virt/xenapi/vmops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index f02beda24..30754b7b6 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -832,7 +832,7 @@ class VMOps(object): if network['cidr_v6']: info['ip6s'] = [ip6_dict()] if network['gateway_v6']: - info['gateway6'] = network['gateway_v6'], + info['gateway6'] = network['gateway_v6'] network_info.append((network, info)) return network_info -- cgit From cd279bec6391ef40d278f377f0039b507b14904b Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 6 Apr 2011 12:58:57 -0700 Subject: check visibility on delete and update --- nova/image/glance.py | 4 ++++ nova/image/local.py | 6 +++++- nova/image/s3.py | 4 ---- 3 files changed, 9 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index f848f40d8..bf49ca96c 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -151,6 +151,8 @@ class GlanceImageService(service.BaseImageService): :raises NotFound if the image does not exist. """ + # NOTE(vish): show is to check if image is available + self.show(context, image_id) try: image_meta = self.client.update_image(image_id, image_meta, data) except glance_exception.NotFound: @@ -165,6 +167,8 @@ class GlanceImageService(service.BaseImageService): :raises NotFound if the image does not exist. """ + # NOTE(vish): show is to check if image is available + self.show(context, image_id) try: result = self.client.delete_image(image_id) except glance_exception.NotFound: diff --git a/nova/image/local.py b/nova/image/local.py index bad4c2d85..8bf78b4c9 100644 --- a/nova/image/local.py +++ b/nova/image/local.py @@ -126,6 +126,8 @@ class LocalImageService(service.BaseImageService): def update(self, context, image_id, metadata, data=None): """Replace the contents of the given image with the new data.""" + # NOTE(vish): show is to check if image is available + self.show(context, image_id) metadata['id'] = image_id try: if data: @@ -143,9 +145,11 @@ class LocalImageService(service.BaseImageService): def delete(self, context, image_id): """Delete the given image. - Raises OSError if the image does not exist. + Raises NotFound if the image does not exist. """ + # NOTE(vish): show is to check if image is available + self.show(context, image_id) try: shutil.rmtree(self._path_to(image_id, None)) except (IOError, ValueError): diff --git a/nova/image/s3.py b/nova/image/s3.py index a7301eefd..554760d53 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -59,13 +59,9 @@ class S3ImageService(service.BaseImageService): return image def delete(self, context, image_id): - # FIXME(vish): call to show is to check visibility - self.show(context, image_id) self.service.delete(context, image_id) def update(self, context, image_id, metadata, data=None): - # FIXME(vish): call to show is to check visibility - self.show(context, image_id) image = self.service.update(context, image_id, metadata, data) return image -- cgit From 5534113f65cc578dcdf93432981b836bf4e3dfaf Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Wed, 6 Apr 2011 16:12:32 -0400 Subject: Added support for listing addresses of a server in the openstack api. Now you can GET * /servers/1/ips * /servers/1/ips/public * /servers/1/ips/private Supports v1.0 json and xml. Added corresponding tests. --- nova/api/openstack/__init__.py | 6 +++ nova/api/openstack/ips.py | 71 +++++++++++++++++++++++++++++ nova/api/openstack/servers.py | 9 ---- nova/api/openstack/views/addresses.py | 10 +++- nova/tests/api/openstack/test_servers.py | 78 ++++++++++++++++++++++++++++++++ 5 files changed, 163 insertions(+), 11 deletions(-) create mode 100644 nova/api/openstack/ips.py (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 7545eb0c9..5e76a06f7 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -34,6 +34,7 @@ from nova.api.openstack import consoles from nova.api.openstack import flavors from nova.api.openstack import images from nova.api.openstack import image_metadata +from nova.api.openstack import ips from nova.api.openstack import limits from nova.api.openstack import servers from nova.api.openstack import server_metadata @@ -144,6 +145,11 @@ class APIRouterV10(APIRouter): parent_resource=dict(member_name='server', collection_name='servers')) + mapper.resource("ip", "ips", controller=ips.Controller(), + collection=dict(public='GET', private='GET'), + 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/ips.py b/nova/api/openstack/ips.py new file mode 100644 index 000000000..89b0936d5 --- /dev/null +++ b/nova/api/openstack/ips.py @@ -0,0 +1,71 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# 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. + +import time + +from webob import exc + +import nova +import nova.api.openstack.views.addresses +from nova.api.openstack import faults + + +class Controller(nova.wsgi.Controller): + """The servers addresses API controller for the Openstack API.""" + + _serialization_metadata = { + 'application/xml': { + 'list_collections': { + 'public': {'item_name': 'ip', 'item_key': 'addr'}, + 'private': {'item_name': 'ip', 'item_key': 'addr'}, + }, + }, + } + + def __init__(self): + self.compute_api = nova.compute.API() + self.builder = nova.api.openstack.views.addresses.ViewBuilderV10() + + def index(self, req, server_id): + try: + instance = self.compute_api.get(req.environ['nova.context'], id) + except nova.exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + return {'addresses': self.builder.build(instance)} + + def public(self, req, server_id): + try: + instance = self.compute_api.get(req.environ['nova.context'], id) + except nova.exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + return {'public': self.builder.build_public_parts(instance)} + + def private(self, req, server_id): + try: + instance = self.compute_api.get(req.environ['nova.context'], id) + except nova.exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + return {'private': self.builder.build_private_parts(instance)} + + def show(self, req, server_id, id): + return faults.Fault(exc.HTTPNotImplemented()) + + def create(self, req, server_id): + return faults.Fault(exc.HTTPNotImplemented()) + + def delete(self, req, server_id, id): + return faults.Fault(exc.HTTPNotImplemented()) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index cada92813..67e6eefd8 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -70,15 +70,6 @@ class Controller(wsgi.Controller): self._image_service = utils.import_object(FLAGS.image_service) super(Controller, self).__init__() - def ips(self, req, id): - try: - instance = self.compute_api.get(req.environ['nova.context'], id) - except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) - - builder = self._get_addresses_view_builder(req) - return builder.build(instance) - def index(self, req): """ Returns a list of server names and ids for a given user """ return self._items(req, is_detail=False) diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py index 90c77855b..2810cce39 100644 --- a/nova/api/openstack/views/addresses.py +++ b/nova/api/openstack/views/addresses.py @@ -28,10 +28,16 @@ class ViewBuilder(object): class ViewBuilderV10(ViewBuilder): def build(self, inst): - private_ips = utils.get_from_path(inst, 'fixed_ip/address') - public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') + private_ips = self.build_private_parts(inst) + public_ips = self.build_public_parts(inst) return dict(public=public_ips, private=private_ips) + def build_public_parts(self, inst): + return utils.get_from_path(inst, 'fixed_ip/floating_ips/address') + + def build_private_parts(self, inst): + return utils.get_from_path(inst, 'fixed_ip/address') + class ViewBuilderV11(ViewBuilder): def build(self, inst): diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index a424a8105..640418cfa 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -228,6 +228,84 @@ class ServersTest(test.TestCase): self.assertEqual(len(addresses["private"]), 1) self.assertEqual(addresses["private"][0], private) + def test_get_server_addresses_V10(self): + private = '192.168.0.3' + public = ['1.2.3.4'] + new_return_server = return_server_with_addresses(private, public) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1/ips') + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(res_dict, { + 'addresses': {'public': public, 'private': [private]}}) + + def test_get_server_addresses_xml_V10(self): + private_expected = "192.168.0.3" + public_expected = ["1.2.3.4"] + new_return_server = return_server_with_addresses(private_expected, + public_expected) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1/ips') + req.headers['Accept'] = 'application/xml' + res = req.get_response(fakes.wsgi_app()) + dom = minidom.parseString(res.body) + (addresses,) = dom.childNodes + self.assertEquals(addresses.nodeName, 'addresses') + (public,) = addresses.getElementsByTagName('public') + (ip,) = public.getElementsByTagName('ip') + self.assertEquals(ip.getAttribute('addr'), public_expected[0]) + (private,) = addresses.getElementsByTagName('private') + (ip,) = private.getElementsByTagName('ip') + self.assertEquals(ip.getAttribute('addr'), private_expected) + + def test_get_server_addresses_public_V10(self): + private = "192.168.0.3" + public = ["1.2.3.4"] + new_return_server = return_server_with_addresses(private, public) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1/ips/public') + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(res_dict, {'public': public}) + + def test_get_server_addresses_private_V10(self): + private = "192.168.0.3" + public = ["1.2.3.4"] + new_return_server = return_server_with_addresses(private, public) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1/ips/private') + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(res_dict, {'private': [private]}) + + def test_get_server_addresses_public_xml_V10(self): + private = "192.168.0.3" + public = ["1.2.3.4"] + new_return_server = return_server_with_addresses(private, public) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1/ips/public') + req.headers['Accept'] = 'application/xml' + res = req.get_response(fakes.wsgi_app()) + dom = minidom.parseString(res.body) + (public_node,) = dom.childNodes + self.assertEquals(public_node.nodeName, 'public') + (ip,) = public_node.getElementsByTagName('ip') + self.assertEquals(ip.getAttribute('addr'), public[0]) + + def test_get_server_addresses_private_xml_V10(self): + private = "192.168.0.3" + public = ["1.2.3.4"] + new_return_server = return_server_with_addresses(private, public) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1/ips/private') + req.headers['Accept'] = 'application/xml' + res = req.get_response(fakes.wsgi_app()) + dom = minidom.parseString(res.body) + (private_node,) = dom.childNodes + self.assertEquals(private_node.nodeName, 'private') + (ip,) = private_node.getElementsByTagName('ip') + self.assertEquals(ip.getAttribute('addr'), private) + def test_get_server_by_id_with_addresses_v11(self): private = "192.168.0.3" public = ["1.2.3.4"] -- cgit From e16571671e4baab50521870ec64e4ab954c7d165 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Wed, 6 Apr 2011 17:50:11 -0400 Subject: Controllers now inherit from nova.api.openstack.common.OpenstackController. --- nova/api/openstack/ips.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py index 89b0936d5..778e9ba1a 100644 --- a/nova/api/openstack/ips.py +++ b/nova/api/openstack/ips.py @@ -21,10 +21,11 @@ from webob import exc import nova import nova.api.openstack.views.addresses +from nova.api.openstack import common from nova.api.openstack import faults -class Controller(nova.wsgi.Controller): +class Controller(common.OpenstackController): """The servers addresses API controller for the Openstack API.""" _serialization_metadata = { -- cgit From 040428cc35aa046de4bcf744c95b7c507df94550 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 6 Apr 2011 14:53:35 -0700 Subject: Add automatic metadata ip to network host on start. Also fix race where gw is readded twice --- nova/network/linux_net.py | 13 ++++++++++++- nova/network/manager.py | 3 +++ 2 files changed, 15 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index d11d21dad..6a7051d10 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -391,6 +391,13 @@ def unbind_floating_ip(floating_ip): 'dev', FLAGS.public_interface) +def ensure_metadata_ip(interface): + """Sets up local metadata ip""" + _execute('sudo', 'ip', 'addr', 'add', '169.254.169.254/32', + 'scope', 'link', 'dev', interface, + check_exit_code=False) + + def ensure_vlan_forward(public_ip, port, private_ip): """Sets up forwarding rules for vlan""" iptables_manager.ipv4['filter'].add_rule("FORWARD", @@ -504,7 +511,11 @@ def ensure_bridge(bridge, interface, net_attrs=None): _execute(*_ip_bridge_cmd('del', params, fields[-1])) _execute(*_ip_bridge_cmd('add', params, bridge)) if gateway: - _execute('sudo', 'route', 'add', '0.0.0.0', 'gw', gateway) + # NOTE(vish): If the gateway already exists we are fine + out, err = _execute('sudo', 'route', 'add', '0.0.0.0', 'gw', + gateway, check_exit_code=False) + if err and err != "SIOCADDRT: File exists\n": + raise exception.Error("Failed to reset gateway: %s" % err) out, err = _execute('sudo', 'brctl', 'addif', bridge, interface, check_exit_code=False) diff --git a/nova/network/manager.py b/nova/network/manager.py index 86ee4fc00..a3c345b69 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -100,6 +100,8 @@ flags.DEFINE_string('network_host', socket.gethostname(), 'Network host to use for ip allocation in flat modes') flags.DEFINE_bool('fake_call', False, 'If True, skip using the queue and make local calls') +flags.DEFINE_string('metadata_interface', 'eth0', + 'interface to add the metadata ip to') class AddressAlreadyAllocated(exception.Error): @@ -128,6 +130,7 @@ class NetworkManager(manager.SchedulerDependentManager): self.driver.init_host() # Set up networking for the projects for which we're already # the designated network host. + self.driver.ensure_metadata_ip(FLAGS.metadata_interface) ctxt = context.get_admin_context() for network in self.db.host_get_networks(ctxt, self.host): self._on_set_network_host(ctxt, network['id']) -- cgit From dc8b3cb3fa124755ed3e5282b2d11a811f1db2d5 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Wed, 6 Apr 2011 15:39:44 -0700 Subject: removed comment on API compliance --- nova/api/openstack/views/servers.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index baa911590..096bf70c6 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -82,7 +82,6 @@ class ViewBuilder(object): # Return the metadata as a dictionary metadata = {} for item in inst.get('metadata', []): - # metadata values must be strings per API metadata[item['key']] = str(item['value']) inst_dict['metadata'] = metadata -- cgit From 3831008b4e8aeec08b58afb49e40428ad5ece5b1 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 6 Apr 2011 16:10:08 -0700 Subject: if we delete the old route when we move it we don't need to check for exists --- nova/network/linux_net.py | 9 ++++----- nova/network/manager.py | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 6a7051d10..a4d312f02 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -449,6 +449,7 @@ def ensure_vlan(vlan_num): return interface +@utils.synchronized('ensure_bridge', external=True) def ensure_bridge(bridge, interface, net_attrs=None): """Create a bridge unless it already exists. @@ -502,6 +503,8 @@ def ensure_bridge(bridge, interface, net_attrs=None): fields = line.split() if fields and fields[0] == "0.0.0.0" and fields[-1] == interface: gateway = fields[1] + _execute('sudo', 'route', 'del', 'default', 'gw', gateway, + 'dev', interface) out, err = _execute('sudo', 'ip', 'addr', 'show', 'dev', interface, 'scope', 'global') for line in out.split("\n"): @@ -511,11 +514,7 @@ def ensure_bridge(bridge, interface, net_attrs=None): _execute(*_ip_bridge_cmd('del', params, fields[-1])) _execute(*_ip_bridge_cmd('add', params, bridge)) if gateway: - # NOTE(vish): If the gateway already exists we are fine - out, err = _execute('sudo', 'route', 'add', '0.0.0.0', 'gw', - gateway, check_exit_code=False) - if err and err != "SIOCADDRT: File exists\n": - raise exception.Error("Failed to reset gateway: %s" % err) + _execute('sudo', 'route', 'add', 'default', 'gw', gateway) out, err = _execute('sudo', 'brctl', 'addif', bridge, interface, check_exit_code=False) diff --git a/nova/network/manager.py b/nova/network/manager.py index a3c345b69..b80560bdf 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -128,9 +128,9 @@ class NetworkManager(manager.SchedulerDependentManager): standalone service. """ self.driver.init_host() + self.driver.ensure_metadata_ip(FLAGS.metadata_interface) # Set up networking for the projects for which we're already # the designated network host. - self.driver.ensure_metadata_ip(FLAGS.metadata_interface) ctxt = context.get_admin_context() for network in self.db.host_get_networks(ctxt, self.host): self._on_set_network_host(ctxt, network['id']) -- cgit From d7e8d91d6bc4954f2d256f57e34444b5bd170ab0 Mon Sep 17 00:00:00 2001 From: Kei Masumoto Date: Thu, 7 Apr 2011 13:08:14 +0900 Subject: fixed based on reviewer's comment - 'locals() should be off from _() --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 37a904b1e..847c3655a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1096,7 +1096,7 @@ class ComputeManager(manager.SchedulerDependentManager): # this case should be ignored. LOG.info(_("the instance '%(name)s' is not found in hypervisor" ", while db record is found. But not synchronize " - "since it is migrating." % locals())) + "since it is migrating.") % locals()) continue if vm_state != db_state: -- cgit From 2bc0e744162276048ddd9c1a1eeacbd647cda6f4 Mon Sep 17 00:00:00 2001 From: Kei Masumoto Date: Thu, 7 Apr 2011 13:32:19 +0900 Subject: fixed based on reviewer's comment - 1. erase unnecessary blank line, 2. adding LOG.debug --- nova/virt/libvirt_conn.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index bdf577825..eab54c53e 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1849,7 +1849,6 @@ class NWFilterFirewall(FirewallDriver): def instance_filter_exists(self, instance): """Check nova-instance-instance-xxx exists""" - network_info = _get_network_info(instance) for (network, mapping) in network_info: nic_id = mapping['mac'].replace(':', '') @@ -1857,6 +1856,9 @@ class NWFilterFirewall(FirewallDriver): try: self._conn.nwfilterLookupByName(instance_filter_name) except libvirt.libvirtError: + name = instance.name + LOG.debug(_('The nwfilter(%(instance_filter_name)s) for' + '%(name)s is not found.') % locals()) return False return True -- cgit From 7cf0deda8f7ab410005c556779353d599c8e8a63 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 7 Apr 2011 10:34:14 -0300 Subject: adds timeout to login_with_password --- nova/virt/xenapi_conn.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 99fd35c61..6dfe0b9a9 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -63,6 +63,7 @@ import xmlrpclib from eventlet import event from eventlet import tpool +from eventlet import timeout from nova import context from nova import db @@ -140,7 +141,9 @@ flags.DEFINE_bool('xenapi_remap_vbd_dev', False, flags.DEFINE_string('xenapi_remap_vbd_dev_prefix', 'sd', 'Specify prefix to remap VBD dev to ' '(ex. /dev/xvdb -> /dev/sdb)') - +flags.DEFINE_integer('xenapi_login_timeout', + 10, + 'Timeout in seconds for XenAPI login.') def get_connection(_): """Note that XenAPI doesn't have a read-only connection mode, so @@ -318,7 +321,9 @@ class XenAPISession(object): def __init__(self, url, user, pw): self.XenAPI = self.get_imported_xenapi() self._session = self._create_session(url) - self._session.login_with_password(user, pw) + exception = self.XenAPI.Failure(_("Unable to log in to XenAPI.")) + with timeout.Timeout(FLAGS.xenapi_login_timeout, exception): + self._session.login_with_password(user, pw) self.loop = None def get_imported_xenapi(self): -- cgit From 9b24c399c5689a1492b96dcd6725590c2a97c6e3 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 7 Apr 2011 10:42:29 -0300 Subject: pep8 --- nova/virt/xenapi_conn.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 6dfe0b9a9..f10aa6eb5 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -145,6 +145,7 @@ flags.DEFINE_integer('xenapi_login_timeout', 10, 'Timeout in seconds for XenAPI login.') + def get_connection(_): """Note that XenAPI doesn't have a read-only connection mode, so the read_only parameter is ignored.""" -- cgit From 9f57f78efab4a31bfe29e2edab1e86eedf4352fd Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 7 Apr 2011 11:59:40 -0300 Subject: better error message --- nova/virt/xenapi_conn.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index f10aa6eb5..0cabccf08 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -322,7 +322,8 @@ class XenAPISession(object): def __init__(self, url, user, pw): self.XenAPI = self.get_imported_xenapi() self._session = self._create_session(url) - exception = self.XenAPI.Failure(_("Unable to log in to XenAPI.")) + exception = self.XenAPI.Failure(_("Unable to log in to XenAPI " + "(is the Dom0 disk full?)")) with timeout.Timeout(FLAGS.xenapi_login_timeout, exception): self._session.login_with_password(user, pw) self.loop = None -- cgit From 8da3e6c19b97ab7cd08e69fb0df114653c0b90db Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 7 Apr 2011 10:37:40 -0700 Subject: Simplify by always adding to loopback --- nova/network/linux_net.py | 5 ++--- nova/network/manager.py | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index a4d312f02..ed6c943c7 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -391,11 +391,10 @@ def unbind_floating_ip(floating_ip): 'dev', FLAGS.public_interface) -def ensure_metadata_ip(interface): +def ensure_metadata_ip(): """Sets up local metadata ip""" _execute('sudo', 'ip', 'addr', 'add', '169.254.169.254/32', - 'scope', 'link', 'dev', interface, - check_exit_code=False) + 'scope', 'link', 'dev', 'lo', check_exit_code=False) def ensure_vlan_forward(public_ip, port, private_ip): diff --git a/nova/network/manager.py b/nova/network/manager.py index b80560bdf..0dd7f2360 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -100,8 +100,6 @@ flags.DEFINE_string('network_host', socket.gethostname(), 'Network host to use for ip allocation in flat modes') flags.DEFINE_bool('fake_call', False, 'If True, skip using the queue and make local calls') -flags.DEFINE_string('metadata_interface', 'eth0', - 'interface to add the metadata ip to') class AddressAlreadyAllocated(exception.Error): @@ -128,7 +126,7 @@ class NetworkManager(manager.SchedulerDependentManager): standalone service. """ self.driver.init_host() - self.driver.ensure_metadata_ip(FLAGS.metadata_interface) + self.driver.ensure_metadata_ip() # Set up networking for the projects for which we're already # the designated network host. ctxt = context.get_admin_context() -- cgit From 404feb59a829e24e026f793a362e54aad1aaa03f Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 7 Apr 2011 13:55:42 -0500 Subject: Renamed computeFault to cloudServersFault --- nova/api/openstack/faults.py | 2 +- nova/tests/api/openstack/test_api.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index bc97639a0..87118ce19 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -47,7 +47,7 @@ class Fault(webob.exc.HTTPException): """Generate a WSGI response based on the exception passed to ctor.""" # Replace the body with fault details. code = self.wrapped_exc.status_int - fault_name = self._fault_names.get(code, "computeFault") + fault_name = self._fault_names.get(code, "cloudServersFault") fault_data = { fault_name: { 'code': code, diff --git a/nova/tests/api/openstack/test_api.py b/nova/tests/api/openstack/test_api.py index 5112c486f..c63431a45 100644 --- a/nova/tests/api/openstack/test_api.py +++ b/nova/tests/api/openstack/test_api.py @@ -53,13 +53,13 @@ class APITest(test.TestCase): #api.application = succeed api = self._wsgi_app(succeed) resp = Request.blank('/').get_response(api) - self.assertFalse('computeFault' in resp.body, resp.body) + self.assertFalse('cloudServersFault' in resp.body, resp.body) self.assertEqual(resp.status_int, 200, resp.body) #api.application = raise_webob_exc api = self._wsgi_app(raise_webob_exc) resp = Request.blank('/').get_response(api) - self.assertFalse('computeFault' in resp.body, resp.body) + self.assertFalse('cloudServersFault' in resp.body, resp.body) self.assertEqual(resp.status_int, 404, resp.body) #api.application = raise_api_fault @@ -71,11 +71,11 @@ class APITest(test.TestCase): #api.application = fail api = self._wsgi_app(fail) resp = Request.blank('/').get_response(api) - self.assertTrue('{"computeFault' in resp.body, resp.body) + self.assertTrue('{"cloudServersFault' in resp.body, resp.body) self.assertEqual(resp.status_int, 500, resp.body) #api.application = fail api = self._wsgi_app(fail) resp = Request.blank('/.xml').get_response(api) - self.assertTrue(' Date: Thu, 7 Apr 2011 15:09:10 -0400 Subject: Drop unneeded Fkey on InstanceTypes.id. --- nova/db/sqlalchemy/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 9d4c6cdef..f79d0f16c 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -258,8 +258,7 @@ class InstanceActions(BASE, NovaBase): class InstanceTypes(BASE, NovaBase): """Represent possible instance_types or flavor of VM offered""" __tablename__ = "instance_types" - id = Column(Integer, ForeignKey('instances.instance_type_id'), - primary_key=True) + id = Column(Integer, primary_key=True) name = Column(String(255), unique=True) memory_mb = Column(Integer) vcpus = Column(Integer) -- cgit From 99e8335e9b07b1cbf9c28cda2dfb2496d955c72c Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 7 Apr 2011 15:43:51 -0400 Subject: Some i18n fixes to instance_types. --- nova/compute/instance_types.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index 5b1d92e29..b3a5ab0ac 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -59,8 +59,8 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0, rxtx_quota=rxtx_quota, rxtx_cap=rxtx_cap)) except exception.DBError, e: - LOG.exception(_('DB error: %s' % e)) - raise exception.ApiError(_("Cannot create instance type: %s" % name)) + LOG.exception(_('DB error: %s') % e) + raise exception.ApiError(_("Cannot create instance type: %s") % name) def destroy(name): @@ -72,8 +72,8 @@ def destroy(name): try: db.instance_type_destroy(context.get_admin_context(), name) except exception.NotFound: - LOG.exception(_('Instance type %s not found for deletion' % name)) - raise exception.ApiError(_("Unknown instance type: %s" % name)) + LOG.exception(_('Instance type %s not found for deletion') % name) + raise exception.ApiError(_("Unknown instance type: %s") % name) def purge(name): @@ -85,8 +85,8 @@ def purge(name): try: db.instance_type_purge(context.get_admin_context(), name) except exception.NotFound: - LOG.exception(_('Instance type %s not found for purge' % name)) - raise exception.ApiError(_("Unknown instance type: %s" % name)) + LOG.exception(_('Instance type %s not found for purge') % name) + raise exception.ApiError(_("Unknown instance type: %s") % name) def get_all_types(inactive=0): @@ -106,7 +106,7 @@ def get_default_instance_type(): try: return get_instance_type_by_name(name) except exception.DBError: - raise exception.ApiError(_("Unknown instance type: %s" % name)) + raise exception.ApiError(_("Unknown instance type: %s") % name) def get_instance_type(id): @@ -117,7 +117,7 @@ def get_instance_type(id): ctxt = context.get_admin_context() return db.instance_type_get_by_id(ctxt, id) except exception.DBError: - raise exception.ApiError(_("Unknown instance type: %s" % name)) + raise exception.ApiError(_("Unknown instance type: %s") % name) def get_instance_type_by_name(name): @@ -128,7 +128,7 @@ def get_instance_type_by_name(name): ctxt = context.get_admin_context() return db.instance_type_get_by_name(ctxt, name) except exception.DBError: - raise exception.ApiError(_("Unknown instance type: %s" % name)) + raise exception.ApiError(_("Unknown instance type: %s") % name) def get_instance_type_by_flavor_id(flavor_id): @@ -139,5 +139,5 @@ def get_instance_type_by_flavor_id(flavor_id): ctxt = context.get_admin_context() return db.instance_type_get_by_flavor_id(ctxt, flavor_id) except exception.DBError, e: - LOG.exception(_('DB error: %s' % e)) - raise exception.ApiError(_("Unknown flavor: %s" % flavor_id)) + LOG.exception(_('DB error: %s') % e) + raise exception.ApiError(_("Unknown flavor: %s") % flavor_id) -- cgit From c7fd470d7ff0df4b23664b6599e5ae5acdb21511 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 7 Apr 2011 15:52:14 -0400 Subject: Drop extra 'None' arg from dict.get call. --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index e4bcccb2b..c3124b89d 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -731,7 +731,7 @@ class CloudController(object): instance['host']) i['productCodesSet'] = self._convert_to_set([], 'product_codes') if instance['instance_type']: - i['instanceType'] = instance['instance_type'].get('name', None) + i['instanceType'] = instance['instance_type'].get('name') else: i['instanceType'] = None i['launchTime'] = instance['created_at'] -- cgit From b5310d58d418f123b2d5d2953d6b4082a70120cd Mon Sep 17 00:00:00 2001 From: Kei Masumoto Date: Fri, 8 Apr 2011 06:32:53 +0900 Subject: fix pep8 violation --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index eab54c53e..256e6e635 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1857,7 +1857,7 @@ class NWFilterFirewall(FirewallDriver): self._conn.nwfilterLookupByName(instance_filter_name) except libvirt.libvirtError: name = instance.name - LOG.debug(_('The nwfilter(%(instance_filter_name)s) for' + LOG.debug(_('The nwfilter(%(instance_filter_name)s) for' '%(name)s is not found.') % locals()) return False return True -- cgit From b54be0e29cdcd91e3d106fb587b89c39ca3a0bff Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Thu, 7 Apr 2011 15:52:27 -0700 Subject: Removed commented-out old 'delete instance on SHUTOFF' code --- nova/compute/manager.py | 6 ------ nova/virt/libvirt_conn.py | 8 +------- 2 files changed, 1 insertion(+), 13 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 72f04ecb1..d3cd2e51a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1090,12 +1090,6 @@ class ComputeManager(manager.SchedulerDependentManager): # NOTE(justinsb): We no longer auto-remove SHUTOFF instances # It's quite hard to get them back when we do. - #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: diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 533ff9394..4523cdd2f 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -232,14 +232,8 @@ class LibvirtConnection(driver.ComputeDriver): {'name': instance['name'], 'state': state}) db.instance_set_state(ctxt, instance['id'], state) - # NOTE(justinsb): We no longer delete these instances, + # NOTE(justinsb): We no longer delete SHUTOFF instances, # the user may want to power them back on - #if 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. - # db.instance_destroy(ctxt, instance['id']) if state != power_state.RUNNING: continue -- cgit From 52478e039b094861e7d783b7995b9cafa68e32b9 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Thu, 7 Apr 2011 15:56:16 -0700 Subject: Fix to correct libvirt error code when the domain is not found --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 4523cdd2f..a7a8a14b1 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -974,7 +974,7 @@ class LibvirtConnection(driver.ComputeDriver): try: virt_dom = self._conn.lookupByName(instance_name) except libvirt.libvirtError as e: - if e.get_error_code() == libvirt.VIR_ERR_UNKNOWN_HOST: + if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN: raise exception.NotFound(_("Instance %s not found") % instance_name) LOG.warning(_("Error from libvirt during lookup: %s") % e) -- cgit From 0c5f70c0bcf9395fb25a231057d997b075d04fda Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Thu, 7 Apr 2011 16:00:55 -0700 Subject: Log libvirt errcode on exception --- nova/virt/libvirt_conn.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index a7a8a14b1..5f1c12ab3 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -974,10 +974,13 @@ class LibvirtConnection(driver.ComputeDriver): try: virt_dom = self._conn.lookupByName(instance_name) except libvirt.libvirtError as e: - if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN: + errcode = e.get_error_code() + if errcode == libvirt.VIR_ERR_NO_DOMAIN: raise exception.NotFound(_("Instance %s not found") % instance_name) - LOG.warning(_("Error from libvirt during lookup: %s") % e) + LOG.warning(_("Error from libvirt during lookup. " + "Code=%(errcode)s Error=%(e)s") % + locals()) raise (state, max_mem, mem, num_cpu, cpu_time) = virt_dom.info() -- cgit From b0ad20c796bd25dad0538ab85b1a56f421e16039 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 7 Apr 2011 16:42:33 -0700 Subject: fix tests from moving access check into update and delete --- nova/image/local.py | 5 ++++- nova/tests/image/test_glance.py | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/image/local.py b/nova/image/local.py index 8bf78b4c9..d4fd62156 100644 --- a/nova/image/local.py +++ b/nova/image/local.py @@ -122,12 +122,15 @@ class LocalImageService(service.BaseImageService): image_path = self._path_to(image_id, None) if not os.path.exists(image_path): os.mkdir(image_path) - return self.update(context, image_id, metadata, data) + return self._store(context, image_id, metadata, data) def update(self, context, image_id, metadata, data=None): """Replace the contents of the given image with the new data.""" # NOTE(vish): show is to check if image is available self.show(context, image_id) + return self._store(context, image_id, metadata, data) + + def _store(self, context, image_id, metadata, data=None): metadata['id'] = image_id try: if data: diff --git a/nova/tests/image/test_glance.py b/nova/tests/image/test_glance.py index 9d0b14613..109905ded 100644 --- a/nova/tests/image/test_glance.py +++ b/nova/tests/image/test_glance.py @@ -209,17 +209,17 @@ class TestMutatorDateTimeTests(BaseGlanceTest): self.assertDateTimesEmpty(image_meta) def test_update_handles_datetimes(self): + self.client.images = {'image1': self._make_datetime_fixture()} self.client.update_response = self._make_datetime_fixture() - dummy_id = 'dummy_id' dummy_meta = {} - image_meta = self.service.update(self.context, 'dummy_id', dummy_meta) + image_meta = self.service.update(self.context, 'image1', dummy_meta) self.assertDateTimesFilled(image_meta) def test_update_handles_none_datetimes(self): + self.client.images = {'image1': self._make_datetime_fixture()} self.client.update_response = self._make_none_datetime_fixture() - dummy_id = 'dummy_id' dummy_meta = {} - image_meta = self.service.update(self.context, 'dummy_id', dummy_meta) + image_meta = self.service.update(self.context, 'image1', dummy_meta) self.assertDateTimesEmpty(image_meta) def _make_datetime_fixture(self): -- cgit From 479c95b8e855c5f07b75883a1f55b4657e886a92 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 7 Apr 2011 21:44:41 -0400 Subject: Ignore errors when deleting the default route in the ensure_bridge function. --- nova/network/linux_net.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index ed6c943c7..ec5579dee 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -503,7 +503,7 @@ def ensure_bridge(bridge, interface, net_attrs=None): if fields and fields[0] == "0.0.0.0" and fields[-1] == interface: gateway = fields[1] _execute('sudo', 'route', 'del', 'default', 'gw', gateway, - 'dev', interface) + 'dev', interface, check_exit_code=False) out, err = _execute('sudo', 'ip', 'addr', 'show', 'dev', interface, 'scope', 'global') for line in out.split("\n"): -- cgit From 7badb6c0278c8cc51fc3e870fd3810ea3706f494 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 7 Apr 2011 23:39:29 -0400 Subject: Update the describe_image_attribute and modify_image_attribute functions in the ec2 API so they use the top level 'is_public' attribute of image objects. This brings these functions in line with the base image service. Added missing EC2 unit tests on describing and modifying image attributes. --- nova/api/ec2/cloud.py | 4 ++-- nova/tests/test_cloud.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 4ed8a9ecf..651ec47f9 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -984,7 +984,7 @@ class CloudController(object): except exception.NotFound: raise exception.NotFound(_('Image %s not found') % image_id) result = {'imageId': image_id, 'launchPermission': []} - if image['properties']['is_public']: + if image['is_public']: result['launchPermission'].append({'group': 'all'}) return result @@ -1009,7 +1009,7 @@ class CloudController(object): internal_id = image['id'] del(image['id']) - image['properties']['is_public'] = (operation_type == 'add') + image['is_public'] = (operation_type == 'add') return self.image_service.update(context, internal_id, image) def update_image(self, context, image_id, **kwargs): diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 5cb969979..5f76a9005 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -247,6 +247,37 @@ class CloudTestCase(test.TestCase): self.assertRaises(NotFound, describe_images, self.context, ['ami-fake']) + def test_describe_image_attribute(self): + describe_image_attribute = self.cloud.describe_image_attribute + + def fake_show(meh, context, id): + return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, + 'type': 'machine'}, 'is_public': True} + + self.stubs.Set(local.LocalImageService, 'show', fake_show) + self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show) + result = describe_image_attribute(self.context, 'ami-00000001', + 'launchPermission') + self.assertEqual([{'group': 'all'}], result['launchPermission']) + + def test_modify_image_attribute(self): + modify_image_attribute = self.cloud.modify_image_attribute + + def fake_show(meh, context, id): + return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, + 'type': 'machine'}, 'is_public': False} + + def fake_update(meh, context, image_id, metadata, data=None): + return metadata + + self.stubs.Set(local.LocalImageService, 'show', fake_show) + self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show) + self.stubs.Set(local.LocalImageService, 'update', fake_update) + result = modify_image_attribute(self.context, 'ami-00000001', + 'launchPermission', 'add', + user_group=['all']) + self.assertEqual(True, result['is_public']) + def test_console_output(self): instance_type = FLAGS.default_instance_type max_count = 1 -- cgit From decdaa30acb15e088eb6a0ca3ebc8ea6f377cbfe Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Fri, 8 Apr 2011 12:22:09 -0400 Subject: Set default stateOrProvice to 'supplied' in openssl.cnf.tmpl. This resolves a stateOrProvince printable string UTF8 mismatch on RHEL 6 and Fedora 14 (using openssl-1.0.0-4.el6.x86_64 or openssl-1.0.0d-1.fc14.x86_64). Fixes x509 certificate generation on Fedora 14 and Redhat 6. --- nova/CA/openssl.cnf.tmpl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/CA/openssl.cnf.tmpl b/nova/CA/openssl.cnf.tmpl index dd81f1c2b..b80fadf40 100644 --- a/nova/CA/openssl.cnf.tmpl +++ b/nova/CA/openssl.cnf.tmpl @@ -41,9 +41,13 @@ nameopt = default_ca certopt = default_ca policy = policy_match +# NOTE(dprince): stateOrProvinceName must be 'supplied' or 'optional' to +# work around a stateOrProvince printable string UTF8 mismatch on +# RHEL 6 and Fedora 14 (using openssl-1.0.0-4.el6.x86_64 or +# openssl-1.0.0d-1.fc14.x86_64) [ policy_match ] countryName = match -stateOrProvinceName = match +stateOrProvinceName = supplied organizationName = optional organizationalUnitName = optional commonName = supplied -- cgit From 0cf2a52218fbb801a35e5dd73e146c6c37e218e2 Mon Sep 17 00:00:00 2001 From: Kei Masumoto Date: Sat, 9 Apr 2011 02:39:18 +0900 Subject: fixed LOG level and log message phrase --- nova/compute/manager.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 847c3655a..c7d86e27b 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1094,9 +1094,8 @@ class ComputeManager(manager.SchedulerDependentManager): # A situation which db record exists, but no instance" # sometimes occurs while live-migration at src compute, # this case should be ignored. - LOG.info(_("the instance '%(name)s' is not found in hypervisor" - ", while db record is found. But not synchronize " - "since it is migrating.") % locals()) + LOG.debug(_("Ignoring %(name)s, as it's currently being " + "migrated.") % locals()) continue if vm_state != db_state: -- cgit From 5deb4796bc26d98eeea94065c5098f7ce30ac2af Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Fri, 8 Apr 2011 11:21:36 -0700 Subject: Short circuit non-existant device during unit tests. It won't ever be created because of the stubs used during the unit tests --- nova/virt/xenapi/vm_utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 73a1e2a3a..d2045a557 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -936,7 +936,11 @@ def with_vdi_attached_here(session, vdi_ref, read_only, f): if dev != orig_dev: LOG.debug(_('VBD %(vbd_ref)s plugged into wrong dev, ' 'remapping to %(dev)s') % locals()) - _wait_for_device(dev) + if dev != 'autodetect': + # NOTE(johannes): Unit tests will end up with a device called + # 'autodetect' which obviously won't exist. It's not ideal, + # but the alternatives were much messier + _wait_for_device(dev) return f(dev) finally: LOG.debug(_('Destroying VBD for VDI %s ... '), vdi_ref) -- cgit From 5ea0991db9526969f100f3361661731aaf4d24d5 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 8 Apr 2011 15:22:15 -0400 Subject: Fixes bug which hangs nova-compute when terminating an instance when using libvirt backend. --- nova/virt/libvirt_conn.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index b949e6c92..51a0a3380 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -325,12 +325,13 @@ class LibvirtConnection(driver.ComputeDriver): state = self.get_info(instance['name'])['state'] db.instance_set_state(context.get_admin_context(), instance['id'], state) - if state == power_state.SHUTDOWN: + if state == power_state.SHUTOFF: break - except Exception: + except Exception as ex: + LOG.debug(ex) db.instance_set_state(context.get_admin_context(), instance['id'], - power_state.SHUTDOWN) + power_state.SHUTOFF) break self.firewall_driver.unfilter_instance(instance) -- cgit From a1c40feb0cd592829b63df1cf19109bc322f81a7 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 8 Apr 2011 15:54:17 -0400 Subject: Added error message to exception logging. --- nova/virt/libvirt_conn.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 51a0a3380..bfa9ff688 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -328,7 +328,9 @@ class LibvirtConnection(driver.ComputeDriver): if state == power_state.SHUTOFF: break except Exception as ex: - LOG.debug(ex) + msg = _("Error encountered when destroying instance '%(id)s': " + "%(ex)s") % locals().update({"id": instance["id"]}) + LOG.debug(msg) db.instance_set_state(context.get_admin_context(), instance['id'], power_state.SHUTOFF) -- cgit From 2800f931e134e7d6b316bb4d7f7118162c301ca9 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Fri, 8 Apr 2011 14:53:54 -0700 Subject: zero out volumes on delete using dd --- nova/volume/driver.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'nova') diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 850893914..906a9654b 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -112,6 +112,11 @@ class VolumeDriver(object): # If the volume isn't present, then don't attempt to delete return True + # zero out old volumes to prevent data leaking between users + # TODO(ja): reclaiming space should be done lazy and low priority + self._try_execute('sudo', 'dd', 'if=/dev/zero', + 'of=%s' % self.local_path(volume), + 'bs=1M') self._try_execute('sudo', 'lvremove', '-f', "%s/%s" % (FLAGS.volume_group, volume['name'])) -- cgit From df4c269a338a9b983488ce4d5b86c829a92d471b Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Fri, 8 Apr 2011 15:17:52 -0700 Subject: dd needs a count to succeed, and remove unused/non-working special case for size 0 --- nova/volume/driver.py | 11 +++-------- nova/volume/san.py | 10 ++-------- 2 files changed, 5 insertions(+), 16 deletions(-) (limited to 'nova') diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 906a9654b..43792b791 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -93,10 +93,7 @@ class VolumeDriver(object): def create_volume(self, volume): """Creates a logical volume. Can optionally return a Dictionary of changes to the volume object to be persisted.""" - if int(volume['size']) == 0: - sizestr = '100M' - else: - sizestr = '%sG' % volume['size'] + sizestr = '%sG' % volume['size'] self._try_execute('sudo', 'lvcreate', '-L', sizestr, '-n', volume['name'], FLAGS.volume_group) @@ -116,6 +113,7 @@ class VolumeDriver(object): # TODO(ja): reclaiming space should be done lazy and low priority self._try_execute('sudo', 'dd', 'if=/dev/zero', 'of=%s' % self.local_path(volume), + 'count=%d' % volume['size'] * 1024, 'bs=1M') self._try_execute('sudo', 'lvremove', '-f', "%s/%s" % (FLAGS.volume_group, @@ -601,10 +599,7 @@ class SheepdogDriver(VolumeDriver): def create_volume(self, volume): """Creates a sheepdog volume""" - if int(volume['size']) == 0: - sizestr = '100M' - else: - sizestr = '%sG' % volume['size'] + sizestr = '%sG' % volume['size'] self._try_execute('qemu-img', 'create', "sheepdog:%s" % volume['name'], sizestr) diff --git a/nova/volume/san.py b/nova/volume/san.py index 9532c8116..dd332de1a 100644 --- a/nova/volume/san.py +++ b/nova/volume/san.py @@ -219,10 +219,7 @@ class SolarisISCSIDriver(SanISCSIDriver): def create_volume(self, volume): """Creates a volume.""" - if int(volume['size']) == 0: - sizestr = '100M' - else: - sizestr = '%sG' % volume['size'] + sizestr = '%sG' % volume['size'] zfs_poolname = self._build_zfs_poolname(volume) @@ -489,10 +486,7 @@ class HpSanISCSIDriver(SanISCSIDriver): #TODO(justinsb): Should we default to inheriting thinProvision? cliq_args['thinProvision'] = '1' if FLAGS.san_thin_provision else '0' cliq_args['volumeName'] = volume['name'] - if int(volume['size']) == 0: - cliq_args['size'] = '100MB' - else: - cliq_args['size'] = '%sGB' % volume['size'] + cliq_args['size'] = '%sGB' % volume['size'] self._cliq_run_xml("createVolume", cliq_args) -- cgit From 7f04c6f5165ea96f22ec17bdbe7a3f2a7595edb1 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Fri, 8 Apr 2011 15:23:17 -0700 Subject: typo - need to get nova-volumes working on this machine :-/ --- nova/volume/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 43792b791..f9c268191 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -113,7 +113,7 @@ class VolumeDriver(object): # TODO(ja): reclaiming space should be done lazy and low priority self._try_execute('sudo', 'dd', 'if=/dev/zero', 'of=%s' % self.local_path(volume), - 'count=%d' % volume['size'] * 1024, + 'count=%d' % (volume['size'] * 1024), 'bs=1M') self._try_execute('sudo', 'lvremove', '-f', "%s/%s" % (FLAGS.volume_group, -- cgit From 5aab609b24140622b87db970243641ec382b214e Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Fri, 8 Apr 2011 15:24:09 -0700 Subject: pylintage --- nova/volume/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/volume/driver.py b/nova/volume/driver.py index f9c268191..0c4086039 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -111,7 +111,7 @@ class VolumeDriver(object): # zero out old volumes to prevent data leaking between users # TODO(ja): reclaiming space should be done lazy and low priority - self._try_execute('sudo', 'dd', 'if=/dev/zero', + self._try_execute('sudo', 'dd', 'if=/dev/zero', 'of=%s' % self.local_path(volume), 'count=%d' % (volume['size'] * 1024), 'bs=1M') -- cgit From 79ebe165f255037b0d5eaad7afe81b51cf85ed63 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Sat, 9 Apr 2011 11:08:47 -0400 Subject: Fixed log message gaffe. --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index bfa9ff688..9c665ab15 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -329,7 +329,7 @@ class LibvirtConnection(driver.ComputeDriver): break except Exception as ex: msg = _("Error encountered when destroying instance '%(id)s': " - "%(ex)s") % locals().update({"id": instance["id"]}) + "%(ex)s") % {"id": instance["id"], "ex": ex} LOG.debug(msg) db.instance_set_state(context.get_admin_context(), instance['id'], -- cgit From a572b49e376cd6da4265c2807eaed8f0a2daf954 Mon Sep 17 00:00:00 2001 From: Justin SB Date: Sat, 9 Apr 2011 11:57:14 -0700 Subject: Remove the XML definition when we destroy a machine --- nova/virt/libvirt_conn.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index b949e6c92..1ad6f8b32 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -309,10 +309,18 @@ class LibvirtConnection(driver.ComputeDriver): return infos def destroy(self, instance, cleanup=True): + name = instance['name'] try: - virt_dom = self._conn.lookupByName(instance['name']) + virt_dom = self._conn.lookupByName(name) virt_dom.destroy() - except Exception as _err: + # NOTE(justinsb): We remove the domain definition. We probably + # would do better to keep it if cleanup=False (e.g. volumes?) + # (e.g. #2 - not losing machines on failure) + virt_dom.undefine() + except Exception as e: + # TODO(justinsb): We really should check the error is 'not found' + LOG.warn(_("Ignoring error destroying domain %(name)s: %(e)s") % + locals()) pass # If the instance is already terminated, we're still happy -- cgit From be386ee614777212da2a14ebd8211f4b3d90ce66 Mon Sep 17 00:00:00 2001 From: Justin SB Date: Sat, 9 Apr 2011 12:33:24 -0700 Subject: Split logic on shutdown and undefine, so that even if the machine is already shutdown we will be able to proceed --- nova/virt/libvirt_conn.py | 53 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 13 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 1ad6f8b32..47eb17abb 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -309,20 +309,47 @@ class LibvirtConnection(driver.ComputeDriver): return infos def destroy(self, instance, cleanup=True): - name = instance['name'] + instance_name = instance['name'] + + # TODO(justinsb): Refactor all lookupByName calls for error-handling try: - virt_dom = self._conn.lookupByName(name) - virt_dom.destroy() - # NOTE(justinsb): We remove the domain definition. We probably - # would do better to keep it if cleanup=False (e.g. volumes?) - # (e.g. #2 - not losing machines on failure) - virt_dom.undefine() - except Exception as e: - # TODO(justinsb): We really should check the error is 'not found' - LOG.warn(_("Ignoring error destroying domain %(name)s: %(e)s") % - locals()) - pass - # If the instance is already terminated, we're still happy + virt_dom = self._conn.lookupByName(instance_name) + except libvirt.libvirtError as e: + errcode = e.get_error_code() + if errcode == libvirt.VIR_ERR_NO_DOMAIN: + virt_dom = None + else: + LOG.warning(_("Error from libvirt during lookup of " + "%(instance_name)s. Code=%(errcode)s " + "Error=%(e)s") % + locals()) + raise + + # If the instance is already terminated, we're still happy + # Otherwise, destroy it + if virt_dom is not None: + try: + virt_dom.destroy() + except libvirt.libvirtError as e: + errcode = e.get_error_code() + LOG.warning(_("Error from libvirt during destroy of " + "%(instance_name)s. Code=%(errcode)s " + "Error=%(e)s") % + locals()) + raise + + try: + # NOTE(justinsb): We remove the domain definition. We probably + # would do better to keep it if cleanup=False (e.g. volumes?) + # (e.g. #2 - not losing machines on failure) + virt_dom.undefine() + except libvirt.libvirtError as e: + errcode = e.get_error_code() + LOG.warning(_("Error from libvirt during undefine of " + "%(instance_name)s. Code=%(errcode)s " + "Error=%(e)s") % + locals()) + raise # We'll save this for when we do shutdown, # instead of destroy - but destroy returns immediately -- cgit From c6923ec603288e1d46fdb80e874c8e71361442f5 Mon Sep 17 00:00:00 2001 From: Justin SB Date: Sat, 9 Apr 2011 12:41:30 -0700 Subject: Handle the case when the machine is already SHUTOFF --- nova/virt/libvirt_conn.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 47eb17abb..7771aad7a 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -331,12 +331,22 @@ class LibvirtConnection(driver.ComputeDriver): try: virt_dom.destroy() except libvirt.libvirtError as e: + is_okay = False errcode = e.get_error_code() - LOG.warning(_("Error from libvirt during destroy of " - "%(instance_name)s. Code=%(errcode)s " - "Error=%(e)s") % - locals()) - raise + if errcode == libvirt.VIR_ERR_OPERATION_INVALID: + # If the instance if already shut off, we get this: + # Code=55 Error=Requested operation is not valid: + # domain is not running + (state, _, _, _, _) = virt_dom.info() + if state == power_state.SHUTOFF: + is_okay = True + + if not is_okay: + LOG.warning(_("Error from libvirt during destroy of " + "%(instance_name)s. Code=%(errcode)s " + "Error=%(e)s") % + locals()) + raise try: # NOTE(justinsb): We remove the domain definition. We probably -- cgit From f59f8e8fcbde6f0d8d4c19b00bfc5f4141287772 Mon Sep 17 00:00:00 2001 From: Justin SB Date: Sat, 9 Apr 2011 12:57:32 -0700 Subject: Ooops - redefining the _ variable seems like a _really_ bad idea --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 7771aad7a..22b0e1103 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -337,7 +337,7 @@ class LibvirtConnection(driver.ComputeDriver): # If the instance if already shut off, we get this: # Code=55 Error=Requested operation is not valid: # domain is not running - (state, _, _, _, _) = virt_dom.info() + (state, _max_mem, _mem, _cpus, _t) = virt_dom.info() if state == power_state.SHUTOFF: is_okay = True -- cgit From 5752838917237e7b86a64117f46c71d1c2a356f3 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 11 Apr 2011 16:18:18 +0200 Subject: Replace instance ref from compute.api.get_all with one from instance_get. This should ensure it gets fully populated with all the relevant attributes. --- nova/api/ec2/cloud.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 651ec47f9..83c894bea 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -142,6 +142,11 @@ class CloudController(object): instance_ref = self.compute_api.get_all(ctxt, fixed_ip=address) if instance_ref is None: return None + + # This ensures that all attributes of the instance + # are populated. + instance_ref = db.instance_get(ctxt, instance_ref['id']) + mpi = self._get_mpi_data(ctxt, instance_ref['project_id']) if instance_ref['key_name']: keys = {'0': {'_name': instance_ref['key_name'], -- cgit From 21fd04c34487b97f7d1ed199773cf80e9ab60839 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Mon, 11 Apr 2011 15:56:36 +0000 Subject: fix reference to genvpn to point to the right shell script --- nova/crypto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/crypto.py b/nova/crypto.py index 9b1897926..605be2a32 100644 --- a/nova/crypto.py +++ b/nova/crypto.py @@ -232,7 +232,7 @@ def generate_vpn_files(project_id): genvpn_sh_path = os.path.join(os.path.dirname(__file__), 'CA', - 'geninter.sh') + 'genvpn.sh') if os.path.exists(crt_fn): return _ensure_project_folder(project_id) -- cgit From 6ac2b25d77ea71be0f9232b5502a75f255a6b2ec Mon Sep 17 00:00:00 2001 From: termie Date: Mon, 11 Apr 2011 11:34:19 -0500 Subject: docstring cleanup, direct api, part of compute --- nova/api/__init__.py | 2 - nova/api/direct.py | 90 +++++++++++++++++++++++++++++++- nova/compute/api.py | 115 ++++++++++++++++++++++------------------- nova/compute/instance_types.py | 35 ++++++------- 4 files changed, 165 insertions(+), 77 deletions(-) (limited to 'nova') diff --git a/nova/api/__init__.py b/nova/api/__init__.py index 0fedbbfad..747015af5 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -15,5 +15,3 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - -"""No-op __init__ for directory full of api goodies.""" diff --git a/nova/api/direct.py b/nova/api/direct.py index f487df7c7..8ceae299c 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -44,14 +44,33 @@ from nova import utils from nova import wsgi +# Global storage for registering modules. ROUTES = {} def register_service(path, handle): + """Register a service handle at a given path. + + Services registered in this way will be made available to any instances of + nova.api.direct.Router. + + :param path: `routes` path, can be a basic string like "/path" + :param handle: an object whose methods will be made available via the api + + """ ROUTES[path] = handle class Router(wsgi.Router): + """A simple WSGI router configured via `register_service`. + + This is a quick way to attach multiple services to a given endpoint. + It will automatically load the routes registered in the `ROUTES` global. + + TODO(termie): provide a paste-deploy version of this. + + """ + def __init__(self, mapper=None): if mapper is None: mapper = routes.Mapper() @@ -66,6 +85,24 @@ class Router(wsgi.Router): class DelegatedAuthMiddleware(wsgi.Middleware): + """A simple and naive authentication middleware. + + Designed mostly to provide basic support for alternative authentication + schemes, this middleware only desires the identity of the user and will + generate the appropriate nova.context.RequestContext for the rest of the + application. This allows any middleware above it in the stack to + authenticate however it would like while only needing to conform to a + minimal interface. + + Expects two headers to determine identity: + - X-OpenStack-User + - X-OpenStack-Project + + This middleware is tied to identity management and will need to be kept + in sync with any changes to the way identity is dealt with internally. + + """ + def process_request(self, request): os_user = request.headers['X-OpenStack-User'] os_project = request.headers['X-OpenStack-Project'] @@ -74,6 +111,20 @@ class DelegatedAuthMiddleware(wsgi.Middleware): class JsonParamsMiddleware(wsgi.Middleware): + """Middleware to allow method arguments to be passed as serialized JSON. + + Accepting arguments as JSON is useful for accepting data that may be more + complex than simple primitives. + + In this case we accept it as urlencoded data under the key 'json' as in + json= but this could be extended to accept raw JSON + in the POST body. + + Filters out the parameters `self`, `context` and anything beginning with + an underscore. + + """ + def process_request(self, request): if 'json' not in request.params: return @@ -92,6 +143,13 @@ class JsonParamsMiddleware(wsgi.Middleware): class PostParamsMiddleware(wsgi.Middleware): + """Middleware to allow method arguments to be passed as POST parameters. + + Filters out the parameters `self`, `context` and anything beginning with + an underscore. + + """ + def process_request(self, request): params_parsed = request.params params = {} @@ -106,12 +164,21 @@ class PostParamsMiddleware(wsgi.Middleware): class Reflection(object): - """Reflection methods to list available methods.""" + """Reflection methods to list available methods. + + This is an object that expects to be registered via register_service. + These methods allow the endpoint to be self-describing. They introspect + the exposed methods and provide call signatures and documentation for + them allowing quick experimentation. + + """ + def __init__(self): self._methods = {} self._controllers = {} def _gather_methods(self): + """Introspect available methods and generate documentation for them.""" methods = {} controllers = {} for route, handler in ROUTES.iteritems(): @@ -185,6 +252,16 @@ class Reflection(object): class ServiceWrapper(wsgi.Controller): + """Wrapper to dynamically povide a WSGI controller for arbitrary objects. + + With lightweight introspection allows public methods on the object to + be accesed via simple WSGI routing and parameters and serializes the + return values. + + Automatically used be nova.api.direct.Router to wrap registered instances. + + """ + def __init__(self, service_handle): self.service_handle = service_handle @@ -260,7 +337,16 @@ class Limited(object): class Proxy(object): - """Pretend a Direct API endpoint is an object.""" + """Pretend a Direct API endpoint is an object. + + This is mostly useful in testing at the moment though it should be easily + extendable to provide a basic API library functionality. + + In testing we use this to stub out internal objects to verify that results + from the API are serializable. + + """ + def __init__(self, app, prefix=None): self.app = app self.prefix = prefix diff --git a/nova/compute/api.py b/nova/compute/api.py index 041e0e74a..e6146231c 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -16,9 +16,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Handles all requests relating to instances (guest vms). -""" +"""Handles all requests relating to instances (guest vms).""" import datetime import re @@ -86,10 +84,10 @@ class API(base.Base): {"method": "get_network_topic", "args": {'fake': 1}}) def _check_injected_file_quota(self, context, injected_files): - """ - Enforce quota limits on injected files + """Enforce quota limits on injected files. + + Raises a QuotaError if any limit is exceeded. - Raises a QuotaError if any limit is exceeded """ if injected_files is None: return @@ -111,8 +109,11 @@ class API(base.Base): key_name=None, key_data=None, security_group='default', availability_zone=None, user_data=None, metadata=[], injected_files=None): - """Create the number of instances requested if quota and - other arguments check out ok.""" + """Create the number and type of instances requested. + + Verifies that quota and other arguments are valid. + + """ if not instance_type: instance_type = instance_types.get_default_instance_type() @@ -262,8 +263,7 @@ class API(base.Base): return [dict(x.iteritems()) for x in instances] def has_finished_migration(self, context, instance_id): - """Retrieves whether or not a finished migration exists for - an instance""" + """Returns true if an instance has a finished migration.""" try: db.migration_get_by_instance_and_status(context, instance_id, 'finished') @@ -272,8 +272,10 @@ class API(base.Base): return False def ensure_default_security_group(self, context): - """ Create security group for the security context if it - does not already exist + """Ensure that a context has a security group. + + Creates a security group for the security context if it does not + already exist. :param context: the security context @@ -289,7 +291,7 @@ class API(base.Base): db.security_group_create(context, values) def trigger_security_group_rules_refresh(self, context, security_group_id): - """Called when a rule is added to or removed from a security_group""" + """Called when a rule is added to or removed from a security_group.""" security_group = self.db.security_group_get(context, security_group_id) @@ -305,11 +307,12 @@ class API(base.Base): "args": {"security_group_id": security_group.id}}) def trigger_security_group_members_refresh(self, context, group_id): - """Called when a security group gains a new or loses a member + """Called when a security group gains a new or loses a member. Sends an update request to each compute node for whom this is - relevant.""" + relevant. + """ # First, we get the security group rules that reference this group as # the grantee.. security_group_rules = \ @@ -354,7 +357,7 @@ class API(base.Base): as data fields of the instance to be updated - :retval None + :returns: None """ rv = self.db.instance_update(context, instance_id, kwargs) @@ -362,6 +365,7 @@ class API(base.Base): @scheduler_api.reroute_compute("delete") def delete(self, context, instance_id): + """Terminate an instance.""" LOG.debug(_("Going to try to terminate %s"), instance_id) try: instance = self.get(context, instance_id) @@ -393,22 +397,28 @@ class API(base.Base): self.db.instance_destroy(context, instance_id) def get(self, context, instance_id): - """Get a single instance with the given ID.""" + """Get a single instance with the given instance_id.""" rv = self.db.instance_get(context, instance_id) return dict(rv.iteritems()) @scheduler_api.reroute_compute("get") def routing_get(self, context, instance_id): - """Use this method instead of get() if this is the only - operation you intend to to. It will route to novaclient.get - if the instance is not found.""" + """A version of get with special routing characteristics. + + Use this method instead of get() if this is the only operation you + intend to to. It will route to novaclient.get if the instance is not + found. + + """ return self.get(context, instance_id) def get_all(self, context, project_id=None, reservation_id=None, fixed_ip=None): - """Get all instances, possibly filtered by one of the - given parameters. If there is no filter and the context is - an admin, it will retreive all instances in the system. + """Get all instances filtered by one of the given parameters. + + If there is no filter and the context is an admin, it will retreive + all instances in the system. + """ if reservation_id is not None: return self.db.instance_get_all_by_reservation( @@ -437,7 +447,8 @@ class API(base.Base): :param params: Optional dictionary of arguments to be passed to the compute worker - :retval None + :returns: None + """ if not params: params = {} @@ -456,7 +467,7 @@ class API(base.Base): :param params: Optional dictionary of arguments to be passed to the compute worker - :retval: Result returned by compute worker + :returns: Result returned by compute worker """ if not params: params = {} @@ -469,13 +480,14 @@ class API(base.Base): return rpc.call(context, queue, kwargs) def _cast_scheduler_message(self, context, args): - """Generic handler for RPC calls to the scheduler""" + """Generic handler for RPC calls to the scheduler.""" rpc.cast(context, FLAGS.scheduler_topic, args) def snapshot(self, context, instance_id, name): """Snapshot the given instance. - :retval: A dict containing image metadata + :returns: A dict containing image metadata + """ properties = {'instance_id': str(instance_id), 'user_id': str(context.user_id)} @@ -492,7 +504,7 @@ class API(base.Base): self._cast_compute_message('reboot_instance', context, instance_id) def revert_resize(self, context, instance_id): - """Reverts a resize, deleting the 'new' instance in the process""" + """Reverts a resize, deleting the 'new' instance in the process.""" context = context.elevated() migration_ref = self.db.migration_get_by_instance_and_status(context, instance_id, 'finished') @@ -507,8 +519,7 @@ class API(base.Base): {'status': 'reverted'}) def confirm_resize(self, context, instance_id): - """Confirms a migration/resize, deleting the 'old' instance in the - process.""" + """Confirms a migration/resize and deletes the 'old' instance.""" context = context.elevated() migration_ref = self.db.migration_get_by_instance_and_status(context, instance_id, 'finished') @@ -568,10 +579,9 @@ class API(base.Base): @scheduler_api.reroute_compute("diagnostics") def get_diagnostics(self, context, instance_id): """Retrieve diagnostics for the given instance.""" - return self._call_compute_message( - "get_diagnostics", - context, - instance_id) + return self._call_compute_message("get_diagnostics", + context, + instance_id) def get_actions(self, context, instance_id): """Retrieve actions for the given instance.""" @@ -579,12 +589,12 @@ class API(base.Base): @scheduler_api.reroute_compute("suspend") def suspend(self, context, instance_id): - """suspend the instance with instance_id""" + """Suspend the given instance.""" self._cast_compute_message('suspend_instance', context, instance_id) @scheduler_api.reroute_compute("resume") def resume(self, context, instance_id): - """resume the instance with instance_id""" + """Resume the given instance.""" self._cast_compute_message('resume_instance', context, instance_id) @scheduler_api.reroute_compute("rescue") @@ -599,15 +609,15 @@ class API(base.Base): def set_admin_password(self, context, instance_id, password=None): """Set the root/admin password for the given instance.""" - self._cast_compute_message('set_admin_password', context, instance_id, - password) + self._cast_compute_message( + 'set_admin_password', context, instance_id, password) def inject_file(self, context, instance_id): """Write a file to the given instance.""" self._cast_compute_message('inject_file', context, instance_id) def get_ajax_console(self, context, instance_id): - """Get a url to an AJAX Console""" + """Get a url to an AJAX Console.""" output = self._call_compute_message('get_ajax_console', context, instance_id) @@ -616,7 +626,7 @@ class API(base.Base): 'args': {'token': output['token'], 'host': output['host'], 'port': output['port']}}) return {'url': '%s/?token=%s' % (FLAGS.ajax_console_proxy_url, - output['token'])} + output['token'])} def get_vnc_console(self, context, instance_id): """Get a url to a VNC Console.""" @@ -638,39 +648,34 @@ class API(base.Base): 'portignore')} def get_console_output(self, context, instance_id): - """Get console output for an an instance""" + """Get console output for an an instance.""" return self._call_compute_message('get_console_output', context, instance_id) def lock(self, context, instance_id): - """lock the instance with instance_id""" + """Lock the given instance.""" self._cast_compute_message('lock_instance', context, instance_id) def unlock(self, context, instance_id): - """unlock the instance with instance_id""" + """Unlock the given instance.""" self._cast_compute_message('unlock_instance', context, instance_id) def get_lock(self, context, instance_id): - """return the boolean state of (instance with instance_id)'s lock""" + """Return the boolean state of given instance's lock.""" instance = self.get(context, instance_id) return instance['locked'] def reset_network(self, context, instance_id): - """ - Reset networking on the instance. - - """ + """Reset networking on the instance.""" self._cast_compute_message('reset_network', context, instance_id) def inject_network_info(self, context, instance_id): - """ - Inject network info for the instance. - - """ + """Inject network info for the instance.""" self._cast_compute_message('inject_network_info', context, instance_id) def attach_volume(self, context, instance_id, volume_id, device): + """Attach an existing volume to an existing instance.""" if not re.match("^/dev/[a-z]d[a-z]+$", device): raise exception.ApiError(_("Invalid device specified: %s. " "Example device: /dev/vdb") % device) @@ -685,6 +690,7 @@ class API(base.Base): "mountpoint": device}}) def detach_volume(self, context, volume_id): + """Detach a volume from an instance.""" instance = self.db.volume_get_instance(context.elevated(), volume_id) if not instance: raise exception.ApiError(_("Volume isn't attached to anything!")) @@ -698,6 +704,7 @@ class API(base.Base): return instance def associate_floating_ip(self, context, instance_id, address): + """Associate a floating ip with an instance.""" instance = self.get(context, instance_id) self.network_api.associate_floating_ip(context, floating_ip=address, @@ -709,11 +716,11 @@ class API(base.Base): return dict(rv.iteritems()) def delete_instance_metadata(self, context, instance_id, key): - """Delete the given metadata item""" + """Delete the given metadata item from an instance.""" self.db.instance_metadata_delete(context, instance_id, key) def update_or_create_instance_metadata(self, context, instance_id, metadata): - """Updates or creates instance metadata""" + """Updates or creates instance metadata.""" self.db.instance_metadata_update_or_create(context, instance_id, metadata) diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index b3a5ab0ac..f893f8478 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -18,9 +18,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" -The built-in instance properties. -""" +"""Built-in instance properties.""" from nova import context from nova import db @@ -34,9 +32,7 @@ LOG = logging.getLogger('nova.instance_types') def create(name, memory, vcpus, local_gb, flavorid, swap=0, rxtx_quota=0, rxtx_cap=0): - """Creates instance types / flavors - arguments: name memory vcpus local_gb flavorid swap rxtx_quota rxtx_cap - """ + """Creates instance types.""" for option in [memory, vcpus, local_gb, flavorid]: try: int(option) @@ -64,8 +60,7 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0, def destroy(name): - """Marks instance types / flavors as deleted - arguments: name""" + """Marks instance types as deleted.""" if name == None: raise exception.InvalidInputException(_("No instance type specified")) else: @@ -77,8 +72,7 @@ def destroy(name): def purge(name): - """Removes instance types / flavors from database - arguments: name""" + """Removes instance types from database.""" if name == None: raise exception.InvalidInputException(_("No instance type specified")) else: @@ -90,18 +84,19 @@ def purge(name): def get_all_types(inactive=0): - """Retrieves non-deleted instance_types. - Pass true as argument if you want deleted instance types returned also.""" + """Get all non-deleted instance_types. + + Pass true as argument if you want deleted instance types returned also. + + """ return db.instance_type_get_all(context.get_admin_context(), inactive) -def get_all_flavors(): - """retrieves non-deleted flavors. alias for instance_types.get_all_types(). - Pass true as argument if you want deleted instance types returned also.""" - return get_all_types(context.get_admin_context()) +get_all_flavors = get_all_types def get_default_instance_type(): + """Get the default instance type.""" name = FLAGS.default_instance_type try: return get_instance_type_by_name(name) @@ -110,7 +105,7 @@ def get_default_instance_type(): def get_instance_type(id): - """Retrieves single instance type by id""" + """Retrieves single instance type by id.""" if id is None: return get_default_instance_type() try: @@ -121,7 +116,7 @@ def get_instance_type(id): def get_instance_type_by_name(name): - """Retrieves single instance type by name""" + """Retrieves single instance type by name.""" if name is None: return get_default_instance_type() try: @@ -131,8 +126,10 @@ def get_instance_type_by_name(name): raise exception.ApiError(_("Unknown instance type: %s") % name) +# TODO(termie): flavor-specific code should probably be in the API that uses +# flavors. def get_instance_type_by_flavor_id(flavor_id): - """retrieve instance type by flavor_id""" + """Retrieve instance type by flavor_id.""" if flavor_id is None: return get_default_instance_type() try: -- cgit From 87d758b18836085d73c8b4230cd4812e0aa876aa Mon Sep 17 00:00:00 2001 From: Yoshiaki Tamura Date: Tue, 12 Apr 2011 11:37:51 +0900 Subject: Fix RBDDriver in volume manager. discover_volume was raising exception. Modified local_path as well. --- nova/volume/driver.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 850893914..85ff17708 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -557,7 +557,7 @@ class RBDDriver(VolumeDriver): """Returns the path of the rbd volume.""" # This is the same as the remote path # since qemu accesses it directly. - return self.discover_volume(volume) + return "rbd:%s/%s" % (FLAGS.rbd_pool, volume['name']) def ensure_export(self, context, volume): """Synchronously recreates an export for a logical volume.""" @@ -571,10 +571,8 @@ class RBDDriver(VolumeDriver): """Removes an export for a logical volume""" pass - def discover_volume(self, volume): + def discover_volume(self, context, volume): """Discover volume on a remote host""" - # NOTE(justinsb): This is messed up... discover_volume takes 3 args - # but then that would break local_path return "rbd:%s/%s" % (FLAGS.rbd_pool, volume['name']) def undiscover_volume(self, volume): -- cgit From 07c1f30225fb27cbc8e7cfeebc6a73ec67a7f2e5 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 12 Apr 2011 09:41:42 +0200 Subject: pep8 --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 83c894bea..5e81878c4 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -144,7 +144,7 @@ class CloudController(object): return None # This ensures that all attributes of the instance - # are populated. + # are populated. instance_ref = db.instance_get(ctxt, instance_ref['id']) mpi = self._get_mpi_data(ctxt, instance_ref['project_id']) -- cgit From 76bb9f42c6cc39218824332e396dca4a5e6ec351 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 12 Apr 2011 18:43:49 +0000 Subject: fix show_by_name in s3.py and give a helpful error message if image lookup fails --- nova/api/ec2/cloud.py | 5 ++++- nova/image/s3.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 5e81878c4..10b1d0ac5 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -908,7 +908,10 @@ class CloudController(object): internal_id = ec2utils.ec2_id_to_id(ec2_id) return self.image_service.show(context, internal_id) except exception.NotFound: - return self.image_service.show_by_name(context, ec2_id) + try: + return self.image_service.show_by_name(context, ec2_id) + except exception.NotFound: + raise exception.NotFound(_('Image %s not found') % ec2_id) def _format_image(self, image): """Convert from format defined by BaseImageService to S3 format.""" diff --git a/nova/image/s3.py b/nova/image/s3.py index 554760d53..b1034d151 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -75,7 +75,7 @@ class S3ImageService(service.BaseImageService): return self.service.show(context, image_id) def show_by_name(self, context, name): - return self.service.show(context, name) + return self.service.show_by_name(context, name) @staticmethod def _conn(context): -- cgit From acfa9d4e3ae2185a0d6d9afdddf3e8a2e7f6f398 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 12 Apr 2011 14:43:07 -0700 Subject: Make VMWare Connection inherit from ComputeDriver --- nova/virt/vmwareapi_conn.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index 20c1b2b45..1c6d2572d 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -42,6 +42,7 @@ from nova import exception from nova import flags from nova import log as logging from nova import utils +from nova.virt import driver from nova.virt.vmwareapi import error_util from nova.virt.vmwareapi import vim from nova.virt.vmwareapi import vim_util @@ -104,11 +105,12 @@ def get_connection(_): api_retry_count) -class VMWareESXConnection(object): +class VMWareESXConnection(driver.ComputeDriver): """The ESX host connection object.""" def __init__(self, host_ip, host_username, host_password, api_retry_count, scheme="https"): + super(VMWareESXConnection, self).__init__() session = VMWareAPISession(host_ip, host_username, host_password, api_retry_count, scheme=scheme) self._vmops = VMWareVMOps(session) -- cgit From 822ec6fe3075bed4479c8e48a984bd4c9622ffe1 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Tue, 12 Apr 2011 16:46:18 -0500 Subject: move from try_execute to _execute --- nova/volume/driver.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 0c4086039..6d86f193f 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -111,10 +111,10 @@ class VolumeDriver(object): # zero out old volumes to prevent data leaking between users # TODO(ja): reclaiming space should be done lazy and low priority - self._try_execute('sudo', 'dd', 'if=/dev/zero', - 'of=%s' % self.local_path(volume), - 'count=%d' % (volume['size'] * 1024), - 'bs=1M') + self._execute('sudo', 'dd', 'if=/dev/zero', + 'of=%s' % self.local_path(volume), + 'count=%d' % (volume['size'] * 1024), + 'bs=1M') self._try_execute('sudo', 'lvremove', '-f', "%s/%s" % (FLAGS.volume_group, volume['name'])) -- cgit From 899e6607086f6df9442f588aae4f3c37367e696d Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Wed, 13 Apr 2011 11:31:28 -0500 Subject: re-add broken code --- nova/volume/driver.py | 10 ++++++++-- nova/volume/san.py | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 6d86f193f..15b6b550b 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -93,7 +93,10 @@ class VolumeDriver(object): def create_volume(self, volume): """Creates a logical volume. Can optionally return a Dictionary of changes to the volume object to be persisted.""" - sizestr = '%sG' % volume['size'] + if int(volume['size']) == 0: + sizestr = '100M' + else: + sizestr = '%sG' % volume['size'] self._try_execute('sudo', 'lvcreate', '-L', sizestr, '-n', volume['name'], FLAGS.volume_group) @@ -599,7 +602,10 @@ class SheepdogDriver(VolumeDriver): def create_volume(self, volume): """Creates a sheepdog volume""" - sizestr = '%sG' % volume['size'] + if int(volume['size']) == 0: + sizestr = '100M' + else: + sizestr = '%sG' % volume['size'] self._try_execute('qemu-img', 'create', "sheepdog:%s" % volume['name'], sizestr) diff --git a/nova/volume/san.py b/nova/volume/san.py index dd332de1a..9532c8116 100644 --- a/nova/volume/san.py +++ b/nova/volume/san.py @@ -219,7 +219,10 @@ class SolarisISCSIDriver(SanISCSIDriver): def create_volume(self, volume): """Creates a volume.""" - sizestr = '%sG' % volume['size'] + if int(volume['size']) == 0: + sizestr = '100M' + else: + sizestr = '%sG' % volume['size'] zfs_poolname = self._build_zfs_poolname(volume) @@ -486,7 +489,10 @@ class HpSanISCSIDriver(SanISCSIDriver): #TODO(justinsb): Should we default to inheriting thinProvision? cliq_args['thinProvision'] = '1' if FLAGS.san_thin_provision else '0' cliq_args['volumeName'] = volume['name'] - cliq_args['size'] = '%sGB' % volume['size'] + if int(volume['size']) == 0: + cliq_args['size'] = '100MB' + else: + cliq_args['size'] = '%sGB' % volume['size'] self._cliq_run_xml("createVolume", cliq_args) -- cgit From eda350a605b5711b8373849f389e3fe472670ca0 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 13 Apr 2011 13:35:32 -0500 Subject: Don't hammer on the DB --- nova/virt/libvirt_conn.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 6ec15fbb8..94410003e 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -372,6 +372,9 @@ class LibvirtConnection(driver.ComputeDriver): instance['id'], state) if state == power_state.SHUTOFF: break + + # Let's not hammer on the DB + time.sleep(1) except Exception as ex: msg = _("Error encountered when destroying instance '%(id)s': " "%(ex)s") % {"id": instance["id"], "ex": ex} -- cgit From db755b38609f5c94b70f88057d0b2f0f4964744e Mon Sep 17 00:00:00 2001 From: "jaypipes@gmail.com" <> Date: Wed, 13 Apr 2011 18:32:43 -0400 Subject: Rework GlanceImageService._translate_base() to not call BaseImageService._translate_base() otherwise the wrong class attributes are used in properties construction... --- nova/image/glance.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index bf49ca96c..1a80bb2af 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -186,7 +186,8 @@ class GlanceImageService(service.BaseImageService): """Overriding the base translation to handle conversion to datetime objects """ - image_meta = service.BaseImageService._translate_to_base(image_meta) + image_meta = service.BaseImageService._propertify_metadata( + image_meta, cls.SERVICE_IMAGE_ATTRS) image_meta = _convert_timestamps_to_datetimes(image_meta) return image_meta -- cgit