545 lines
18 KiB
Python
545 lines
18 KiB
Python
# 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
|
|
import os
|
|
import pickle
|
|
|
|
from cinder import units
|
|
from cinder.volume.drivers.xenapi import tools
|
|
|
|
|
|
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 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')
|
|
|
|
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)
|
|
|
|
def copy(self, vdi_ref, sr_ref):
|
|
return self.call_xenapi('VDI.copy', vdi_ref, sr_ref)
|
|
|
|
def resize(self, vdi_ref, size):
|
|
return self.call_xenapi('VDI.resize', vdi_ref, str(size))
|
|
|
|
|
|
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)
|
|
self.pool = PoolOperations(self)
|
|
self.VBD = VBDOperations(self)
|
|
self.VM = VMOperations(self)
|
|
|
|
def close(self):
|
|
return self.call_xenapi('logout')
|
|
|
|
@contextlib.contextmanager
|
|
def exception_converter(self):
|
|
try:
|
|
yield None
|
|
except self._exception_to_convert as e:
|
|
raise XenAPIException(e)
|
|
|
|
def call_xenapi(self, method, *args):
|
|
with self.exception_converter():
|
|
return self._session.xenapi_request(method, args)
|
|
|
|
def call_plugin(self, host_ref, plugin, function, args):
|
|
with self.exception_converter():
|
|
return self._session.xenapi.host.call_plugin(
|
|
host_ref, plugin, function, args)
|
|
|
|
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 * units.GiB
|
|
|
|
|
|
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)
|
|
|
|
def copy_vdi_to_sr(self, vdi_ref, sr_ref):
|
|
return self.VDI.copy(vdi_ref, sr_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 XapiPluginProxy(object):
|
|
def __init__(self, session_factory, plugin_name):
|
|
self._session_factory = session_factory
|
|
self._plugin_name = plugin_name
|
|
|
|
def call(self, function, *plugin_args, **plugin_kwargs):
|
|
plugin_params = dict(args=plugin_args, kwargs=plugin_kwargs)
|
|
args = dict(params=pickle.dumps(plugin_params))
|
|
|
|
with self._session_factory.get_session() as session:
|
|
host_ref = session.get_this_host()
|
|
result = session.call_plugin(
|
|
host_ref, self._plugin_name, function, args)
|
|
|
|
return pickle.loads(result)
|
|
|
|
|
|
class GlancePluginProxy(XapiPluginProxy):
|
|
def __init__(self, session_factory):
|
|
super(GlancePluginProxy, self).__init__(session_factory, 'glance')
|
|
|
|
def download_vhd(self, image_id, glance_host, glance_port, glance_use_ssl,
|
|
uuid_stack, sr_path, auth_token):
|
|
return self.call(
|
|
'download_vhd',
|
|
image_id=image_id,
|
|
glance_host=glance_host,
|
|
glance_port=glance_port,
|
|
glance_use_ssl=glance_use_ssl,
|
|
uuid_stack=uuid_stack,
|
|
sr_path=sr_path,
|
|
auth_token=auth_token)
|
|
|
|
def upload_vhd(self, vdi_uuids, image_id, glance_host, glance_port,
|
|
glance_use_ssl, sr_path, auth_token, properties):
|
|
return self.call(
|
|
'upload_vhd',
|
|
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=auth_token,
|
|
properties=properties)
|
|
|
|
|
|
class NFSBasedVolumeOperations(object):
|
|
def __init__(self, session_factory):
|
|
self._session_factory = session_factory
|
|
self.glance_plugin = GlancePluginProxy(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)
|
|
|
|
def copy_volume(self, server, serverpath, sr_uuid, vdi_uuid,
|
|
name=None, description=None):
|
|
with self._session_factory.get_session() as session:
|
|
src_refs = session.connect_volume(
|
|
server, serverpath, sr_uuid, vdi_uuid)
|
|
try:
|
|
host_ref = session.get_this_host()
|
|
|
|
with session.new_sr_on_nfs(host_ref, server, serverpath,
|
|
name, description) as target_sr_ref:
|
|
target_vdi_ref = session.copy_vdi_to_sr(
|
|
src_refs['vdi_ref'], target_sr_ref)
|
|
|
|
dst_refs = dict(
|
|
sr_uuid=session.SR.get_uuid(target_sr_ref),
|
|
vdi_uuid=session.VDI.get_uuid(target_vdi_ref)
|
|
)
|
|
|
|
finally:
|
|
session.unplug_pbds_and_forget_sr(src_refs['sr_ref'])
|
|
|
|
return dst_refs
|
|
|
|
def resize_volume(self, server, serverpath, sr_uuid, vdi_uuid,
|
|
size_in_gigabytes):
|
|
self.connect_volume(server, serverpath, sr_uuid, vdi_uuid)
|
|
|
|
try:
|
|
with self._session_factory.get_session() as session:
|
|
vdi_ref = session.VDI.get_by_uuid(vdi_uuid)
|
|
session.VDI.resize(vdi_ref, to_bytes(size_in_gigabytes))
|
|
finally:
|
|
self.disconnect_volume(vdi_uuid)
|
|
|
|
def use_glance_plugin_to_overwrite_volume(self, server, serverpath,
|
|
sr_uuid, vdi_uuid, glance_server,
|
|
image_id, auth_token,
|
|
sr_base_path):
|
|
self.connect_volume(server, serverpath, sr_uuid, vdi_uuid)
|
|
|
|
uuid_stack = [vdi_uuid]
|
|
glance_host, glance_port, glance_use_ssl = glance_server
|
|
|
|
try:
|
|
result = self.glance_plugin.download_vhd(
|
|
image_id, glance_host, glance_port, glance_use_ssl, uuid_stack,
|
|
os.path.join(sr_base_path, sr_uuid), auth_token)
|
|
finally:
|
|
self.disconnect_volume(vdi_uuid)
|
|
|
|
if len(result) != 1 or result['root']['uuid'] != vdi_uuid:
|
|
return False
|
|
|
|
return True
|
|
|
|
def use_glance_plugin_to_upload_volume(self, server, serverpath,
|
|
sr_uuid, vdi_uuid, glance_server,
|
|
image_id, auth_token, sr_base_path):
|
|
self.connect_volume(server, serverpath, sr_uuid, vdi_uuid)
|
|
|
|
vdi_uuids = [vdi_uuid]
|
|
glance_host, glance_port, glance_use_ssl = glance_server
|
|
|
|
try:
|
|
result = self.glance_plugin.upload_vhd(
|
|
vdi_uuids, image_id, glance_host, glance_port, glance_use_ssl,
|
|
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)
|