xenapi: Add support for different image upload drivers
This change refactors image upload code for xenapi and adds the xenapi_image_upload_handler config option. Preparation for bp nova-direct-image-upload DocImpact Change-Id: Icd4e7ef135beddca9ddcc4a880c09e5a1702a93c
This commit is contained in:
@@ -19,7 +19,6 @@
|
|||||||
import ast
|
import ast
|
||||||
import base64
|
import base64
|
||||||
import contextlib
|
import contextlib
|
||||||
import cPickle as pickle
|
|
||||||
import functools
|
import functools
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -48,6 +47,7 @@ from nova.virt.xenapi import agent
|
|||||||
from nova.virt.xenapi import driver as xenapi_conn
|
from nova.virt.xenapi import driver as xenapi_conn
|
||||||
from nova.virt.xenapi import fake as xenapi_fake
|
from nova.virt.xenapi import fake as xenapi_fake
|
||||||
from nova.virt.xenapi import host
|
from nova.virt.xenapi import host
|
||||||
|
from nova.virt.xenapi.imageupload import glance
|
||||||
from nova.virt.xenapi import pool
|
from nova.virt.xenapi import pool
|
||||||
from nova.virt.xenapi import pool_states
|
from nova.virt.xenapi import pool_states
|
||||||
from nova.virt.xenapi import vm_utils
|
from nova.virt.xenapi import vm_utils
|
||||||
@@ -431,15 +431,29 @@ class XenAPIVMTestCase(stubs.XenAPITestBase):
|
|||||||
{'task_state': task_states.IMAGE_UPLOADING,
|
{'task_state': task_states.IMAGE_UPLOADING,
|
||||||
'expected_state': task_states.IMAGE_PENDING_UPLOAD}}]
|
'expected_state': task_states.IMAGE_PENDING_UPLOAD}}]
|
||||||
func_call_matcher = matchers.FunctionCallMatcher(expected_calls)
|
func_call_matcher = matchers.FunctionCallMatcher(expected_calls)
|
||||||
|
image_id = "my_snapshot_id"
|
||||||
|
|
||||||
stubs.stubout_instance_snapshot(self.stubs)
|
stubs.stubout_instance_snapshot(self.stubs)
|
||||||
stubs.stubout_is_snapshot(self.stubs)
|
stubs.stubout_is_snapshot(self.stubs)
|
||||||
# Stubbing out firewall driver as previous stub sets alters
|
# Stubbing out firewall driver as previous stub sets alters
|
||||||
# xml rpc result parsing
|
# xml rpc result parsing
|
||||||
stubs.stubout_firewall_driver(self.stubs, self.conn)
|
stubs.stubout_firewall_driver(self.stubs, self.conn)
|
||||||
|
|
||||||
instance = self._create_instance()
|
instance = self._create_instance()
|
||||||
|
|
||||||
image_id = "my_snapshot_id"
|
self.fake_upload_called = False
|
||||||
|
|
||||||
|
def fake_image_upload(_self, ctx, session, inst, vdi_uuids,
|
||||||
|
img_id):
|
||||||
|
self.fake_upload_called = True
|
||||||
|
self.assertEqual(ctx, self.context)
|
||||||
|
self.assertEqual(inst, instance)
|
||||||
|
self.assertTrue(isinstance(vdi_uuids, list))
|
||||||
|
self.assertEqual(img_id, image_id)
|
||||||
|
|
||||||
|
self.stubs.Set(glance.GlanceStore, 'upload_image',
|
||||||
|
fake_image_upload)
|
||||||
|
|
||||||
self.conn.snapshot(self.context, instance, image_id,
|
self.conn.snapshot(self.context, instance, image_id,
|
||||||
func_call_matcher.call)
|
func_call_matcher.call)
|
||||||
|
|
||||||
@@ -469,6 +483,8 @@ class XenAPIVMTestCase(stubs.XenAPITestBase):
|
|||||||
name_label = vdi_rec["name_label"]
|
name_label = vdi_rec["name_label"]
|
||||||
self.assert_(not name_label.endswith('snapshot'))
|
self.assert_(not name_label.endswith('snapshot'))
|
||||||
|
|
||||||
|
self.assertTrue(self.fake_upload_called)
|
||||||
|
|
||||||
def create_vm_record(self, conn, os_type, name):
|
def create_vm_record(self, conn, os_type, name):
|
||||||
instances = conn.list_instances()
|
instances = conn.list_instances()
|
||||||
self.assertEquals(instances, [name])
|
self.assertEquals(instances, [name])
|
||||||
@@ -2574,54 +2590,6 @@ class SwapXapiHostTestCase(test.TestCase):
|
|||||||
"http://someserver", 'otherserver'))
|
"http://someserver", 'otherserver'))
|
||||||
|
|
||||||
|
|
||||||
class VmUtilsTestCase(test.TestCase):
|
|
||||||
"""Unit tests for xenapi utils."""
|
|
||||||
|
|
||||||
def test_upload_image(self):
|
|
||||||
def fake_instance_system_metadata_get(context, uuid):
|
|
||||||
return dict(image_a=1, image_b=2, image_c='c', d='d')
|
|
||||||
|
|
||||||
def fake_get_sr_path(session):
|
|
||||||
return "foo"
|
|
||||||
|
|
||||||
class FakeInstance(dict):
|
|
||||||
def __init__(self):
|
|
||||||
super(FakeInstance, self).__init__({
|
|
||||||
'auto_disk_config': 'auto disk config',
|
|
||||||
'os_type': 'os type'})
|
|
||||||
|
|
||||||
def __missing__(self, item):
|
|
||||||
return "whatever"
|
|
||||||
|
|
||||||
class FakeSession(object):
|
|
||||||
def call_plugin(session_self, service, command, kwargs):
|
|
||||||
self.kwargs = kwargs
|
|
||||||
|
|
||||||
def call_plugin_serialized(session_self, service, command, *args,
|
|
||||||
**kwargs):
|
|
||||||
self.kwargs = kwargs
|
|
||||||
|
|
||||||
def fake_dumps(thing):
|
|
||||||
return thing
|
|
||||||
|
|
||||||
self.stubs.Set(db, "instance_system_metadata_get",
|
|
||||||
fake_instance_system_metadata_get)
|
|
||||||
self.stubs.Set(vm_utils, "get_sr_path", fake_get_sr_path)
|
|
||||||
self.stubs.Set(pickle, "dumps", fake_dumps)
|
|
||||||
|
|
||||||
ctx = context.get_admin_context()
|
|
||||||
|
|
||||||
instance = FakeInstance()
|
|
||||||
session = FakeSession()
|
|
||||||
vm_utils.upload_image(ctx, session, instance, "vmi uuids", "image id")
|
|
||||||
|
|
||||||
actual = self.kwargs['properties']
|
|
||||||
# Inheritance happens in another place, now
|
|
||||||
expected = dict(auto_disk_config='auto disk config',
|
|
||||||
os_type='os type')
|
|
||||||
self.assertEquals(expected, actual)
|
|
||||||
|
|
||||||
|
|
||||||
class XenAPILiveMigrateTestCase(stubs.XenAPITestBase):
|
class XenAPILiveMigrateTestCase(stubs.XenAPITestBase):
|
||||||
"""Unit tests for live_migration."""
|
"""Unit tests for live_migration."""
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
0
nova/tests/virt/xenapi/imageupload/__init__.py
Normal file
0
nova/tests/virt/xenapi/imageupload/__init__.py
Normal file
74
nova/tests/virt/xenapi/imageupload/test_glance.py
Normal file
74
nova/tests/virt/xenapi/imageupload/test_glance.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2013 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 mox
|
||||||
|
|
||||||
|
from nova import context
|
||||||
|
from nova import test
|
||||||
|
from nova.virt.xenapi.imageupload import glance
|
||||||
|
from nova.virt.xenapi import vm_utils
|
||||||
|
|
||||||
|
|
||||||
|
class TestGlanceStore(test.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestGlanceStore, self).setUp()
|
||||||
|
self.store = glance.GlanceStore()
|
||||||
|
self.mox = mox.Mox()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(TestGlanceStore, self).tearDown()
|
||||||
|
|
||||||
|
def test_upload_image(self):
|
||||||
|
glance_host = '0.1.2.3'
|
||||||
|
glance_port = 8143
|
||||||
|
glance_use_ssl = False
|
||||||
|
sr_path = '/fake/sr/path'
|
||||||
|
self.flags(glance_host=glance_host)
|
||||||
|
self.flags(glance_port=glance_port)
|
||||||
|
self.flags(glance_api_insecure=glance_use_ssl)
|
||||||
|
|
||||||
|
def fake_get_sr_path(*_args, **_kwargs):
|
||||||
|
return sr_path
|
||||||
|
|
||||||
|
self.stubs.Set(vm_utils, 'get_sr_path', fake_get_sr_path)
|
||||||
|
|
||||||
|
ctx = context.RequestContext('user', 'project', auth_token='foobar')
|
||||||
|
properties = {
|
||||||
|
'auto_disk_config': True,
|
||||||
|
'os_type': 'default',
|
||||||
|
}
|
||||||
|
image_id = 'fake_image_uuid'
|
||||||
|
vdi_uuids = ['fake_vdi_uuid']
|
||||||
|
instance = {'uuid': 'blah'}
|
||||||
|
instance.update(properties)
|
||||||
|
|
||||||
|
params = {'vdi_uuids': vdi_uuids,
|
||||||
|
'image_id': image_id,
|
||||||
|
'glance_host': glance_host,
|
||||||
|
'glance_port': glance_port,
|
||||||
|
'glance_use_ssl': glance_use_ssl,
|
||||||
|
'sr_path': sr_path,
|
||||||
|
'auth_token': 'foobar',
|
||||||
|
'properties': properties}
|
||||||
|
session = self.mox.CreateMockAnything()
|
||||||
|
session.call_plugin_serialized('glance', 'upload_vhd', **params)
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
self.store.upload_image(ctx, session, instance, vdi_uuids, image_id)
|
||||||
|
|
||||||
|
self.mox.VerifyAll()
|
||||||
0
nova/virt/xenapi/imageupload/__init__.py
Normal file
0
nova/virt/xenapi/imageupload/__init__.py
Normal file
54
nova/virt/xenapi/imageupload/glance.py
Normal file
54
nova/virt/xenapi/imageupload/glance.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Copyright 2013 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.
|
||||||
|
|
||||||
|
from nova.image import glance
|
||||||
|
from nova.openstack.common import cfg
|
||||||
|
import nova.openstack.common.log as logging
|
||||||
|
from nova.virt.xenapi import vm_utils
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class GlanceStore(object):
|
||||||
|
|
||||||
|
def upload_image(self, context, session, instance, vdi_uuids, image_id):
|
||||||
|
"""Requests that the Glance plugin bundle the specified VDIs and
|
||||||
|
push them into Glance using the specified human-friendly name.
|
||||||
|
"""
|
||||||
|
# NOTE(sirp): Currently we only support uploading images as VHD, there
|
||||||
|
# is no RAW equivalent (yet)
|
||||||
|
LOG.debug(_("Asking xapi to upload to glance %(vdi_uuids)s as"
|
||||||
|
" ID %(image_id)s"), locals(), instance=instance)
|
||||||
|
|
||||||
|
glance_api_servers = glance.get_api_servers()
|
||||||
|
glance_host, glance_port, glance_use_ssl = glance_api_servers.next()
|
||||||
|
|
||||||
|
properties = {
|
||||||
|
'auto_disk_config': instance['auto_disk_config'],
|
||||||
|
'os_type': instance['os_type'] or CONF.default_os_type,
|
||||||
|
}
|
||||||
|
|
||||||
|
params = {'vdi_uuids': vdi_uuids,
|
||||||
|
'image_id': image_id,
|
||||||
|
'glance_host': glance_host,
|
||||||
|
'glance_port': glance_port,
|
||||||
|
'glance_use_ssl': glance_use_ssl,
|
||||||
|
'sr_path': vm_utils.get_sr_path(session),
|
||||||
|
'auth_token': getattr(context, 'auth_token', None),
|
||||||
|
'properties': properties}
|
||||||
|
|
||||||
|
session.call_plugin_serialized('glance', 'upload_vhd', **params)
|
||||||
@@ -722,35 +722,6 @@ def _find_cached_image(session, image_id, sr_ref):
|
|||||||
return cached_images.get(image_id)
|
return cached_images.get(image_id)
|
||||||
|
|
||||||
|
|
||||||
def upload_image(context, session, instance, vdi_uuids, image_id):
|
|
||||||
"""Requests that the Glance plugin bundle the specified VDIs and
|
|
||||||
push them into Glance using the specified human-friendly name.
|
|
||||||
"""
|
|
||||||
# NOTE(sirp): Currently we only support uploading images as VHD, there
|
|
||||||
# is no RAW equivalent (yet)
|
|
||||||
LOG.debug(_("Asking xapi to upload %(vdi_uuids)s as"
|
|
||||||
" ID %(image_id)s"), locals(), instance=instance)
|
|
||||||
|
|
||||||
glance_api_servers = glance.get_api_servers()
|
|
||||||
glance_host, glance_port, glance_use_ssl = glance_api_servers.next()
|
|
||||||
|
|
||||||
properties = {
|
|
||||||
'auto_disk_config': instance['auto_disk_config'],
|
|
||||||
'os_type': instance['os_type'] or CONF.default_os_type,
|
|
||||||
}
|
|
||||||
|
|
||||||
params = {'vdi_uuids': vdi_uuids,
|
|
||||||
'image_id': image_id,
|
|
||||||
'glance_host': glance_host,
|
|
||||||
'glance_port': glance_port,
|
|
||||||
'glance_use_ssl': glance_use_ssl,
|
|
||||||
'sr_path': get_sr_path(session),
|
|
||||||
'auth_token': getattr(context, 'auth_token', None),
|
|
||||||
'properties': properties}
|
|
||||||
|
|
||||||
session.call_plugin_serialized('glance', 'upload_vhd', **params)
|
|
||||||
|
|
||||||
|
|
||||||
def resize_disk(session, instance, vdi_ref, instance_type):
|
def resize_disk(session, instance, vdi_ref, instance_type):
|
||||||
# Copy VDI over to something we can resize
|
# Copy VDI over to something we can resize
|
||||||
# NOTE(jerdfelt): Would be nice to just set vdi_ref to read/write
|
# NOTE(jerdfelt): Would be nice to just set vdi_ref to read/write
|
||||||
|
|||||||
@@ -59,7 +59,10 @@ xenapi_vmops_opts = [
|
|||||||
'to go to running state'),
|
'to go to running state'),
|
||||||
cfg.StrOpt('xenapi_vif_driver',
|
cfg.StrOpt('xenapi_vif_driver',
|
||||||
default='nova.virt.xenapi.vif.XenAPIBridgeDriver',
|
default='nova.virt.xenapi.vif.XenAPIBridgeDriver',
|
||||||
help='The XenAPI VIF driver using XenServer Network APIs.')
|
help='The XenAPI VIF driver using XenServer Network APIs.'),
|
||||||
|
cfg.StrOpt('xenapi_image_upload_handler',
|
||||||
|
default='nova.virt.xenapi.imageupload.glance.GlanceStore',
|
||||||
|
help='Object Store Driver used to handle image uploads.'),
|
||||||
]
|
]
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@@ -161,6 +164,11 @@ class VMOps(object):
|
|||||||
self.vif_driver = vif_impl(xenapi_session=self._session)
|
self.vif_driver = vif_impl(xenapi_session=self._session)
|
||||||
self.default_root_dev = '/dev/sda'
|
self.default_root_dev = '/dev/sda'
|
||||||
|
|
||||||
|
msg = _("Importing image upload handler: %s")
|
||||||
|
LOG.debug(msg % CONF.xenapi_image_upload_handler)
|
||||||
|
self.image_upload_handler = importutils.import_object(
|
||||||
|
CONF.xenapi_image_upload_handler)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def agent_enabled(self):
|
def agent_enabled(self):
|
||||||
return not CONF.xenapi_disable_agent
|
return not CONF.xenapi_disable_agent
|
||||||
@@ -661,9 +669,11 @@ class VMOps(object):
|
|||||||
coalesce together, so, we must wait for this coalescing to occur to
|
coalesce together, so, we must wait for this coalescing to occur to
|
||||||
get a stable representation of the data on disk.
|
get a stable representation of the data on disk.
|
||||||
|
|
||||||
3. Push-to-glance: Once coalesced, we call a plugin on the XenServer
|
3. Push-to-data-store: Once coalesced, we call a plugin on the
|
||||||
that will bundle the VHDs together and then push the bundle into
|
XenServer that will bundle the VHDs together and then push the
|
||||||
Glance.
|
bundle. Depending on the configured value of
|
||||||
|
'xenapi_image_upload_handler', image data may be pushed to
|
||||||
|
Glance or the specified data store.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
vm_ref = self._get_vm_opaque_ref(instance)
|
vm_ref = self._get_vm_opaque_ref(instance)
|
||||||
@@ -674,8 +684,11 @@ class VMOps(object):
|
|||||||
update_task_state) as vdi_uuids:
|
update_task_state) as vdi_uuids:
|
||||||
update_task_state(task_state=task_states.IMAGE_UPLOADING,
|
update_task_state(task_state=task_states.IMAGE_UPLOADING,
|
||||||
expected_state=task_states.IMAGE_PENDING_UPLOAD)
|
expected_state=task_states.IMAGE_PENDING_UPLOAD)
|
||||||
vm_utils.upload_image(
|
self.image_upload_handler.upload_image(context,
|
||||||
context, self._session, instance, vdi_uuids, image_id)
|
self._session,
|
||||||
|
instance,
|
||||||
|
vdi_uuids,
|
||||||
|
image_id)
|
||||||
|
|
||||||
LOG.debug(_("Finished snapshot and upload for VM"),
|
LOG.debug(_("Finished snapshot and upload for VM"),
|
||||||
instance=instance)
|
instance=instance)
|
||||||
|
|||||||
Reference in New Issue
Block a user