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 import driver as parent_driver
from cinder.volume.drivers.xenapi import lib from cinder.volume.drivers.xenapi import lib
from cinder.volume.drivers.xenapi import sm as driver from cinder.volume.drivers.xenapi import sm as driver
from cinder.volume.drivers.xenapi import tools
import contextlib
import mock
import mox import mox
import StringIO
import unittest import unittest
@ -260,7 +264,67 @@ class DriverTestCase(unittest.TestCase):
drv.delete_snapshot(snapshot) drv.delete_snapshot(snapshot)
mock.VerifyAll() 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( mock, drv = self._setup_mock_driver(
'server', 'serverpath', '/var/run/sr-mount') 'server', 'serverpath', '/var/run/sr-mount')
@ -280,11 +344,11 @@ class DriverTestCase(unittest.TestCase):
'server', 'serverpath', 'sr-uuid', 'vdi-uuid', 2) 'server', 'serverpath', 'sr-uuid', 'vdi-uuid', 2)
mock.ReplayAll() mock.ReplayAll()
drv.copy_image_to_volume( drv._use_glance_plugin_to_copy_image_to_volume(
MockContext('token'), volume, "ignore", "image_id") MockContext('token'), volume, "ignore", "image_id")
mock.VerifyAll() 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( mock, drv = self._setup_mock_driver(
'server', 'serverpath', '/var/run/sr-mount') 'server', 'serverpath', '/var/run/sr-mount')
@ -304,7 +368,7 @@ class DriverTestCase(unittest.TestCase):
self.assertRaises( self.assertRaises(
exception.ImageCopyFailure, exception.ImageCopyFailure,
lambda: drv.copy_image_to_volume( lambda: drv._use_glance_plugin_to_copy_image_to_volume(
MockContext('token'), volume, "ignore", "image_id")) MockContext('token'), volume, "ignore", "image_id"))
mock.VerifyAll() mock.VerifyAll()
@ -336,3 +400,24 @@ class DriverTestCase(unittest.TestCase):
stats = drv.get_volume_stats() stats = drv.get_volume_stats()
self.assertEquals('xensm', stats['storage_protocol']) 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 # License for the specific language governing permissions and limitations
# under the License. # under the License.
from cinder.volume.drivers.xenapi import tools
import contextlib import contextlib
import os import os
import pickle import pickle
@ -35,6 +36,55 @@ class OperationsBase(object):
return self.session.call_xenapi(method, *args) 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): class PbdOperations(OperationsBase):
def get_all(self): def get_all(self):
return self.call_xenapi('PBD.get_all') return self.call_xenapi('PBD.get_all')
@ -161,6 +211,9 @@ class XenAPISession(object):
self.SR = SrOperations(self) self.SR = SrOperations(self)
self.VDI = VdiOperations(self) self.VDI = VdiOperations(self)
self.host = HostOperations(self) self.host = HostOperations(self)
self.pool = PoolOperations(self)
self.VBD = VBDOperations(self)
self.VM = VMOperations(self)
def close(self): def close(self):
return self.call_xenapi('logout') return self.call_xenapi('logout')
@ -465,3 +518,25 @@ class NFSBasedVolumeOperations(object):
os.path.join(sr_base_path, sr_uuid), auth_token, dict()) os.path.join(sr_base_path, sr_uuid), auth_token, dict())
finally: finally:
self.disconnect_volume(vdi_uuid) 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 exception
from cinder import flags from cinder import flags
from cinder.image import glance from cinder.image import glance
from cinder.image import image_utils
from cinder.openstack.common import log as logging from cinder.openstack.common import log as logging
from cinder.volume import driver from cinder.volume import driver
from cinder.volume.drivers.xenapi import lib as xenapi_lib from cinder.volume.drivers.xenapi import lib as xenapi_lib
@ -155,6 +156,27 @@ class XenAPINFSDriver(driver.VolumeDriver):
pass pass
def copy_image_to_volume(self, context, volume, image_service, image_id): 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('/') sr_uuid, vdi_uuid = volume['provider_location'].split('/')
api_servers = glance.get_api_servers() api_servers = glance.get_api_servers()
@ -212,3 +234,12 @@ class XenAPINFSDriver(driver.VolumeDriver):
reserved_percentage=0) reserved_percentage=0)
return self._stats 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 distribute>=0.6.28
coverage coverage
mock
mox>=0.5.3 mox>=0.5.3
nose nose
nosexcover nosexcover