mirror of
https://github.com/xen-project/xen.git
synced 2026-10-28 10:11:27 +01:00
[XEND] Updates to SR and VDI implementations
* Moved xenapi transport util method, stringify to xen.util.xmlrpclib2 * XenVDI now preserves configuration to an XML-ish file * Update Xen API's class names to be all lowercase * Update get_by_label to get_by_name_label and return sets as the API expects. * Add support for VBD creation with a VDI reference. Signed-off-by: Alastair Tse <atse@xensource.com>
This commit is contained in:
1 parent
2556368450
commit
76b24ff4e2
8 files changed
+474
-115
No files matched your search
@@ -21,15 +21,23 @@ from optparse import *
|
||||
from pprint import pprint
|
||||
from types import DictType
|
||||
|
||||
MB = 1024 * 1024
|
||||
|
||||
HOST_INFO_FORMAT = '%-20s: %-50s'
|
||||
VM_LIST_FORMAT = '%(name_label)-18s %(memory_actual)-5s %(vcpus_number)-5s'\
|
||||
' %(power_state)-12s %(uuid)-32s'
|
||||
|
||||
' %(power_state)-12s %(uuid)-36s'
|
||||
SR_LIST_FORMAT = '%(name_label)-18s %(uuid)-36s %(physical_size)-10s' \
|
||||
'%(type)-10s'
|
||||
VDI_LIST_FORMAT = '%(name_label)-18s %(uuid)-36s %(virtual_size)-8s '\
|
||||
'%(sector_size)-8s'
|
||||
LOGIN = ('atse', 'passwd')
|
||||
|
||||
COMMANDS = {
|
||||
'host-info': ('', 'Get Xen Host Info'),
|
||||
'sr-list': ('', 'List all SRs'),
|
||||
'vbd-create': ('<domname> <pycfg>', 'Create VBD attached to domname'),
|
||||
'vdi-list' : ('', 'List all VDI'),
|
||||
'vdi-delete': ('<vdi_uuid>', 'Delete VDI'),
|
||||
'vif-create': ('<domname> <pycfg>', 'Create VIF attached to domname'),
|
||||
|
||||
'vm-create': ('<pycfg>', 'Create VM with python config'),
|
||||
@@ -84,8 +92,8 @@ def execute(fn, *args):
|
||||
|
||||
def _connect(*args):
|
||||
server = ServerProxy('httpu:///var/run/xend/xmlrpc.sock')
|
||||
session = execute(server.Session.login_with_password, *LOGIN)
|
||||
host = execute(server.Session.get_this_host, session)
|
||||
session = execute(server.session.login_with_password, *LOGIN)
|
||||
host = execute(server.session.get_this_host, session)
|
||||
return (server, session)
|
||||
|
||||
def _stringify(adict):
|
||||
@@ -248,8 +256,55 @@ def xapi_vif_create(*args):
|
||||
vif_uuid = execute(server.VIF.create, session, cfg)
|
||||
print 'Done. (%s)' % vif_uuid
|
||||
|
||||
|
||||
def xapi_vdi_list(*args):
|
||||
server, session = _connect()
|
||||
vdis = execute(server.VDI.get_all, session)
|
||||
|
||||
print VDI_LIST_FORMAT % {'name_label': 'VDI Label',
|
||||
'uuid' : 'UUID',
|
||||
'virtual_size': 'Sectors',
|
||||
'sector_size': 'Sector Size'}
|
||||
|
||||
for vdi in vdis:
|
||||
vdi_struct = execute(server.VDI.get_record, session, vdi)
|
||||
print VDI_LIST_FORMAT % vdi_struct
|
||||
|
||||
def xapi_sr_list(*args):
|
||||
server, session = _connect()
|
||||
srs = execute(server.SR.get_all, session)
|
||||
print SR_LIST_FORMAT % {'name_label': 'SR Label',
|
||||
'uuid' : 'UUID',
|
||||
'physical_size': 'Size',
|
||||
'type': 'Type'}
|
||||
for sr in srs:
|
||||
sr_struct = execute(server.SR.get_record, session, sr)
|
||||
sr_struct['physical_size'] = int(sr_struct['physical_size'])/MB
|
||||
print SR_LIST_FORMAT % sr_struct
|
||||
|
||||
def xapi_vdi_create(*args):
|
||||
server, session = _connect()
|
||||
cfg = _read_python_cfg(args[0])
|
||||
|
||||
srs = execute(server.SR.get_all, session)
|
||||
sr = srs[0]
|
||||
cfg['SR'] = sr
|
||||
|
||||
size = (cfg['virtual_size'] * cfg['sector_size'])/MB
|
||||
print 'Creating VDI of size: %dMB' % size
|
||||
uuid = execute(server.VDI.create, session, cfg)
|
||||
print 'Done. (%s)' % uuid
|
||||
|
||||
def xapi_vdi_delete(*args):
|
||||
server, session = _connect()
|
||||
if len(args) < 1:
|
||||
raise OptionError('Not enough arguments')
|
||||
|
||||
vdi_uuid = args[0]
|
||||
print 'Deleting VDI %s' % vdi_uuid
|
||||
result = execute(server.VDI.destroy, session, vdi_uuid)
|
||||
print 'Done.'
|
||||
|
||||
|
||||
#
|
||||
# Command Line Utils
|
||||
#
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
name_label = 'VDI 1'
|
||||
name_description = ''
|
||||
virtual_size = 10 * 1024
|
||||
sector_size = 1024
|
||||
type = 'system'
|
||||
sharable = False
|
||||
read_only = False
|
||||
@@ -21,8 +21,9 @@ An enhanced XML-RPC client/server interface for Python.
|
||||
"""
|
||||
|
||||
import string
|
||||
import types
|
||||
import fcntl
|
||||
from types import *
|
||||
|
||||
|
||||
from httplib import HTTPConnection, HTTP
|
||||
from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
|
||||
@@ -39,6 +40,23 @@ except ImportError:
|
||||
# package.
|
||||
ssh_enabled = False
|
||||
|
||||
#
|
||||
# Convert all integers to strings as described in the Xen API
|
||||
#
|
||||
|
||||
|
||||
def stringify(value):
|
||||
if isinstance(value, IntType) and not isinstance(value, BooleanType):
|
||||
return str(value)
|
||||
elif isinstance(value, DictType):
|
||||
for k, v in value.items():
|
||||
value[k] = stringify(v)
|
||||
return value
|
||||
elif isinstance(value, (TupleType, ListType)):
|
||||
return [stringify(v) for v in value]
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
# A new ServerProxy that also supports httpu urls. An http URL comes in the
|
||||
# form:
|
||||
@@ -91,8 +109,7 @@ class UnixTransport(xmlrpclib.Transport):
|
||||
|
||||
# See _marshalled_dispatch below.
|
||||
def conv_string(x):
|
||||
if (isinstance(x, types.StringType) or
|
||||
isinstance(x, unicode)):
|
||||
if isinstance(x, StringTypes):
|
||||
s = string.replace(x, "'", r"\047")
|
||||
exec "s = '" + s + "'"
|
||||
return s
|
||||
@@ -169,8 +186,7 @@ class TCPXMLRPCServer(SocketServer.ThreadingMixIn, SimpleXMLRPCServer):
|
||||
# to transmit the string using Python encoding.
|
||||
# Thanks to David Mertz <mertz@gnosis.cx> for the trick (buried
|
||||
# in xml_pickle.py).
|
||||
if (isinstance(response, types.StringType) or
|
||||
isinstance(response, unicode)):
|
||||
if isinstance(response, StringTypes):
|
||||
response = repr(response)[1:-1]
|
||||
|
||||
response = (response,)
|
||||
|
||||
@@ -25,23 +25,10 @@ from xen.xend.XendClient import ERROR_INVALID_DOMAIN
|
||||
from xen.xend.XendLogging import log
|
||||
|
||||
from xen.xend.XendAPIConstants import *
|
||||
from xen.util.xmlrpclib2 import stringify
|
||||
|
||||
from types import *
|
||||
|
||||
def _stringify(value):
|
||||
if isinstance(value, IntType) and not isinstance(value, BooleanType):
|
||||
return str(value)
|
||||
elif isinstance(value, DictType):
|
||||
for k, v in value.items():
|
||||
value[k] = _stringify(v)
|
||||
return value
|
||||
elif isinstance(value, (TupleType, ListType)):
|
||||
return [_stringify(v) for v in value]
|
||||
else:
|
||||
return value
|
||||
|
||||
def xen_api_success(value):
|
||||
return {"Status": "Success", "Value": _stringify(value)}
|
||||
return {"Status": "Success", "Value": stringify(value)}
|
||||
|
||||
def xen_api_success_void():
|
||||
"""Return success, but caller expects no return value."""
|
||||
@@ -252,7 +239,7 @@ class XendAPI:
|
||||
"""
|
||||
|
||||
classes = {
|
||||
'Session': (session_required,),
|
||||
'session': (session_required,),
|
||||
'host': (valid_host, session_required),
|
||||
'host_cpu': (valid_host_cpu, session_required),
|
||||
'VM': (valid_vm, session_required),
|
||||
@@ -346,9 +333,9 @@ class XendAPI:
|
||||
# ----------------------------------------------------------------
|
||||
# NOTE: Left unwrapped by __init__
|
||||
|
||||
Session_attr_ro = ['this_host', 'this_user']
|
||||
Session_methods = ['logout']
|
||||
# Session_funcs = ['login_with_password']
|
||||
session_attr_ro = ['this_host', 'this_user']
|
||||
session_methods = ['logout']
|
||||
# session_funcs = ['login_with_password']
|
||||
|
||||
def session_login_with_password(self, username, password):
|
||||
try:
|
||||
@@ -356,7 +343,7 @@ class XendAPI:
|
||||
return xen_api_success(session)
|
||||
except XendError, e:
|
||||
return xen_api_error(XEND_ERROR_AUTHENTICATION_FAILED)
|
||||
session_login_with_password.api = 'Session.login_with_password'
|
||||
session_login_with_password.api = 'session.login_with_password'
|
||||
|
||||
|
||||
# object methods
|
||||
@@ -405,7 +392,7 @@ class XendAPI:
|
||||
'reboot',
|
||||
'shutdown']
|
||||
|
||||
host_funcs = ['get_by_label']
|
||||
host_funcs = ['get_by_name_label']
|
||||
|
||||
# attributes
|
||||
def host_get_name_label(self, session, host_ref):
|
||||
@@ -572,7 +559,7 @@ class XendAPI:
|
||||
'suspend',
|
||||
'resume']
|
||||
|
||||
VM_funcs = ['get_by_label']
|
||||
VM_funcs = ['get_by_name_label']
|
||||
|
||||
# parameters required for _create()
|
||||
VM_attr_inst = [
|
||||
@@ -892,7 +879,8 @@ class XendAPI:
|
||||
def vm_get_all(self, session):
|
||||
refs = [d.get_uuid() for d in XendDomain.instance().list()]
|
||||
return xen_api_success(refs)
|
||||
def vm_get_by_label(self, session, label):
|
||||
|
||||
def vm_get_by_name_label(self, session, label):
|
||||
xendom = XendDomain.instance()
|
||||
dom = xendom.domain_lookup_nr(label)
|
||||
if dom:
|
||||
@@ -1022,16 +1010,27 @@ class XendAPI:
|
||||
# class methods
|
||||
def vbd_create(self, session, vbd_struct):
|
||||
xendom = XendDomain.instance()
|
||||
if xendom.is_valid_vm(vbd_struct['VM']):
|
||||
dom = xendom.get_vm_by_uuid(vbd_struct['VM'])
|
||||
try:
|
||||
vbd_ref = dom.create_vbd(vbd_struct)
|
||||
xendom.managed_config_save(dom)
|
||||
return xen_api_success(vbd_ref)
|
||||
except XendError:
|
||||
return xen_api_error(XEND_ERROR_TODO)
|
||||
else:
|
||||
if not xendom.is_valid_vm(vbd_struct['VM']):
|
||||
return xen_api_error(XEND_ERROR_DOMAIN_INVALID)
|
||||
|
||||
dom = xendom.get_vm_by_uuid(vbd_struct['VM'])
|
||||
vbd_ref = ''
|
||||
try:
|
||||
if vbd_struct.get('VDI', None):
|
||||
# this is a traditional VBD without VDI and SR
|
||||
vbd_ref = dom.create_vbd(vbd_struct)
|
||||
else:
|
||||
# new VBD via VDI/SR
|
||||
vdi_ref = vbd_struct.get('VDI')
|
||||
sr = XendNode.instance().get_sr()
|
||||
vdi_image = sr.xen_api_get_by_uuid(vdi_ref)
|
||||
vdi_image_path = vdi_image.image_path
|
||||
vbd_ref = dom.create_vbd_with_vdi(vbd_struct, vdi_image_path)
|
||||
except XendError:
|
||||
return xen_api_todo()
|
||||
|
||||
xendom.managed_config_save(dom)
|
||||
return xen_api_success(vbd_ref)
|
||||
|
||||
# attributes (rw)
|
||||
def vbd_get_vm(self, session, vbd_ref):
|
||||
@@ -1118,61 +1117,144 @@ class XendAPI:
|
||||
VDI_attr_inst = VDI_attr_ro + VDI_attr_rw
|
||||
|
||||
VDI_methods = ['snapshot']
|
||||
VDI_funcs = ['get_by_label']
|
||||
VDI_funcs = ['get_by_name_label']
|
||||
|
||||
def vdi_get_vbds(self, session, vdi_ref):
|
||||
return xen_api_todo()
|
||||
|
||||
def vdi_get_physical_utilisation(self, session, vdi_ref):
|
||||
return xen_api_todo()
|
||||
sr = XendNode.instance().get_sr()
|
||||
image = sr.xen_api_get_by_uuid(vdi_ref)
|
||||
return xen_api_success(image.get_physical_utilisation())
|
||||
|
||||
def vdi_get_sector_size(self, session, vdi_ref):
|
||||
return xen_api_todo()
|
||||
sr = XendNode.instance().get_sr()
|
||||
image = sr.xen_api_get_by_uuid(vdi_ref)
|
||||
return xen_api_success(image.sector_size)
|
||||
|
||||
def vdi_get_type(self, session, vdi_ref):
|
||||
return xen_api_todo()
|
||||
sr = XendNode.instance().get_sr()
|
||||
image = sr.xen_api_get_by_uuid(vdi_ref)
|
||||
return xen_api_success(image.type)
|
||||
|
||||
def vdi_get_parent(self, session, vdi_ref):
|
||||
return xen_api_todo()
|
||||
sr = XendNode.instance().get_sr()
|
||||
image = sr.xen_api_get_by_uuid(vdi_ref)
|
||||
return xen_api_success(image.parent)
|
||||
|
||||
def vdi_get_children(self, session, vdi_ref):
|
||||
return xen_api_todo()
|
||||
sr = XendNode.instance().get_sr()
|
||||
image = sr.xen_api_get_by_uuid(vdi_ref)
|
||||
return xen_api_success(image.children)
|
||||
|
||||
def vdi_get_name_label(self, session, vdi_ref):
|
||||
return xen_api_todo()
|
||||
sr = XendNode.instance().get_sr()
|
||||
image = sr.xen_api_get_by_uuid(vdi_ref)
|
||||
return xen_api_success(image.name_label)
|
||||
|
||||
def vdi_get_name_description(self, session, vdi_ref):
|
||||
return xen_api_todo()
|
||||
sr = XendNode.instance().get_sr()
|
||||
image = sr.xen_api_get_by_uuid(vdi_ref)
|
||||
return xen_api_success(image.name_description)
|
||||
|
||||
def vdi_get_sr(self, session, vdi_ref):
|
||||
return xen_api_todo()
|
||||
sr = XendNode.instance().get_sr()
|
||||
return xen_api_success(sr.uuid)
|
||||
|
||||
def vdi_get_virtual_size(self, session, vdi_ref):
|
||||
return xen_api_todo()
|
||||
sr = XendNode.instance().get_sr()
|
||||
image = sr.xen_api_get_by_uuid(vdi_ref)
|
||||
return xen_api_success(image.virtual_size)
|
||||
|
||||
def vdi_get_sharable(self, session, vdi_ref):
|
||||
return xen_api_todo()
|
||||
sr = XendNode.instance().get_sr()
|
||||
image = sr.xen_api_get_by_uuid(vdi_ref)
|
||||
return xen_api_success(image.sharable)
|
||||
|
||||
def vdi_get_read_only(self, session, vdi_ref):
|
||||
return xen_api_todo()
|
||||
def vdi_get_uuid(self, session, vdi_ref):
|
||||
return xen_api_todo()
|
||||
sr = XendNode.instance().get_sr()
|
||||
image = sr.xen_api_get_by_uuid(vdi_ref)
|
||||
return xen_api_success(image.sharable)
|
||||
|
||||
def vdi_set_name_label(self, session, vdi_ref, value):
|
||||
return xen_api_todo()
|
||||
sr = XendNode.instance().get_sr()
|
||||
image = sr.xen_api_get_by_uuid(vdi_ref)
|
||||
image.name_label = value
|
||||
return xen_api_success_void()
|
||||
|
||||
def vdi_set_name_description(self, session, vdi_ref, value):
|
||||
return xen_api_todo()
|
||||
sr = XendNode.instance().get_sr()
|
||||
image = sr.xen_api_get_by_uuid(vdi_ref)
|
||||
image.name_description = value
|
||||
return xen_api_success_void()
|
||||
|
||||
def vdi_set_sr(self, session, vdi_ref, value):
|
||||
return xen_api_todo()
|
||||
return xen_api_error(XEND_ERROR_UNSUPPORTED)
|
||||
|
||||
def vdi_set_virtual_size(self, session, vdi_ref, value):
|
||||
return xen_api_todo()
|
||||
return xen_api_error(XEND_ERROR_UNSUPPORTED)
|
||||
|
||||
def vdi_set_sharable(self, session, vdi_ref, value):
|
||||
return xen_api_todo()
|
||||
def vdi_set_read_only(self, session, vdi_ref, value):
|
||||
return xen_api_todo()
|
||||
|
||||
# Object Methods
|
||||
def vdi_snapshot(self, session, vdi_ref):
|
||||
return xen_api_todo()
|
||||
|
||||
def vdi_destroy(self, session, vdi_ref):
|
||||
return xen_api_todo()
|
||||
sr = XendNode.instance().get_sr()
|
||||
sr.destroy_image(vdi_ref)
|
||||
return xen_api_success_void()
|
||||
|
||||
def vdi_to_xml(self, session, vdi_ref):
|
||||
return xen_api_todo()
|
||||
|
||||
def vdi_get_record(self, session, vdi_ref):
|
||||
return xen_api_todo()
|
||||
def vdi_create(self, session):
|
||||
return xen_api_todo()
|
||||
def vdi_get_by_uuid(self, session):
|
||||
return xen_api_todo()
|
||||
sr = XendNode.instance().get_sr()
|
||||
image = sr.xen_api_get_by_uuid(vdi_ref)
|
||||
if image:
|
||||
return xen_api_success({
|
||||
'uuid': vdi_ref,
|
||||
'name_label': image.name_label,
|
||||
'name_description': image.name_description,
|
||||
'SR': sr.uuid,
|
||||
'VBDs': [], # TODO
|
||||
'virtual_size': image.virtual_size,
|
||||
'physical_utilisation': image.physical_utilisation,
|
||||
'sector_size': image.sector_size,
|
||||
'type': image.type,
|
||||
'parent': image.parent,
|
||||
'children': image.children,
|
||||
'sharable': image.sharable,
|
||||
'read_only': image.read_only,
|
||||
})
|
||||
|
||||
return xen_api_error(XEND_ERROR_VDI_INVALID)
|
||||
|
||||
# Class Functions
|
||||
def vdi_create(self, session, vdi_struct):
|
||||
sr = XendNode.instance().get_sr()
|
||||
sr_ref = vdi_struct['SR']
|
||||
if sr.uuid != sr_ref:
|
||||
return xen_api_error(XEND_ERROR_SR_INVALID)
|
||||
|
||||
vdi_uuid = sr.create_image(vdi_struct)
|
||||
return xen_api_success(vdi_uuid)
|
||||
|
||||
def vdi_get_all(self, session):
|
||||
return xen_api_todo()
|
||||
def vdi_get_by_label(self, session):
|
||||
return xen_api_todo()
|
||||
sr = XendNode.instance().get_sr()
|
||||
return xen_api_success(sr.list_images())
|
||||
|
||||
def vdi_get_by_name_label(self, session, name):
|
||||
sr = XendNode.instance().get_sr()
|
||||
image_uuid = sr.xen_api_get_by_name_label(name)
|
||||
if image_uuid:
|
||||
return xen_api_success(image_uuid)
|
||||
|
||||
return xen_api_error(XEND_ERROR_VDI_INVALID)
|
||||
|
||||
|
||||
# Xen API: Class SR
|
||||
# ----------------------------------------------------------------
|
||||
@@ -1193,14 +1275,14 @@ class XendAPI:
|
||||
'name_description']
|
||||
|
||||
SR_methods = ['clone']
|
||||
SR_funcs = ['get_by_label']
|
||||
SR_funcs = ['get_by_name_label']
|
||||
|
||||
# Class Functions
|
||||
def sr_get_all(self, session):
|
||||
sr = XendNode.instance().get_sr()
|
||||
return xen_api_success([sr.uuid])
|
||||
|
||||
def sr_get_by_label(self, session, label):
|
||||
def sr_get_by_name_label(self, session, label):
|
||||
sr = XendNode.instance().get_sr()
|
||||
if sr.name_label != label:
|
||||
return xen_api_error(XEND_ERROR_SR_INVALID)
|
||||
|
||||
@@ -566,7 +566,7 @@ class XendConfig(dict):
|
||||
for dev_uuid, (dev_type, dev_info) in cfg['device'].items():
|
||||
if dev_type == 'vif':
|
||||
cfg['vif_refs'].append(dev_uuid)
|
||||
elif dev_type == 'vbd':
|
||||
elif dev_type in ('vbd','tap'):
|
||||
cfg['vbd_refs'].append(dev_uuid)
|
||||
|
||||
return cfg
|
||||
@@ -771,6 +771,8 @@ class XendConfig(dict):
|
||||
self['device'][dev_uuid] = (dev_type, dev_info)
|
||||
if dev_type in ('vif', 'vbd'):
|
||||
self['%s_refs' % dev_type].append(dev_uuid)
|
||||
elif dev_type in ('tap',):
|
||||
self['vbd_refs'].append(dev_uuid)
|
||||
return dev_uuid
|
||||
|
||||
if cfg_xenapi:
|
||||
@@ -805,7 +807,21 @@ class XendConfig(dict):
|
||||
self['device'][dev_uuid] = (dev_type, dev_info)
|
||||
self['vbd_refs'].append(dev_uuid)
|
||||
return dev_uuid
|
||||
|
||||
elif dev_type == 'tap':
|
||||
dev_info['uname'] = 'tap:qcow:%s' % cfg_xenapi.get('image')
|
||||
dev_info['dev'] = '%s:disk' % cfg_xenapi.get('device')
|
||||
|
||||
if cfg_xenapi.get('mode') == 'RW':
|
||||
dev_info['mode'] = 'w'
|
||||
else:
|
||||
dev_info['mode'] = 'r'
|
||||
|
||||
dev_uuid = cfg_xenapi.get('uuid', uuid.createString())
|
||||
dev_info['uuid'] = dev_uuid
|
||||
self['device'][dev_uuid] = (dev_type, dev_info)
|
||||
self['vbd_refs'].append(dev_uuid)
|
||||
return dev_uuid
|
||||
|
||||
return ''
|
||||
|
||||
|
||||
@@ -1826,8 +1826,33 @@ class XendDomainInfo:
|
||||
|
||||
return dev_uuid
|
||||
|
||||
def create_vbd_with_vdi(self, xenapi_vbd, vdi_image_path):
|
||||
"""Create a VBD using a VDI from XendStorageRepository.
|
||||
|
||||
@param xenapi_vbd: vbd struct from the Xen API
|
||||
@param vdi_image_path: VDI UUID
|
||||
@rtype: string
|
||||
@return: uuid of the device
|
||||
"""
|
||||
xenapi_vbd['image'] = vdi_image_path
|
||||
dev_uuid = self.info.device_add('tap', cfg_xenapi = xenapi_vbd)
|
||||
if not dev_uuid:
|
||||
raise XendError('Failed to create device')
|
||||
|
||||
if self.state in (XEN_API_VM_POWER_STATE_RUNNING,):
|
||||
sxpr = self.info.device_sxpr(dev_uuid)
|
||||
devid = self.getDeviceController('tap').createDevice(sxpr)
|
||||
raise XendError("Device creation failed")
|
||||
|
||||
return dev_uuid
|
||||
|
||||
def create_vif(self, xenapi_vif):
|
||||
|
||||
"""Create VIF device from the passed struct in Xen API format.
|
||||
|
||||
@param xenapi_vif: Xen API VIF Struct.
|
||||
@rtype: string
|
||||
@return: UUID
|
||||
"""
|
||||
dev_uuid = self.info.device_add('vif', cfg_xenapi = xenapi_vif)
|
||||
if not dev_uuid:
|
||||
raise XendError('Failed to create device')
|
||||
|
||||
@@ -31,7 +31,8 @@ XEND_STORAGE_MAX_IGNORE = -1
|
||||
XEND_STORAGE_DIR = "/var/lib/xend/storage/"
|
||||
XEND_STORAGE_QCOW_FILENAME = "%s.qcow"
|
||||
XEND_STORAGE_IMG_FILENAME = "%s.img"
|
||||
DF_COMMAND = "df -kl"
|
||||
XEND_STORAGE_VDICFG_FILENAME = "%s.vdi.xml"
|
||||
DF_COMMAND = "df -lP"
|
||||
QCOW_CREATE_COMMAND = "/usr/sbin/qcow-create %d %s %s"
|
||||
|
||||
KB = 1024
|
||||
@@ -55,7 +56,7 @@ class XendStorageRepository:
|
||||
"""
|
||||
@keyword storage_dir: Where the images will be stored.
|
||||
@type storage_dir: string
|
||||
@keyword storage_max: Maximum disk space to use in KB.
|
||||
@keyword storage_max: Maximum disk space to use in bytes.
|
||||
@type storage_max: int
|
||||
|
||||
@ivar storage_free: storage space free for this repository
|
||||
@@ -82,7 +83,7 @@ class XendStorageRepository:
|
||||
def _sr_uuid(self):
|
||||
uuid_file = os.path.join(XEND_STORAGE_DIR, 'uuid')
|
||||
try:
|
||||
if os.path.exists(uuid_file):
|
||||
if uuid_file and os.path.exists(uuid_file):
|
||||
return open(uuid_file, 'r').read().strip()
|
||||
else:
|
||||
new_uuid = uuid.createString()
|
||||
@@ -114,16 +115,25 @@ class XendStorageRepository:
|
||||
if image_uuid not in self.images:
|
||||
image_file = XEND_STORAGE_IMG_FILENAME % image_uuid
|
||||
qcow_file = XEND_STORAGE_QCOW_FILENAME % image_uuid
|
||||
image_path = os.path.join(XEND_STORAGE_DIR,
|
||||
image_file)
|
||||
cfg_file = XEND_STORAGE_VDICFG_FILENAME % image_uuid
|
||||
|
||||
image_path = os.path.join(XEND_STORAGE_DIR,image_file)
|
||||
qcow_path = os.path.join(XEND_STORAGE_DIR, qcow_file)
|
||||
image_size_kb = (os.stat(image_path).st_size)/1024
|
||||
cfg_path = os.path.join(XEND_STORAGE_DIR, cfg_file)
|
||||
|
||||
qcow_size = os.stat(qcow_path).st_size
|
||||
image_size = os.stat(image_path).st_size
|
||||
|
||||
vdi = XendQCOWVDI(image_uuid, self.uuid,
|
||||
qcow_path, image_path,
|
||||
image_size_kb, image_size_kb)
|
||||
qcow_path, image_path, cfg_path,
|
||||
image_size,
|
||||
qcow_size + image_size)
|
||||
|
||||
if cfg_path and os.path.exists(cfg_path):
|
||||
vdi.load_config(cfg_path)
|
||||
|
||||
self.images[image_uuid] = vdi
|
||||
total_used += image_size_kb
|
||||
total_used += image_size
|
||||
|
||||
# remove images that aren't valid
|
||||
for image_uuid in self.images.keys():
|
||||
@@ -147,7 +157,7 @@ class XendStorageRepository:
|
||||
def _get_df(self):
|
||||
"""Returns the output of 'df' in a dictionary where the keys
|
||||
are the Linux device numbers, and the values are it's corresponding
|
||||
free space in KB.
|
||||
free space in bytes
|
||||
|
||||
@rtype: dictionary
|
||||
"""
|
||||
@@ -162,7 +172,7 @@ class XendStorageRepository:
|
||||
return devnum_free
|
||||
|
||||
def _get_free_space(self):
|
||||
"""Returns the amount of free space in KB available in the storage
|
||||
"""Returns the amount of free space in bytes available in the storage
|
||||
partition. Note that this may not be used if the storage repository
|
||||
is initialised with a maximum size in storage_max.
|
||||
|
||||
@@ -175,7 +185,7 @@ class XendStorageRepository:
|
||||
raise DeviceInvalidError("Device not found for storage path: %s" %
|
||||
self.storage_dir)
|
||||
|
||||
def _has_space_available_for(self, size_kb):
|
||||
def _has_space_available_for(self, size_bytes):
|
||||
"""Returns whether there is enough space for an image in the
|
||||
partition which the storage_dir resides on.
|
||||
|
||||
@@ -184,15 +194,15 @@ class XendStorageRepository:
|
||||
if self.storage_max != -1:
|
||||
return self.storage_free
|
||||
|
||||
kb_free = self._get_free_space()
|
||||
bytes_free = self._get_free_space()
|
||||
try:
|
||||
if size_kb < kb_free:
|
||||
if size_bytes < bytes_free:
|
||||
return True
|
||||
except DeviceInvalidError:
|
||||
pass
|
||||
return False
|
||||
|
||||
def create_image(self, desired_size_kb):
|
||||
def _create_image_files(self, desired_size_bytes):
|
||||
"""Create an image and return its assigned UUID.
|
||||
|
||||
@param desired_size_kb: Desired image size in KB.
|
||||
@@ -204,23 +214,28 @@ class XendStorageRepository:
|
||||
"""
|
||||
self.lock.acquire()
|
||||
try:
|
||||
if not self._has_space_available_for(desired_size_kb):
|
||||
if not self._has_space_available_for(desired_size_bytes):
|
||||
raise XendError("Not enough space")
|
||||
|
||||
image_uuid = uuid.createString()
|
||||
# create file based image
|
||||
image_path = os.path.join(XEND_STORAGE_DIR,
|
||||
XEND_STORAGE_IMG_FILENAME % image_uuid)
|
||||
block = '\x00' * 1024
|
||||
|
||||
if image_path and os.path.exists(image_path):
|
||||
raise XendError("Image with same UUID alreaady exists:" %
|
||||
image_uuid)
|
||||
|
||||
block = '\x00' * KB
|
||||
img = open(image_path, 'w')
|
||||
for i in range(desired_size_kb):
|
||||
for i in range(desired_size_bytes/KB):
|
||||
img.write(block)
|
||||
img.close()
|
||||
|
||||
# TODO: create qcow image
|
||||
qcow_path = os.path.join(XEND_STORAGE_DIR,
|
||||
XEND_STORAGE_QCOW_FILENAME % image_uuid)
|
||||
cmd = QCOW_CREATE_COMMAND % (desired_size_kb/1024,
|
||||
cmd = QCOW_CREATE_COMMAND % (desired_size_bytes/MB,
|
||||
qcow_path, image_path)
|
||||
|
||||
rc, output = commands.getstatusoutput(cmd)
|
||||
@@ -233,7 +248,7 @@ class XendStorageRepository:
|
||||
return image_uuid
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
|
||||
def destroy_image(self, image_uuid):
|
||||
"""Destroy an image that is managed by this storage repository.
|
||||
|
||||
@@ -247,9 +262,12 @@ class XendStorageRepository:
|
||||
# TODO: check if it is being used?
|
||||
qcow_path = self.images[image_uuid].qcow_path
|
||||
image_path = self.images[image_uuid].image_path
|
||||
cfg_path = self.images[image_uuid].cfg_path
|
||||
try:
|
||||
os.unlink(qcow_path)
|
||||
os.unlink(image_path)
|
||||
if cfg_path and os.path.exists(cfg_path):
|
||||
os.unlink(cfg_path)
|
||||
except OSError:
|
||||
# TODO: log warning
|
||||
pass
|
||||
@@ -272,7 +290,7 @@ class XendStorageRepository:
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def free_space_kb(self):
|
||||
def free_space_bytes(self):
|
||||
"""Returns the amount of available space in KB.
|
||||
@rtype: int
|
||||
"""
|
||||
@@ -282,7 +300,7 @@ class XendStorageRepository:
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def total_space_kb(self):
|
||||
def total_space_bytes(self):
|
||||
"""Returns the total usable space of the storage repo in KB.
|
||||
@rtype: int
|
||||
"""
|
||||
@@ -295,7 +313,7 @@ class XendStorageRepository:
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def used_space_kb(self):
|
||||
def used_space_bytes(self):
|
||||
"""Returns the total amount of space used by this storage repository.
|
||||
@rtype: int
|
||||
"""
|
||||
@@ -308,25 +326,72 @@ class XendStorageRepository:
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def used_space_bytes(self):
|
||||
return self.used_space_kb() * KB
|
||||
def free_space_bytes(self):
|
||||
return self.free_space_kb() * KB
|
||||
def total_space_bytes(self):
|
||||
return self.total_space_kb() * KB
|
||||
|
||||
def is_valid_vdi(self, vdi_uuid):
|
||||
return (vdi_uuid in self.images)
|
||||
|
||||
def create_image(self, vdi_struct):
|
||||
image_uuid = None
|
||||
try:
|
||||
sector_count = int(vdi_struct.get('virtual_size', 0))
|
||||
sector_size = int(vdi_struct.get('sector_size', 1024))
|
||||
size_bytes = (sector_count * sector_size)
|
||||
|
||||
image_uuid = self._create_image_files(size_bytes)
|
||||
image = self.images[image_uuid]
|
||||
image_cfg = {
|
||||
'sector_size': sector_size,
|
||||
'virtual_size': sector_count,
|
||||
'type': vdi_struct.get('type', 'system'),
|
||||
'name_label': vdi_struct.get('name_label', ''),
|
||||
'name_description': vdi_struct.get('name_description', ''),
|
||||
'sharable': bool(vdi_struct.get('sharable', False)),
|
||||
'read_only': bool(vdi_struct.get('read_only', False)),
|
||||
}
|
||||
|
||||
# load in configuration from vdi_struct
|
||||
image.load_config_dict(image_cfg)
|
||||
|
||||
# save configuration to file
|
||||
cfg_filename = XEND_STORAGE_VDICFG_FILENAME % image_uuid
|
||||
cfg_path = os.path.join(XEND_STORAGE_DIR, cfg_filename)
|
||||
image.save_config(cfg_path)
|
||||
|
||||
except Exception, e:
|
||||
# cleanup before raising exception
|
||||
if image_uuid:
|
||||
self.destroy_image(image_uuid)
|
||||
|
||||
raise
|
||||
|
||||
return image_uuid
|
||||
|
||||
def xen_api_get_by_label(self, label):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
for image_uuid, val in self.images.values():
|
||||
if val.name_label == label:
|
||||
return image_uuid
|
||||
return None
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def xen_api_get_by_uuid(self, image_uuid):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
return self.images.get(image_uuid)
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
|
||||
# remove everything below this line!!
|
||||
if __name__ == "__main__":
|
||||
xsr = XendStorageRepository()
|
||||
print 'Free Space: %d MB' % (xsr.free_space_kb()/1024)
|
||||
print 'Free Space: %d MB' % (xsr.free_space_bytes()/MB)
|
||||
print "Create Image:",
|
||||
print xsr.create_image(10 * 1024)
|
||||
print xsr._create_image_files(10 * MB)
|
||||
print 'Delete all images:'
|
||||
for image_uuid in xsr.list_images():
|
||||
print image_uuid,
|
||||
xsr.destroy_image(image_uuid)
|
||||
xsr._destroy_image_files(image_uuid)
|
||||
|
||||
print
|
||||
@@ -19,26 +19,119 @@
|
||||
# Representation of a Xen API VDI
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
from xen.util.xmlrpclib2 import stringify
|
||||
from xmlrpclib import dumps, loads
|
||||
|
||||
KB = 1024
|
||||
MB = 1024 * 1024
|
||||
|
||||
class XendVDI:
|
||||
"""Generic Xen API compatible VDI representation.
|
||||
|
||||
@cvar SAVED_CFG: list of configuration attributes to save.
|
||||
@cvar SAVED_CFG_INT: list of configurations that should be ints.
|
||||
"""
|
||||
|
||||
SAVED_CFG = ['name_label',
|
||||
'name_description',
|
||||
'sector_size',
|
||||
'virtual_size',
|
||||
'physical_utilisation',
|
||||
'parent',
|
||||
'children',
|
||||
'sharable',
|
||||
'read_only']
|
||||
|
||||
SAVED_CFG_INT = ['sector_size', 'virtual_size', 'physical_utilisation']
|
||||
|
||||
def __init__(self, uuid, sr_uuid):
|
||||
self.uuid = uuid
|
||||
self.sr_uuid = sr_uuid
|
||||
self.name_label = ""
|
||||
self.name_description = ""
|
||||
self.sector_size = 1024
|
||||
self.virtual_size = 0
|
||||
self.physical_utilisation = 0
|
||||
self.parent = None
|
||||
self.children = []
|
||||
self.sharable = False
|
||||
self.read_only = False
|
||||
self.type = "system"
|
||||
|
||||
self.cfg_path = None
|
||||
|
||||
def load_config_dict(self, cfg):
|
||||
"""Loads configuration into the object from a dict.
|
||||
|
||||
@param cfg: configuration dict
|
||||
@type cfg: dict
|
||||
"""
|
||||
for key in self.SAVED_CFG:
|
||||
if key in cfg:
|
||||
if key in self.SAVED_CFG_INT:
|
||||
setattr(self, key, int(cfg[key]))
|
||||
else:
|
||||
setattr(self, key, cfg[key])
|
||||
|
||||
def load_config(self, cfg_path):
|
||||
"""Loads configuration from an XMLRPC parameter format.
|
||||
|
||||
@param cfg_path: configuration file path
|
||||
@type cfg_path: type
|
||||
@rtype: bool
|
||||
@return: Successful or not.
|
||||
"""
|
||||
try:
|
||||
cfg, _ = loads(open(cfg_path).read())
|
||||
cfg = cfg[0]
|
||||
self.load_config_dict(cfg)
|
||||
self.cfg_path = cfg_path
|
||||
except IOError, e:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def save_config(self, cfg_path = None):
|
||||
"""Saves configuration at give path in XMLRPC parameter format.
|
||||
|
||||
If cfg_path is not give, it defaults to the where the VDI
|
||||
configuration as loaded if it load_config was called.
|
||||
|
||||
@keyword cfg_path: optional configuration file path
|
||||
@rtype: bool
|
||||
@return: Successful or not.
|
||||
"""
|
||||
try:
|
||||
if not cfg_path and not self.cfg_path:
|
||||
return False
|
||||
|
||||
if not cfg_path:
|
||||
cfg_path = self.cfg_path
|
||||
|
||||
cfg = {}
|
||||
for key in self.SAVED_CFG:
|
||||
try:
|
||||
cfg[key] = getattr(self, key)
|
||||
except AttributeError:
|
||||
pass
|
||||
open(cfg_path, 'w').write(dumps((stringify(cfg),),
|
||||
allow_none = True))
|
||||
except IOError, e:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
class XendQCOWVDI(XendVDI):
|
||||
vdi_type = "system"
|
||||
|
||||
def __init__(self, uuid, sr_uuid, qcow_path, image_path, vsize, psize):
|
||||
def __init__(self, uuid, sr_uuid, qcow_path, image_path, cfg_path,
|
||||
vsize, psize):
|
||||
XendVDI.__init__(self, uuid, sr_uuid)
|
||||
self.qcow_path = qcow_path
|
||||
self.image_path = image_path
|
||||
self.vsize = vsize
|
||||
self.psize = psize
|
||||
self.cfg_path = cfg_path
|
||||
self.physical_utilisation = psize
|
||||
self.virtual_size = vsize
|
||||
self.sector_size = 1
|
||||
|
||||
def get_physical_utilisation(self):
|
||||
return self.psize * KB
|
||||
|
||||
def get_virtual_size(self):
|
||||
return self.vsize * KB
|
||||