XenAPINFS: Create volume from image (generic)

Fixes bug 1130706

Related to blueprint xenapinfs-glance-integration

This patch enables users to use any images recognised by qemu-img to
create volumes on XenAPINFS.

Change-Id: I9dd8ec237309da82ec7f73db1acb48e5b7411c9e
This commit is contained in:
Mate Lakat 2013-02-13 10:48:34 +00:00
parent c529b1f6be
commit f2ce6984b7
5 changed files with 203 additions and 4 deletions

View File

@ -23,7 +23,11 @@ from cinder.volume import configuration as conf
from cinder.volume import driver as parent_driver
from cinder.volume.drivers.xenapi import lib
from cinder.volume.drivers.xenapi import sm as driver
from cinder.volume.drivers.xenapi import tools
import contextlib
import mock
import mox
import StringIO
import unittest
@ -260,7 +264,67 @@ class DriverTestCase(unittest.TestCase):
drv.delete_snapshot(snapshot)
mock.VerifyAll()
def test_copy_image_to_volume_success(self):
def test_copy_image_to_volume_xenserver_case(self):
mock, drv = self._setup_mock_driver(
'server', 'serverpath', '/var/run/sr-mount')
mock.StubOutWithMock(drv, '_use_glance_plugin_to_copy_image_to_volume')
mock.StubOutWithMock(driver, 'is_xenserver_image')
context = MockContext('token')
driver.is_xenserver_image(
context, 'image_service', 'image_id').AndReturn(True)
drv._use_glance_plugin_to_copy_image_to_volume(
context, 'volume', 'image_service', 'image_id').AndReturn('result')
mock.ReplayAll()
result = drv.copy_image_to_volume(
context, "volume", "image_service", "image_id")
self.assertEquals('result', result)
mock.VerifyAll()
def test_copy_image_to_volume_non_xenserver_case(self):
mock, drv = self._setup_mock_driver(
'server', 'serverpath', '/var/run/sr-mount')
mock.StubOutWithMock(drv, '_use_image_utils_to_pipe_bytes_to_volume')
mock.StubOutWithMock(driver, 'is_xenserver_image')
context = MockContext('token')
driver.is_xenserver_image(
context, 'image_service', 'image_id').AndReturn(False)
drv._use_image_utils_to_pipe_bytes_to_volume(
context, 'volume', 'image_service', 'image_id').AndReturn(True)
mock.ReplayAll()
drv.copy_image_to_volume(
context, "volume", "image_service", "image_id")
mock.VerifyAll()
def test_use_image_utils_to_pipe_bytes_to_volume(self):
mock, drv = self._setup_mock_driver(
'server', 'serverpath', '/var/run/sr-mount')
volume = dict(provider_location='sr-uuid/vdi-uuid')
context = MockContext('token')
mock.StubOutWithMock(driver.image_utils, 'fetch_to_raw')
@contextlib.contextmanager
def simple_context(value):
yield value
drv.nfs_ops.volume_attached_here(
'server', 'serverpath', 'sr-uuid', 'vdi-uuid', False).AndReturn(
simple_context('device'))
driver.image_utils.fetch_to_raw(
context, 'image_service', 'image_id', 'device')
mock.ReplayAll()
drv._use_image_utils_to_pipe_bytes_to_volume(
context, volume, "image_service", "image_id")
mock.VerifyAll()
def test_use_glance_plugin_to_copy_image_to_volume_success(self):
mock, drv = self._setup_mock_driver(
'server', 'serverpath', '/var/run/sr-mount')
@ -280,11 +344,11 @@ class DriverTestCase(unittest.TestCase):
'server', 'serverpath', 'sr-uuid', 'vdi-uuid', 2)
mock.ReplayAll()
drv.copy_image_to_volume(
drv._use_glance_plugin_to_copy_image_to_volume(
MockContext('token'), volume, "ignore", "image_id")
mock.VerifyAll()
def test_copy_image_to_volume_fail(self):
def test_use_glance_plugin_to_copy_image_to_volume_fail(self):
mock, drv = self._setup_mock_driver(
'server', 'serverpath', '/var/run/sr-mount')
@ -304,7 +368,7 @@ class DriverTestCase(unittest.TestCase):
self.assertRaises(
exception.ImageCopyFailure,
lambda: drv.copy_image_to_volume(
lambda: drv._use_glance_plugin_to_copy_image_to_volume(
MockContext('token'), volume, "ignore", "image_id"))
mock.VerifyAll()
@ -336,3 +400,24 @@ class DriverTestCase(unittest.TestCase):
stats = drv.get_volume_stats()
self.assertEquals('xensm', stats['storage_protocol'])
class ToolsTest(unittest.TestCase):
@mock.patch('cinder.volume.drivers.xenapi.tools._stripped_first_line_of')
def test_get_this_vm_uuid(self, mock_read_first_line):
mock_read_first_line.return_value = 'someuuid'
self.assertEquals('someuuid', tools.get_this_vm_uuid())
mock_read_first_line.assert_called_once_with('/sys/hypervisor/uuid')
def test_stripped_first_line_of(self):
mock_context_manager = mock.Mock()
mock_context_manager.__enter__ = mock.Mock(
return_value=StringIO.StringIO(' blah \n second line \n'))
mock_context_manager.__exit__ = mock.Mock(return_value=False)
mock_open = mock.Mock(return_value=mock_context_manager)
with mock.patch('__builtin__.open', mock_open):
self.assertEquals(
'blah', tools._stripped_first_line_of('/somefile'))
mock_open.assert_called_once_with('/somefile', 'rb')

View File

@ -16,6 +16,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from cinder.volume.drivers.xenapi import tools
import contextlib
import os
import pickle
@ -35,6 +36,55 @@ class OperationsBase(object):
return self.session.call_xenapi(method, *args)
class VMOperations(OperationsBase):
def get_by_uuid(self, vm_uuid):
return self.call_xenapi('VM.get_by_uuid', vm_uuid)
def get_vbds(self, vm_uuid):
return self.call_xenapi('VM.get_VBDs', vm_uuid)
class VBDOperations(OperationsBase):
def create(self, vm_ref, vdi_ref, userdevice, bootable, mode, type,
empty, other_config):
vbd_rec = dict(
VM=vm_ref,
VDI=vdi_ref,
userdevice=str(userdevice),
bootable=bootable,
mode=mode,
type=type,
empty=empty,
other_config=other_config,
qos_algorithm_type='',
qos_algorithm_params=dict()
)
return self.call_xenapi('VBD.create', vbd_rec)
def destroy(self, vbd_ref):
self.call_xenapi('VBD.destroy', vbd_ref)
def get_device(self, vbd_ref):
return self.call_xenapi('VBD.get_device', vbd_ref)
def plug(self, vbd_ref):
return self.call_xenapi('VBD.plug', vbd_ref)
def unplug(self, vbd_ref):
return self.call_xenapi('VBD.unplug', vbd_ref)
def get_vdi(self, vbd_ref):
return self.call_xenapi('VBD.get_VDI', vbd_ref)
class PoolOperations(OperationsBase):
def get_all(self):
return self.call_xenapi('pool.get_all')
def get_default_SR(self, pool_ref):
return self.call_xenapi('pool.get_default_SR', pool_ref)
class PbdOperations(OperationsBase):
def get_all(self):
return self.call_xenapi('PBD.get_all')
@ -161,6 +211,9 @@ class XenAPISession(object):
self.SR = SrOperations(self)
self.VDI = VdiOperations(self)
self.host = HostOperations(self)
self.pool = PoolOperations(self)
self.VBD = VBDOperations(self)
self.VM = VMOperations(self)
def close(self):
return self.call_xenapi('logout')
@ -465,3 +518,25 @@ class NFSBasedVolumeOperations(object):
os.path.join(sr_base_path, sr_uuid), auth_token, dict())
finally:
self.disconnect_volume(vdi_uuid)
@contextlib.contextmanager
def volume_attached_here(self, server, serverpath, sr_uuid, vdi_uuid,
readonly=True):
self.connect_volume(server, serverpath, sr_uuid, vdi_uuid)
with self._session_factory.get_session() as session:
vm_uuid = tools.get_this_vm_uuid()
vm_ref = session.VM.get_by_uuid(vm_uuid)
vdi_ref = session.VDI.get_by_uuid(vdi_uuid)
vbd_ref = session.VBD.create(
vm_ref, vdi_ref, userdevice='autodetect', bootable=False,
mode='RO' if readonly else 'RW', type='disk', empty=False,
other_config=dict())
session.VBD.plug(vbd_ref)
device = session.VBD.get_device(vbd_ref)
try:
yield "/dev/" + device
finally:
session.VBD.unplug(vbd_ref)
session.VBD.destroy(vbd_ref)
self.disconnect_volume(vdi_uuid)

View File

@ -21,6 +21,7 @@ from oslo.config import cfg
from cinder import exception
from cinder import flags
from cinder.image import glance
from cinder.image import image_utils
from cinder.openstack.common import log as logging
from cinder.volume import driver
from cinder.volume.drivers.xenapi import lib as xenapi_lib
@ -155,6 +156,27 @@ class XenAPINFSDriver(driver.VolumeDriver):
pass
def copy_image_to_volume(self, context, volume, image_service, image_id):
if is_xenserver_image(context, image_service, image_id):
return self._use_glance_plugin_to_copy_image_to_volume(
context, volume, image_service, image_id)
return self._use_image_utils_to_pipe_bytes_to_volume(
context, volume, image_service, image_id)
def _use_image_utils_to_pipe_bytes_to_volume(self, context, volume,
image_service, image_id):
sr_uuid, vdi_uuid = volume['provider_location'].split('/')
with self.nfs_ops.volume_attached_here(FLAGS.xenapi_nfs_server,
FLAGS.xenapi_nfs_serverpath,
sr_uuid, vdi_uuid,
False) as device:
image_utils.fetch_to_raw(context,
image_service,
image_id,
device)
def _use_glance_plugin_to_copy_image_to_volume(self, context, volume,
image_service, image_id):
sr_uuid, vdi_uuid = volume['provider_location'].split('/')
api_servers = glance.get_api_servers()
@ -212,3 +234,12 @@ class XenAPINFSDriver(driver.VolumeDriver):
reserved_percentage=0)
return self._stats
def is_xenserver_image(context, image_service, image_id):
image_meta = image_service.show(context, image_id)
return (
image_meta['disk_format'] == 'vhd'
and image_meta['container_format'] == 'ovf'
)

View File

@ -0,0 +1,7 @@
def _stripped_first_line_of(filename):
with open(filename, 'rb') as f:
return f.readline().strip()
def get_this_vm_uuid():
return _stripped_first_line_of('/sys/hypervisor/uuid')

View File

@ -2,6 +2,7 @@
distribute>=0.6.28
coverage
mock
mox>=0.5.3
nose
nosexcover