diff options
author | Sandy Walsh <sandy.walsh@rackspace.com> | 2011-02-25 13:59:26 -0800 |
---|---|---|
committer | Sandy Walsh <sandy.walsh@rackspace.com> | 2011-02-25 13:59:26 -0800 |
commit | 6f72ba3d88f31e6336725bcffe47fa2bd5f1dba0 (patch) | |
tree | 5c558286c6926a0d7edfa07fc529f5ee2e5b1653 | |
parent | eab99bbcdb02aa9bd330b01f2d27a297ab6b7f2e (diff) | |
parent | f3efbaa3d10997038e32c6e5e53dfef117236247 (diff) | |
download | nova-6f72ba3d88f31e6336725bcffe47fa2bd5f1dba0.tar.gz nova-6f72ba3d88f31e6336725bcffe47fa2bd5f1dba0.tar.xz nova-6f72ba3d88f31e6336725bcffe47fa2bd5f1dba0.zip |
merge with zones2 fixes and trunk
-rwxr-xr-x | bin/nova-api | 49 | ||||
-rw-r--r-- | nova/api/ec2/__init__.py | 6 | ||||
-rw-r--r-- | nova/api/openstack/servers.py | 2 | ||||
-rw-r--r-- | nova/compute/manager.py | 4 | ||||
-rw-r--r-- | nova/fakerabbit.py | 1 | ||||
-rw-r--r-- | nova/network/api.py | 1 | ||||
-rw-r--r-- | nova/scheduler/api.py | 2 | ||||
-rw-r--r-- | nova/service.py | 88 | ||||
-rw-r--r-- | nova/tests/glance/stubs.py | 15 | ||||
-rw-r--r-- | nova/tests/test_xenapi.py | 24 | ||||
-rw-r--r-- | nova/tests/xenapi/stubs.py | 16 | ||||
-rw-r--r-- | nova/virt/xenapi/vm_utils.py | 18 | ||||
-rw-r--r-- | nova/virt/xenapi/vmops.py | 89 | ||||
-rw-r--r-- | nova/virt/xenapi_conn.py | 14 | ||||
-rw-r--r-- | plugins/xenserver/xenapi/etc/xapi.d/plugins/glance | 14 |
15 files changed, 224 insertions, 119 deletions
diff --git a/bin/nova-api b/bin/nova-api index 0b2a44c88..06bb855cb 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -36,51 +36,15 @@ gettext.install('nova', unicode=1) from nova import flags from nova import log as logging +from nova import service from nova import utils from nova import version from nova import wsgi + LOG = logging.getLogger('nova.api') FLAGS = flags.FLAGS -flags.DEFINE_string('paste_config', "api-paste.ini", - 'File name for the paste.deploy config for nova-api') -flags.DEFINE_string('ec2_listen', "0.0.0.0", - 'IP address for EC2 API to listen') -flags.DEFINE_integer('ec2_listen_port', 8773, 'port for ec2 api to listen') -flags.DEFINE_string('osapi_listen', "0.0.0.0", - 'IP address for OpenStack API to listen') -flags.DEFINE_integer('osapi_listen_port', 8774, 'port for os api to listen') -flags.DEFINE_flag(flags.HelpFlag()) -flags.DEFINE_flag(flags.HelpshortFlag()) -flags.DEFINE_flag(flags.HelpXMLFlag()) - -API_ENDPOINTS = ['ec2', 'osapi'] - - -def run_app(paste_config_file): - LOG.debug(_("Using paste.deploy config at: %s"), paste_config_file) - apps = [] - for api in API_ENDPOINTS: - config = wsgi.load_paste_configuration(paste_config_file, api) - if config is None: - LOG.debug(_("No paste configuration for app: %s"), api) - continue - LOG.debug(_("App Config: %(api)s\n%(config)r") % locals()) - LOG.info(_("Running %s API"), api) - app = wsgi.load_paste_app(paste_config_file, api) - apps.append((app, getattr(FLAGS, "%s_listen_port" % api), - getattr(FLAGS, "%s_listen" % api))) - if len(apps) == 0: - LOG.error(_("No known API applications configured in %s."), - paste_config_file) - return - - server = wsgi.Server() - for app in apps: - server.start(*app) - server.wait() - if __name__ == '__main__': utils.default_flagfile() @@ -92,9 +56,6 @@ if __name__ == '__main__': for flag in FLAGS: flag_get = FLAGS.get(flag, None) LOG.debug("%(flag)s : %(flag_get)s" % locals()) - conf = wsgi.paste_config_file(FLAGS.paste_config) - if conf: - run_app(conf) - else: - LOG.error(_("No paste configuration found for: %s"), - FLAGS.paste_config) + + service = service.serve_wsgi(service.ApiService) + service.wait() diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 5adc2c075..4425ba3cd 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -198,6 +198,12 @@ class Requestify(wsgi.Middleware): try: # Raise KeyError if omitted action = req.params['Action'] + # Fix bug lp:720157 for older (version 1) clients + version = req.params['SignatureVersion'] + if int(version) == 1: + non_args.remove('SignatureMethod') + if 'SignatureMethod' in args: + args.pop('SignatureMethod') for non_arg in non_args: # Remove, but raise KeyError if omitted args.pop(non_arg) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index c2bf42b72..85999764f 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -450,7 +450,7 @@ class Controller(wsgi.Controller): _("Cannot build from image %(image_id)s, status not active") % locals()) - if image['type'] != 'machine': + if image['disk_format'] != 'ami': return None, None try: diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 7eb5eed16..ebe1ce6f0 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -511,9 +511,7 @@ class ComputeManager(scheduler_manager.SchedulerDependentManager): instance_ref = self.db.instance_get(context, migration_ref['instance_id']) - # this may get passed into the following spawn instead - new_disk_info = self.driver.attach_disk(instance_ref, disk_info) - self.driver.spawn(instance_ref, disk=new_disk_info) + self.driver.finish_resize(instance_ref, disk_info) self.db.migration_update(context, migration_id, {'status': 'finished', }) diff --git a/nova/fakerabbit.py b/nova/fakerabbit.py index dd82a9366..a7dee8caf 100644 --- a/nova/fakerabbit.py +++ b/nova/fakerabbit.py @@ -48,7 +48,6 @@ class Exchange(object): nm = self.name LOG.debug(_('(%(nm)s) publish (key: %(routing_key)s)' ' %(message)s') % locals()) - routing_key = routing_key.split('.')[0] if routing_key in self._routes: for f in self._routes[routing_key]: LOG.debug(_('Publishing to route %s'), f) diff --git a/nova/network/api.py b/nova/network/api.py index bf43acb51..4ee1148cb 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -21,6 +21,7 @@ Handles all requests relating to instances (guest vms). """ from nova import db +from nova import exception from nova import flags from nova import log as logging from nova import quota diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py index fcff2f146..b6d27dacc 100644 --- a/nova/scheduler/api.py +++ b/nova/scheduler/api.py @@ -40,7 +40,7 @@ def _call_scheduler(method, context, params=None): return rpc.call(context, queue, kwargs) -class API: +class API(object): """API for interacting with the scheduler.""" @classmethod diff --git a/nova/service.py b/nova/service.py index a98d6aac1..51c0e935e 100644 --- a/nova/service.py +++ b/nova/service.py @@ -2,6 +2,7 @@ # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. +# Copyright 2011 Justin Santa Barbara # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -39,6 +40,7 @@ from nova import flags from nova import rpc from nova import utils from nova import version +from nova import wsgi FLAGS = flags.FLAGS @@ -48,6 +50,14 @@ flags.DEFINE_integer('report_interval', 10, flags.DEFINE_integer('periodic_interval', 60, 'seconds between running periodic tasks', lower_bound=1) +flags.DEFINE_string('ec2_listen', "0.0.0.0", + 'IP address for EC2 API to listen') +flags.DEFINE_integer('ec2_listen_port', 8773, 'port for ec2 api to listen') +flags.DEFINE_string('osapi_listen', "0.0.0.0", + 'IP address for OpenStack API to listen') +flags.DEFINE_integer('osapi_listen_port', 8774, 'port for os api to listen') +flags.DEFINE_string('api_paste_config', "api-paste.ini", + 'File name for the paste.deploy config for nova-api') class Service(object): @@ -216,6 +226,41 @@ class Service(object): logging.exception(_("model server went away")) +class WsgiService(object): + """Base class for WSGI based services. + + For each api you define, you must also define these flags: + :<api>_listen: The address on which to listen + :<api>_listen_port: The port on which to listen + """ + + def __init__(self, conf, apis): + self.conf = conf + self.apis = apis + self.wsgi_app = None + + def start(self): + self.wsgi_app = _run_wsgi(self.conf, self.apis) + + def wait(self): + self.wsgi_app.wait() + + +class ApiService(WsgiService): + """Class for our nova-api service""" + @classmethod + def create(cls, conf=None): + if not conf: + conf = wsgi.paste_config_file(FLAGS.api_paste_config) + if not conf: + message = (_("No paste configuration found for: %s"), + FLAGS.api_paste_config) + raise exception.Error(message) + api_endpoints = ['ec2', 'osapi'] + service = cls(conf, api_endpoints) + return service + + def serve(*services): try: if not services: @@ -245,3 +290,46 @@ def serve(*services): def wait(): while True: greenthread.sleep(5) + + +def serve_wsgi(cls, conf=None): + try: + service = cls.create(conf) + except Exception: + logging.exception('in WsgiService.create()') + raise + finally: + # After we've loaded up all our dynamic bits, check + # whether we should print help + flags.DEFINE_flag(flags.HelpFlag()) + flags.DEFINE_flag(flags.HelpshortFlag()) + flags.DEFINE_flag(flags.HelpXMLFlag()) + FLAGS.ParseNewFlags() + + service.start() + + return service + + +def _run_wsgi(paste_config_file, apis): + logging.debug(_("Using paste.deploy config at: %s"), paste_config_file) + apps = [] + for api in apis: + config = wsgi.load_paste_configuration(paste_config_file, api) + if config is None: + logging.debug(_("No paste configuration for app: %s"), api) + continue + logging.debug(_("App Config: %(api)s\n%(config)r") % locals()) + logging.info(_("Running %s API"), api) + app = wsgi.load_paste_app(paste_config_file, api) + apps.append((app, getattr(FLAGS, "%s_listen_port" % api), + getattr(FLAGS, "%s_listen" % api))) + if len(apps) == 0: + logging.error(_("No known API applications configured in %s."), + paste_config_file) + return + + server = wsgi.Server() + for app in apps: + server.start(*app) + return server diff --git a/nova/tests/glance/stubs.py b/nova/tests/glance/stubs.py index 3ff8d7ce5..5872552ec 100644 --- a/nova/tests/glance/stubs.py +++ b/nova/tests/glance/stubs.py @@ -35,23 +35,28 @@ class FakeGlance(object): IMAGE_FIXTURES = { IMAGE_MACHINE: { 'image_meta': {'name': 'fakemachine', 'size': 0, - 'type': 'machine'}, + 'disk_format': 'ami', + 'container_format': 'ami'}, 'image_data': StringIO.StringIO('')}, IMAGE_KERNEL: { 'image_meta': {'name': 'fakekernel', 'size': 0, - 'type': 'kernel'}, + 'disk_format': 'aki', + 'container_format': 'aki'}, 'image_data': StringIO.StringIO('')}, IMAGE_RAMDISK: { 'image_meta': {'name': 'fakeramdisk', 'size': 0, - 'type': 'ramdisk'}, + 'disk_format': 'ari', + 'container_format': 'ari'}, 'image_data': StringIO.StringIO('')}, IMAGE_RAW: { 'image_meta': {'name': 'fakeraw', 'size': 0, - 'type': 'raw'}, + 'disk_format': 'raw', + 'container_format': 'bare'}, 'image_data': StringIO.StringIO('')}, IMAGE_VHD: { 'image_meta': {'name': 'fakevhd', 'size': 0, - 'type': 'vhd'}, + 'disk_format': 'vhd', + 'container_format': 'ovf'}, 'image_data': StringIO.StringIO('')}} def __init__(self, host, port=None, use_ssl=False): diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 7f437c2b8..c26dc8639 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -360,16 +360,28 @@ class XenAPIMigrateInstance(test.TestCase): db_fakes.stub_out_db_instance_api(self.stubs) stubs.stub_out_get_target(self.stubs) xenapi_fake.reset() + self.manager = manager.AuthManager() + self.user = self.manager.create_user('fake', 'fake', 'fake', + admin=True) + self.project = self.manager.create_project('fake', 'fake', 'fake') self.values = {'name': 1, 'id': 1, - 'project_id': 'fake', - 'user_id': 'fake', + 'project_id': self.project.id, + 'user_id': self.user.id, 'image_id': 1, - 'kernel_id': 2, - 'ramdisk_id': 3, + 'kernel_id': None, + 'ramdisk_id': None, 'instance_type': 'm1.large', 'mac_address': 'aa:bb:cc:dd:ee:ff', } stubs.stub_out_migration_methods(self.stubs) + glance_stubs.stubout_glance_client(self.stubs, + glance_stubs.FakeGlance) + + def tearDown(self): + super(XenAPIMigrateInstance, self).tearDown() + self.manager.delete_project(self.project) + self.manager.delete_user(self.user) + self.stubs.UnsetAll() def test_migrate_disk_and_power_off(self): instance = db.instance_create(self.values) @@ -377,11 +389,11 @@ class XenAPIMigrateInstance(test.TestCase): conn = xenapi_conn.get_connection(False) conn.migrate_disk_and_power_off(instance, '127.0.0.1') - def test_attach_disk(self): + def test_finish_resize(self): instance = db.instance_create(self.values) stubs.stubout_session(self.stubs, stubs.FakeSessionForMigrationTests) conn = xenapi_conn.get_connection(False) - conn.attach_disk(instance, {'base_copy': 'hurr', 'cow': 'durr'}) + conn.finish_resize(instance, dict(base_copy='hurr', cow='durr')) class XenAPIDetermineDiskImageTestCase(test.TestCase): diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 11e89c9b4..70d46a1fb 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -225,6 +225,18 @@ class FakeSessionForMigrationTests(fake.SessionBase): def __init__(self, uri): super(FakeSessionForMigrationTests, self).__init__(uri) + def VDI_get_by_uuid(*args): + return 'hurr' + + def VM_start(self, _1, ref, _2, _3): + vm = fake.get_record('VM', ref) + if vm['power_state'] != 'Halted': + raise fake.Failure(['VM_BAD_POWER_STATE', ref, 'Halted', + vm['power_state']]) + vm['power_state'] = 'Running' + vm['is_a_template'] = False + vm['is_control_domain'] = False + def stub_out_migration_methods(stubs): def fake_get_snapshot(self, instance): @@ -251,6 +263,9 @@ def stub_out_migration_methods(stubs): def fake_destroy(*args, **kwargs): pass + def fake_reset_network(*args, **kwargs): + pass + stubs.Set(vmops.VMOps, '_destroy', fake_destroy) stubs.Set(vm_utils.VMHelper, 'scan_default_sr', fake_sr) stubs.Set(vm_utils.VMHelper, 'scan_sr', fake_sr) @@ -258,4 +273,5 @@ def stub_out_migration_methods(stubs): stubs.Set(vm_utils.VMHelper, 'get_vdi_for_vm_safely', fake_get_vdi) stubs.Set(xenapi_conn.XenAPISession, 'wait_for_task', lambda x, y, z: None) stubs.Set(vm_utils.VMHelper, 'get_sr_path', fake_get_sr_path) + stubs.Set(vmops.VMOps, 'reset_network', fake_reset_network) stubs.Set(vmops.VMOps, '_shutdown', fake_shutdown) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 80b7540d4..ce081a2d6 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -467,19 +467,21 @@ class VMHelper(HelperBase): "%(image_id)s, instance %(instance_id)s") % locals()) def determine_from_glance(): - glance_type2nova_type = {'machine': ImageType.DISK, - 'raw': ImageType.DISK_RAW, - 'vhd': ImageType.DISK_VHD, - 'kernel': ImageType.KERNEL_RAMDISK, - 'ramdisk': ImageType.KERNEL_RAMDISK} + glance_disk_format2nova_type = { + 'ami': ImageType.DISK, + 'aki': ImageType.KERNEL_RAMDISK, + 'ari': ImageType.KERNEL_RAMDISK, + 'raw': ImageType.DISK_RAW, + 'vhd': ImageType.DISK_VHD} client = glance.client.Client(FLAGS.glance_host, FLAGS.glance_port) meta = client.get_image_meta(instance.image_id) - type_ = meta['type'] + disk_format = meta['disk_format'] try: - return glance_type2nova_type[type_] + return glance_disk_format2nova_type[disk_format] except KeyError: raise exception.NotFound( - _("Unrecognized image type '%(type_)s'") % locals()) + _("Unrecognized disk_format '%(disk_format)s'") + % locals()) def determine_from_instance(): if instance.kernel_id: diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index b862c9de9..562ecd4d5 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -72,13 +72,25 @@ class VMOps(object): LOG.debug(_("Starting instance %s"), instance.name) self._session.call_xenapi('VM.start', vm_ref, False, False) - def spawn(self, instance, disk): + def create_disk(self, instance): + user = AuthManager().get_user(instance.user_id) + project = AuthManager().get_project(instance.project_id) + disk_image_type = VMHelper.determine_disk_image_type(instance) + vdi_uuid = VMHelper.fetch_image(self._session, instance.id, + instance.image_id, user, project, disk_image_type) + return vdi_uuid + + def spawn(self, instance): + vdi_uuid = self.create_disk(instance) + self._spawn_with_disk(instance, vdi_uuid=vdi_uuid) + + def _spawn_with_disk(self, instance, vdi_uuid): """Create VM instance""" instance_name = instance.name vm = VMHelper.lookup(self._session, instance_name) if vm is not None: raise exception.Duplicate(_('Attempted to create' - ' non-unique name %s') % instance_name) + ' non-unique name %s') % instance_name) #ensure enough free memory is available if not VMHelper.ensure_free_mem(self._session, instance): @@ -92,20 +104,12 @@ class VMOps(object): user = AuthManager().get_user(instance.user_id) project = AuthManager().get_project(instance.project_id) - vdi_ref = kernel = ramdisk = pv_kernel = None + kernel = ramdisk = pv_kernel = None # Are we building from a pre-existing disk? - if not disk: - #if kernel is not present we must download a raw disk - - disk_image_type = VMHelper.determine_disk_image_type(instance) - vdi_uuid = VMHelper.fetch_image(self._session, instance.id, - instance.image_id, user, project, disk_image_type) - vdi_ref = self._session.call_xenapi('VDI.get_by_uuid', vdi_uuid) - - else: - vdi_ref = self._session.call_xenapi('VDI.get_by_uuid', disk) + vdi_ref = self._session.call_xenapi('VDI.get_by_uuid', vdi_uuid) + disk_image_type = VMHelper.determine_disk_image_type(instance) if disk_image_type == ImageType.DISK_RAW: # Have a look at the VDI and see if it has a PV kernel pv_kernel = VMHelper.lookup_image(self._session, instance.id, @@ -188,35 +192,38 @@ class VMOps(object): """Refactored out the common code of many methods that receive either a vm name or a vm instance, and want a vm instance in return. """ - vm = None - try: - if instance_or_vm.startswith("OpaqueRef:"): - # Got passed an opaque ref; return it + # if instance_or_vm is a string it must be opaque ref or instance name + if isinstance(instance_or_vm, basestring): + obj = None + try: + # check for opaque ref + obj = self._session.get_xenapi().VM.get_record(instance_or_vm) return instance_or_vm - else: - # Must be the instance name + except self.XenAPI.Failure: + # wasn't an opaque ref, must be an instance name instance_name = instance_or_vm - except (AttributeError, KeyError): - # Note the the KeyError will only happen with fakes.py - # Not a string; must be an ID or a vm instance - if isinstance(instance_or_vm, (int, long)): - ctx = context.get_admin_context() - try: - instance_obj = db.instance_get(ctx, instance_or_vm) - instance_name = instance_obj.name - except exception.NotFound: - # The unit tests screw this up, as they use an integer for - # the vm name. I'd fix that up, but that's a matter for - # another bug report. So for now, just try with the passed - # value - instance_name = instance_or_vm - else: - instance_name = instance_or_vm.name - vm = VMHelper.lookup(self._session, instance_name) - if vm is None: + + # if instance_or_vm is an int/long it must be instance id + elif isinstance(instance_or_vm, (int, long)): + ctx = context.get_admin_context() + try: + instance_obj = db.instance_get(ctx, instance_or_vm) + instance_name = instance_obj.name + except exception.NotFound: + # The unit tests screw this up, as they use an integer for + # the vm name. I'd fix that up, but that's a matter for + # another bug report. So for now, just try with the passed + # value + instance_name = instance_or_vm + + # otherwise instance_or_vm is an instance object + else: + instance_name = instance_or_vm.name + vm_ref = VMHelper.lookup(self._session, instance_name) + if vm_ref is None: raise exception.NotFound( _('Instance not present %s') % instance_name) - return vm + return vm_ref def _acquire_bootlock(self, vm): """Prevent an instance from booting""" @@ -337,14 +344,14 @@ class VMOps(object): # sensible so we don't need to blindly pass around dictionaries return {'base_copy': base_copy_uuid, 'cow': cow_uuid} - def attach_disk(self, instance, disk_info): + def attach_disk(self, instance, base_copy_uuid, cow_uuid): """Links the base copy VHD to the COW via the XAPI plugin""" vm_ref = VMHelper.lookup(self._session, instance.name) new_base_copy_uuid = str(uuid.uuid4()) new_cow_uuid = str(uuid.uuid4()) params = {'instance_id': instance.id, - 'old_base_copy_uuid': disk_info['base_copy'], - 'old_cow_uuid': disk_info['cow'], + 'old_base_copy_uuid': base_copy_uuid, + 'old_cow_uuid': cow_uuid, 'new_base_copy_uuid': new_base_copy_uuid, 'new_cow_uuid': new_cow_uuid, 'sr_path': VMHelper.get_sr_path(self._session), } diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 62e17e851..b63a5f8c3 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -154,9 +154,15 @@ class XenAPIConnection(object): """List VM instances""" return self._vmops.list_instances() - def spawn(self, instance, disk=None): + def spawn(self, instance): """Create VM instance""" - self._vmops.spawn(instance, disk) + self._vmops.spawn(instance) + + def finish_resize(self, instance, disk_info): + """Completes a resize, turning on the migrated instance""" + vdi_uuid = self._vmops.attach_disk(instance, disk_info['base_copy'], + disk_info['cow']) + self._vmops._spawn_with_disk(instance, vdi_uuid) def snapshot(self, instance, image_id): """ Create snapshot from a running VM instance """ @@ -197,10 +203,6 @@ class XenAPIConnection(object): off the instance copies over the COW disk""" return self._vmops.migrate_disk_and_power_off(instance, dest) - def attach_disk(self, instance, disk_info): - """Moves the copied VDIs into the SR""" - return self._vmops.attach_disk(instance, disk_info) - def suspend(self, instance, callback): """suspend the specified instance""" self._vmops.suspend(instance, callback) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance b/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance index aa12d432a..201b99fda 100644 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance @@ -201,13 +201,21 @@ def _upload_tarball(staging_path, image_id, glance_host, glance_port): # to request conn.putrequest('PUT', '/images/%s' % image_id) - # TODO(sirp): make `store` configurable + # NOTE(sirp): There is some confusion around OVF. Here's a summary of + # where we currently stand: + # 1. OVF as a container format is misnamed. We really should be using + # OVA since that is the name for the container format; OVF is the + # standard applied to the manifest file contained within. + # 2. We're currently uploading a vanilla tarball. In order to be OVF/OVA + # compliant, we'll need to embed a minimal OVF manifest as the first + # file. headers = { 'content-type': 'application/octet-stream', 'transfer-encoding': 'chunked', - 'x-image-meta-is_public': 'True', + 'x-image-meta-is-public': 'True', 'x-image-meta-status': 'queued', - 'x-image-meta-type': 'vhd'} + 'x-image-meta-disk-format': 'vhd', + 'x-image-meta-container-format': 'ovf'} for header, value in headers.iteritems(): conn.putheader(header, value) conn.endheaders() |