Merge "Add XenAPINFSDriver"

This commit is contained in:
Jenkins
2012-11-21 17:33:35 +00:00
committed by Gerrit Code Review
5 changed files with 646 additions and 0 deletions

View File

@@ -0,0 +1,186 @@
# 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.
from cinder.volume.xenapi import lib
from cinder.volume import xenapi_sm as driver
import mox
import unittest
class DriverTestCase(unittest.TestCase):
def assert_flag(self, flagname):
self.assertTrue(hasattr(driver.FLAGS, flagname))
def test_config_options(self):
self.assert_flag('xenapi_connection_url')
self.assert_flag('xenapi_connection_username')
self.assert_flag('xenapi_connection_password')
self.assert_flag('xenapi_nfs_server')
self.assert_flag('xenapi_nfs_serverpath')
def test_do_setup(self):
mock = mox.Mox()
mock.StubOutWithMock(driver, 'xenapi_lib')
mock.StubOutWithMock(driver, 'FLAGS')
driver.FLAGS.xenapi_connection_url = 'url'
driver.FLAGS.xenapi_connection_username = 'user'
driver.FLAGS.xenapi_connection_password = 'pass'
session_factory = object()
nfsops = object()
driver.xenapi_lib.SessionFactory('url', 'user', 'pass').AndReturn(
session_factory)
driver.xenapi_lib.NFSBasedVolumeOperations(
session_factory).AndReturn(nfsops)
drv = driver.XenAPINFSDriver()
mock.ReplayAll()
drv.do_setup('context')
mock.VerifyAll()
self.assertEquals(nfsops, drv.nfs_ops)
def test_create_volume(self):
mock = mox.Mox()
mock.StubOutWithMock(driver, 'FLAGS')
driver.FLAGS.xenapi_nfs_server = 'server'
driver.FLAGS.xenapi_nfs_serverpath = 'path'
ops = mock.CreateMock(lib.NFSBasedVolumeOperations)
drv = driver.XenAPINFSDriver()
drv.nfs_ops = ops
volume_details = dict(
sr_uuid='sr_uuid',
vdi_uuid='vdi_uuid'
)
ops.create_volume(
'server', 'path', 1, 'name', 'desc').AndReturn(volume_details)
mock.ReplayAll()
result = drv.create_volume(dict(
size=1, display_name='name', display_description='desc'))
mock.VerifyAll()
self.assertEquals(dict(
provider_location='sr_uuid/vdi_uuid'
), result)
def test_delete_volume(self):
mock = mox.Mox()
mock.StubOutWithMock(driver, 'FLAGS')
driver.FLAGS.xenapi_nfs_server = 'server'
driver.FLAGS.xenapi_nfs_serverpath = 'path'
ops = mock.CreateMock(lib.NFSBasedVolumeOperations)
drv = driver.XenAPINFSDriver()
drv.nfs_ops = ops
ops.delete_volume('server', 'path', 'sr_uuid', 'vdi_uuid')
mock.ReplayAll()
result = drv.delete_volume(dict(
provider_location='sr_uuid/vdi_uuid'))
mock.VerifyAll()
def test_create_export_does_not_raise_exception(self):
drv = driver.XenAPINFSDriver()
drv.create_export('context', 'volume')
def test_remove_export_does_not_raise_exception(self):
drv = driver.XenAPINFSDriver()
drv.remove_export('context', 'volume')
def test_initialize_connection(self):
mock = mox.Mox()
mock.StubOutWithMock(driver, 'FLAGS')
driver.FLAGS.xenapi_nfs_server = 'server'
driver.FLAGS.xenapi_nfs_serverpath = 'path'
drv = driver.XenAPINFSDriver()
mock.ReplayAll()
result = drv.initialize_connection(
dict(
display_name='name',
display_description='desc',
provider_location='sr_uuid/vdi_uuid'),
'connector'
)
mock.VerifyAll()
self.assertEquals(
dict(
driver_volume_type='xensm',
data=dict(
name_label='name',
name_description='desc',
sr_uuid='sr_uuid',
vdi_uuid='vdi_uuid',
sr_type='nfs',
server='server',
serverpath='path',
introduce_sr_keys=['sr_type', 'server', 'serverpath']
)
),
result
)
def test_initialize_connection_null_values(self):
mock = mox.Mox()
mock.StubOutWithMock(driver, 'FLAGS')
driver.FLAGS.xenapi_nfs_server = 'server'
driver.FLAGS.xenapi_nfs_serverpath = 'path'
drv = driver.XenAPINFSDriver()
mock.ReplayAll()
result = drv.initialize_connection(
dict(
display_name=None,
display_description=None,
provider_location='sr_uuid/vdi_uuid'),
'connector'
)
mock.VerifyAll()
self.assertEquals(
dict(
driver_volume_type='xensm',
data=dict(
name_label='',
name_description='',
sr_uuid='sr_uuid',
vdi_uuid='vdi_uuid',
sr_type='nfs',
server='server',
serverpath='path',
introduce_sr_keys=['sr_type', 'server', 'serverpath']
)
),
result
)

View File

View File

333
cinder/volume/xenapi/lib.py Normal file
View File

@@ -0,0 +1,333 @@
# 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.
import contextlib
class XenAPIException(Exception):
def __init__(self, original_exception):
super(XenAPIException, self).__init__(str(original_exception))
self.original_exception = original_exception
class OperationsBase(object):
def __init__(self, xenapi_session):
self.session = xenapi_session
def call_xenapi(self, method, *args):
return self.session.call_xenapi(method, *args)
class PbdOperations(OperationsBase):
def get_all(self):
return self.call_xenapi('PBD.get_all')
def unplug(self, pbd_ref):
self.call_xenapi('PBD.unplug', pbd_ref)
def create(self, host_ref, sr_ref, device_config):
return self.call_xenapi(
'PBD.create',
dict(
host=host_ref,
SR=sr_ref,
device_config=device_config
)
)
def plug(self, pbd_ref):
self.call_xenapi('PBD.plug', pbd_ref)
class SrOperations(OperationsBase):
def get_all(self):
return self.call_xenapi('SR.get_all')
def get_record(self, sr_ref):
return self.call_xenapi('SR.get_record', sr_ref)
def forget(self, sr_ref):
self.call_xenapi('SR.forget', sr_ref)
def scan(self, sr_ref):
self.call_xenapi('SR.scan', sr_ref)
def create(self, host_ref, device_config, name_label, name_description,
sr_type, physical_size=None, content_type=None,
shared=False, sm_config=None):
return self.call_xenapi(
'SR.create',
host_ref,
device_config,
physical_size or '0',
name_label or '',
name_description or '',
sr_type,
content_type or '',
shared,
sm_config or dict()
)
def introduce(self, sr_uuid, name_label, name_description, sr_type,
content_type=None, shared=False, sm_config=None):
return self.call_xenapi(
'SR.introduce',
sr_uuid,
name_label or '',
name_description or '',
sr_type,
content_type or '',
shared,
sm_config or dict()
)
def get_uuid(self, sr_ref):
return self.get_record(sr_ref)['uuid']
def get_name_label(self, sr_ref):
return self.get_record(sr_ref)['name_label']
def get_name_description(self, sr_ref):
return self.get_record(sr_ref)['name_description']
def destroy(self, sr_ref):
self.call_xenapi('SR.destroy', sr_ref)
class VdiOperations(OperationsBase):
def get_all(self):
return self.call_xenapi('VDI.get_all')
def get_record(self, vdi_ref):
return self.call_xenapi('VDI.get_record', vdi_ref)
def get_by_uuid(self, vdi_uuid):
return self.call_xenapi('VDI.get_by_uuid', vdi_uuid)
def get_uuid(self, vdi_ref):
return self.get_record(vdi_ref)['uuid']
def create(self, sr_ref, size, vdi_type,
sharable=False, read_only=False, other_config=None):
return self.call_xenapi('VDI.create',
dict(
SR=sr_ref,
virtual_size=str(size),
type=vdi_type,
sharable=sharable,
read_only=read_only,
other_config=other_config or dict()
)
)
def destroy(self, vdi_ref):
self.call_xenapi('VDI.destroy', vdi_ref)
class HostOperations(OperationsBase):
def get_record(self, host_ref):
return self.call_xenapi('host.get_record', host_ref)
def get_uuid(self, host_ref):
return self.get_record(host_ref)['uuid']
class XenAPISession(object):
def __init__(self, session, exception_to_convert):
self._session = session
self._exception_to_convert = exception_to_convert
self.handle = self._session.handle
self.PBD = PbdOperations(self)
self.SR = SrOperations(self)
self.VDI = VdiOperations(self)
self.host = HostOperations(self)
def close(self):
return self.call_xenapi('logout')
def call_xenapi(self, method, *args):
try:
return self._session.xenapi_request(method, args)
except self._exception_to_convert as e:
raise XenAPIException(e)
def get_pool(self):
return self.call_xenapi('session.get_pool', self.handle)
def get_this_host(self):
return self.call_xenapi('session.get_this_host', self.handle)
class CompoundOperations(object):
def unplug_pbds_from_sr(self, sr_ref):
sr_rec = self.SR.get_record(sr_ref)
for pbd_ref in sr_rec.get('PBDs', []):
self.PBD.unplug(pbd_ref)
def unplug_pbds_and_forget_sr(self, sr_ref):
self.unplug_pbds_from_sr(sr_ref)
self.SR.forget(sr_ref)
def create_new_vdi(self, sr_ref, size_in_gigabytes):
return self.VDI.create(
sr_ref,
to_bytes(size_in_gigabytes),
'User',
)
def to_bytes(size_in_gigs):
return size_in_gigs * 1024 * 1024 * 1024
class NFSOperationsMixIn(CompoundOperations):
def is_nfs_sr(self, sr_ref):
return self.SR.get_record(sr_ref).get('type') == 'nfs'
@contextlib.contextmanager
def new_sr_on_nfs(self, host_ref, server, serverpath,
name_label=None, name_description=None):
device_config = dict(
server=server,
serverpath=serverpath
)
name_label = name_label or ''
name_description = name_description or ''
sr_type = 'nfs'
sr_ref = self.SR.create(
host_ref,
device_config,
name_label,
name_description,
sr_type,
)
yield sr_ref
self.unplug_pbds_and_forget_sr(sr_ref)
def plug_nfs_sr(self, host_ref, server, serverpath, sr_uuid,
name_label=None, name_description=None):
device_config = dict(
server=server,
serverpath=serverpath
)
sr_type = 'nfs'
sr_ref = self.SR.introduce(
sr_uuid,
name_label,
name_description,
sr_type,
)
pbd_ref = self.PBD.create(
host_ref,
sr_ref,
device_config
)
self.PBD.plug(pbd_ref)
return sr_ref
def connect_volume(self, server, serverpath, sr_uuid, vdi_uuid):
host_ref = self.get_this_host()
sr_ref = self.plug_nfs_sr(
host_ref,
server,
serverpath,
sr_uuid
)
self.SR.scan(sr_ref)
vdi_ref = self.VDI.get_by_uuid(vdi_uuid)
return dict(sr_ref=sr_ref, vdi_ref=vdi_ref)
class ContextAwareSession(XenAPISession):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
class OpenStackXenAPISession(ContextAwareSession,
NFSOperationsMixIn):
pass
def connect(url, user, password):
import XenAPI
session = XenAPI.Session(url)
session.login_with_password(user, password)
return OpenStackXenAPISession(session, XenAPI.Failure)
class SessionFactory(object):
def __init__(self, url, user, password):
self.url = url
self.user = user
self.password = password
def get_session(self):
return connect(self.url, self.user, self.password)
class NFSBasedVolumeOperations(object):
def __init__(self, session_factory):
self._session_factory = session_factory
def create_volume(self, server, serverpath, size,
name=None, description=None):
with self._session_factory.get_session() as session:
host_ref = session.get_this_host()
with session.new_sr_on_nfs(host_ref, server, serverpath,
name, description) as sr_ref:
vdi_ref = session.create_new_vdi(sr_ref, size)
return dict(
sr_uuid=session.SR.get_uuid(sr_ref),
vdi_uuid=session.VDI.get_uuid(vdi_ref)
)
def delete_volume(self, server, serverpath, sr_uuid, vdi_uuid):
with self._session_factory.get_session() as session:
refs = session.connect_volume(
server, serverpath, sr_uuid, vdi_uuid)
session.VDI.destroy(refs['vdi_ref'])
sr_ref = refs['sr_ref']
session.unplug_pbds_from_sr(sr_ref)
session.SR.destroy(sr_ref)
def connect_volume(self, server, serverpath, sr_uuid, vdi_uuid):
with self._session_factory.get_session() as session:
refs = session.connect_volume(
server, serverpath, sr_uuid, vdi_uuid)
return session.VDI.get_uuid(refs['vdi_ref'])
def disconnect_volume(self, vdi_uuid):
with self._session_factory.get_session() as session:
vdi_ref = session.VDI.get_by_uuid(vdi_uuid)
vdi_rec = session.VDI.get_record(vdi_ref)
sr_ref = vdi_rec['SR']
session.unplug_pbds_and_forget_sr(sr_ref)

127
cinder/volume/xenapi_sm.py Normal file
View File

@@ -0,0 +1,127 @@
# 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.
from cinder import flags
from cinder.openstack.common import cfg
from cinder.volume import driver
from cinder.volume.xenapi import lib as xenapi_lib
xenapi_opts = [
cfg.StrOpt('xenapi_connection_url',
default=None,
help='URL for XenAPI connection'),
cfg.StrOpt('xenapi_connection_username',
default='root',
help='Username for XenAPI connection'),
cfg.StrOpt('xenapi_connection_password',
default=None,
help='Password for XenAPI connection'),
]
xenapi_nfs_opts = [
cfg.StrOpt('xenapi_nfs_server',
default=None,
help='NFS server to be used by XenAPINFSDriver'),
cfg.StrOpt('xenapi_nfs_serverpath',
default=None,
help='Path of exported NFS, used by XenAPINFSDriver'),
]
FLAGS = flags.FLAGS
FLAGS.register_opts(xenapi_opts)
FLAGS.register_opts(xenapi_nfs_opts)
class XenAPINFSDriver(driver.VolumeDriver):
def do_setup(self, context):
session_factory = xenapi_lib.SessionFactory(
FLAGS.xenapi_connection_url,
FLAGS.xenapi_connection_username,
FLAGS.xenapi_connection_password
)
self.nfs_ops = xenapi_lib.NFSBasedVolumeOperations(session_factory)
def create_volume(self, volume):
volume_details = self.nfs_ops.create_volume(
FLAGS.xenapi_nfs_server,
FLAGS.xenapi_nfs_serverpath,
volume['size'],
volume['display_name'],
volume['display_description']
)
location = "%(sr_uuid)s/%(vdi_uuid)s" % volume_details
return dict(provider_location=location)
def create_export(self, context, volume):
pass
def delete_volume(self, volume):
sr_uuid, vdi_uuid = volume['provider_location'].split('/')
self.nfs_ops.delete_volume(
FLAGS.xenapi_nfs_server,
FLAGS.xenapi_nfs_serverpath,
sr_uuid,
vdi_uuid
)
def remove_export(self, context, volume):
pass
def initialize_connection(self, volume, connector):
sr_uuid, vdi_uuid = volume['provider_location'].split('/')
return dict(
driver_volume_type='xensm',
data=dict(
name_label=volume['display_name'] or '',
name_description=volume['display_description'] or '',
sr_uuid=sr_uuid,
vdi_uuid=vdi_uuid,
sr_type='nfs',
server=FLAGS.xenapi_nfs_server,
serverpath=FLAGS.xenapi_nfs_serverpath,
introduce_sr_keys=['sr_type', 'server', 'serverpath']
)
)
def terminate_connection(self, volume, connector, force=False, **kwargs):
pass
def check_for_setup_error(self):
"""To override superclass' method"""
def create_volume_from_snapshot(self, volume, snapshot):
raise NotImplementedError()
def create_snapshot(self, snapshot):
raise NotImplementedError()
def delete_snapshot(self, snapshot):
raise NotImplementedError()
def ensure_export(self, context, volume):
pass
def copy_image_to_volume(self, context, volume, image_service, image_id):
raise NotImplementedError()
def copy_volume_to_image(self, context, volume, image_service, image_id):
raise NotImplementedError()