[XEND] Add support for multiple storage repositories.

Splits XendStorageRepository into a helper class and
XendQCowStorageRepo as the QCoW File backed repository. This gives us
the opportunity to introduce a pseudo storage repository to represent
the old way of specifying block devices. This is the
XendLocalStorageRepo.

Note that this still introduces a non-Xen API 'uri' attribute to VDIs
in order to support querying VDIs for their actual location relative
to the Xen host.

The QCoW backed repository is now named QCoW, and the pseudo-SR is
named Local.

This removes the 'image' attribute backwards-compat hack in VBDs in
the Xen API.

Signed-off-by: Alastair Tse <atse@xensource.com>
This commit is contained in:
Alastair Tse committed 2007-01-24 15:47:31 +00:00
1 parent cd402c7539
commit d3cc627bf9
9 files changed
+675 -429

No files matched your search

+53 -60
View File
@@ -214,7 +214,7 @@ def valid_vdi(func):
@rtype: callable object
"""
return lambda *args, **kwargs: \
_check_ref(XendNode.instance().get_sr().is_valid_vdi,
_check_ref(XendNode.instance().is_valid_vdi,
'VDI_HANDLE_INVALID', func, *args, **kwargs)
def valid_vtpm(func):
@@ -234,7 +234,7 @@ def valid_sr(func):
@rtype: callable object
"""
return lambda *args, **kwargs: \
_check_ref(lambda r: XendNode.instance().get_sr().uuid == r,
_check_ref(lambda r: XendNode.instance().is_valid_sr,
'SR_HANDLE_INVALID', func, *args, **kwargs)
def valid_pif(func):
@@ -1203,8 +1203,7 @@ class XendAPI:
# Xen API: Class VBD
# ----------------------------------------------------------------
VBD_attr_ro = ['image',
'io_read_kbs',
VBD_attr_ro = ['io_read_kbs',
'io_write_kbs']
VBD_attr_rw = ['VM',
'VDI',
@@ -1213,7 +1212,7 @@ class XendAPI:
'type',
'driver']
VBD_attr_inst = VBD_attr_rw + ['image']
VBD_attr_inst = VBD_attr_rw
VBD_methods = [('media_change', None)]
VBD_funcs = [('create', 'VBD')]
@@ -1250,18 +1249,15 @@ class XendAPI:
dom = xendom.get_vm_by_uuid(vbd_struct['VM'])
vbd_ref = ''
try:
if not 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)
if not vdi_image:
return xen_api_error(['VDI_HANDLE_INVALID', vdi_ref])
vdi_image = vdi_image.qcow_path
vbd_ref = dom.create_vbd_with_vdi(vbd_struct, vdi_image)
# new VBD via VDI/SR
vdi_ref = vbd_struct.get('VDI')
vdi = XendNode.instance().get_vdi_by_uuid(vdi_ref)
if not vdi:
return xen_api_error(['VDI_HANDLE_INVALID', vdi_ref])
vdi_image = vdi.get_image_uri()
vbd_ref = XendTask.log_progress(0, 100,
dom.create_vbd,
vbd_struct, vdi_image)
except XendError:
return xen_api_todo()
@@ -1275,7 +1271,7 @@ class XendAPI:
if not vm:
return xen_api_error(['VBD_HANDLE_INVALID', vbd_ref])
vm.destroy_vbd(vbd_ref)
XendTask.log_progress(0, 100, vm.destroy_vbd, vbd_ref)
return xen_api_success_void()
# attributes (rw)
@@ -1446,7 +1442,7 @@ class XendAPI:
('get_by_name_label', 'Set(VDI)')]
def _get_VDI(self, ref):
return XendNode.instance().get_sr().xen_api_get_by_uuid(ref)
return XendNode.instance().get_vdi_by_uuid(ref)
def VDI_get_VBDs(self, session, vdi_ref):
return xen_api_todo()
@@ -1474,8 +1470,7 @@ class XendAPI:
return xen_api_success(self._get_VDI(vdi_ref).name_description)
def VDI_get_SR(self, session, vdi_ref):
sr = XendNode.instance().get_sr()
return xen_api_success(sr.uuid)
return xen_api_success(self._get_VDI(vdi_ref).sr_uuid)
def VDI_get_virtual_size(self, session, vdi_ref):
return xen_api_success(self._get_VDI(vdi_ref).virtual_size)
@@ -1513,18 +1508,17 @@ class XendAPI:
return xen_api_todo()
def VDI_destroy(self, session, vdi_ref):
sr = XendNode.instance().get_sr()
sr.destroy_image(vdi_ref)
sr = XendNode.instance().get_sr_containing_vdi(vdi_ref)
sr.destroy_vdi(vdi_ref)
return xen_api_success_void()
def VDI_get_record(self, session, vdi_ref):
sr = XendNode.instance().get_sr()
image = sr.xen_api_get_by_uuid(vdi_ref)
image = XendNode.instance().get_vdi_by_uuid(vdi_ref)
return xen_api_success({
'uuid': vdi_ref,
'name_label': image.name_label,
'name_description': image.name_description,
'SR': sr.uuid,
'SR': image.sr_uuid,
'VBDs': [], # TODO
'virtual_size': image.virtual_size,
'physical_utilisation': image.physical_utilisation,
@@ -1538,25 +1532,22 @@ class XendAPI:
# Class Functions
def VDI_create(self, session, vdi_struct):
sr = XendNode.instance().get_sr()
sr_ref = vdi_struct.get('SR')
xennode = XendNode.instance()
if not xennode.is_valid_sr(sr_ref):
return xen_api_error(['SR_HANDLE_INVALID', sr_ref])
if sr.uuid != sr_ref:
return xen_api_error(['SR_HANDLE_INVALID', vdi_struct.get('SR')])
vdi_uuid = sr.create_image(vdi_struct)
vdi_uuid = xennode.srs[sr_ref].create_vdi(vdi_struct)
return xen_api_success(vdi_uuid)
def VDI_get_all(self, session):
sr = XendNode.instance().get_sr()
return xen_api_success(sr.list_images())
xennode = XendNode.instance()
vdis = [sr.get_vdis() for sr in xennode.srs.values()]
return xen_api_success(reduce(lambda x, y: x + y, vdis))
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_success([])
xennode = XendNode.instance()
return xen_api_success(xennode.get_vdi_by_name_label(name))
# Xen API: Class VTPM
@@ -1691,15 +1682,11 @@ class XendAPI:
# Class Functions
def SR_get_all(self, session):
sr = XendNode.instance().get_sr()
return xen_api_success([sr.uuid])
return xen_api_success(XendNode.instance().get_all_sr_uuid())
def SR_get_by_name_label(self, session, label):
sr = XendNode.instance().get_sr()
if sr.name_label != label:
return xen_api_success([])
return xen_api_success([sr.uuid])
return xen_api_success(XendNode.instance().get_sr_by_name(label))
def SR_create(self, session):
return xen_api_error(XEND_ERROR_UNSUPPORTED)
@@ -1711,16 +1698,20 @@ class XendAPI:
return xen_api_error(XEND_ERROR_UNSUPPORTED)
def SR_get_record(self, session, sr_ref):
sr = XendNode.instance().get_sr()
return xen_api_success(sr.get_record())
sr = XendNode.instance().get_sr(sr_ref)
if sr:
return xen_api_success(sr.get_record())
return xen_api_error(['SR_HANDLE_INVALID', sr_ref])
# Attribute acceess
def _get_SR_func(self, _, func):
return xen_api_success(getattr(XendNode.instance().get_sr(), func)())
def _get_SR_func(self, sr_ref, func):
return xen_api_success(getattr(XendNode.instance().get_sr(sr_ref),
func)())
def _get_SR_attr(self, _, attr):
return xen_api_success(getattr(XendNode.instance().get_sr(), attr))
def _get_SR_attr(self, sr_ref, attr):
return xen_api_success(getattr(XendNode.instance().get_sr(sr_ref),
attr))
def SR_get_VDIs(self, _, ref):
return self._get_SR_func(ref, 'list_images')
@@ -1729,10 +1720,10 @@ class XendAPI:
return self._get_SR_func(ref, 'virtual_allocation')
def SR_get_physical_utilisation(self, _, ref):
return self._get_SR_func(ref, 'used_space_bytes')
return self._get_SR_func(ref, 'physical_utilisation')
def SR_get_physical_size(self, _, ref):
return self._get_SR_func(ref, 'total_space_bytes')
return self._get_SR_func(ref, 'physical_size')
def SR_get_type(self, _, ref):
return self._get_SR_attr(ref, 'type')
@@ -1747,15 +1738,17 @@ class XendAPI:
return self._get_SR_attr(ref, 'name_description')
def SR_set_name_label(self, session, sr_ref, value):
sr = XendNode.instance().get_sr()
sr.name_label = value
XendNode.instance().save()
sr = XendNode.instance.get_sr(sr_ref)
if sr:
sr.name_label = value
XendNode.instance().save()
return xen_api_success_void()
def SR_set_name_description(self, session, sr_ref, value):
sr = XendNode.instance().get_sr()
sr.name_description = value
XendNode.instance().save()
sr = XendNode.instance.get_sr(sr_ref)
if sr:
sr.name_description = value
XendNode.instance().save()
return xen_api_success_void()
+8 -6
View File
@@ -994,13 +994,15 @@ class XendConfig(dict):
return dev_uuid
elif dev_type in ('vbd', 'tap'):
if dev_type == 'vbd':
dev_info['uname'] = cfg_xenapi.get('image', '')
dev_info['dev'] = '%s:disk' % cfg_xenapi.get('device')
elif dev_type == 'tap':
dev_info['uname'] = 'tap:qcow:%s' % cfg_xenapi.get('image')
dev_info['dev'] = '%s:disk' % cfg_xenapi.get('device')
dev_info['type'] = cfg_xenapi.get('type', 'Disk')
if dev_info['type'] == 'CD':
old_vbd_type = 'cdrom'
else:
old_vbd_type = 'disk'
dev_info['uname'] = cfg_xenapi.get('image', '')
dev_info['dev'] = '%s:%s' % (cfg_xenapi.get('device'),
old_vbd_type)
dev_info['driver'] = cfg_xenapi.get('driver')
dev_info['VDI'] = cfg_xenapi.get('VDI', '')
+17 -22
View File
@@ -1656,7 +1656,7 @@ class XendDomainInfo:
from xen.xend import XendDomain
dom0 = XendDomain.instance().privilegedDomain()
dom0._waitForDeviceUUID(dom0.create_vbd_with_vdi(vbd, fn))
dom0._waitForDeviceUUID(dom0.create_vbd(vbd, fn))
fn = BOOTLOADER_LOOPBACK_DEVICE
try:
@@ -2133,24 +2133,7 @@ class XendDomainInfo:
def get_vtpms(self):
return self.info.get('vtpm_refs', [])
def create_vbd(self, xenapi_vbd):
"""Create a VBD device from the passed struct in Xen API format.
@return: uuid of the device
@rtype: string
"""
dev_uuid = self.info.device_add('vbd', cfg_xenapi = xenapi_vbd)
if not dev_uuid:
raise XendError('Failed to create device')
if self.state == XEN_API_VM_POWER_STATE_RUNNING:
_, config = self.info['devices'][dev_uuid]
config['devid'] = self.getDeviceController('vbd').createDevice(config)
return dev_uuid
def create_vbd_with_vdi(self, xenapi_vbd, vdi_image_path):
def create_vbd(self, xenapi_vbd, vdi_image_path):
"""Create a VBD using a VDI from XendStorageRepository.
@param xenapi_vbd: vbd struct from the Xen API
@@ -2159,14 +2142,26 @@ class XendDomainInfo:
@return: uuid of the device
"""
xenapi_vbd['image'] = vdi_image_path
log.debug('create_vbd_with_vdi: %s' % xenapi_vbd)
dev_uuid = self.info.device_add('tap', cfg_xenapi = xenapi_vbd)
log.debug('create_vbd: %s' % xenapi_vbd)
dev_uuid = ''
if vdi_image_path.startswith('tap'):
dev_uuid = self.info.device_add('tap', cfg_xenapi = xenapi_vbd)
else:
dev_uuid = self.info.device_add('vbd', cfg_xenapi = xenapi_vbd)
if not dev_uuid:
raise XendError('Failed to create device')
if self.state == XEN_API_VM_POWER_STATE_RUNNING:
_, config = self.info['devices'][dev_uuid]
config['devid'] = self.getDeviceController('tap').createDevice(config)
dev_control = None
if vdi_image_path.startswith('tap'):
dev_control = self.getDeviceController('tap')
else:
dev_control = self.getDeviceController('vbd')
config['devid'] = dev_control.createDevice(config)
return dev_uuid
@@ -0,0 +1,94 @@
#!/usr/bin/python
#============================================================================
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#============================================================================
# Copyright (C) 2007 XenSource Ltd.
#============================================================================
#
# A pseudo-StorageRepository to provide a representation for the images
# that can be specified by xm.
#
import commands
import logging
import os
import stat
import threading
import re
import sys
import struct
from xen.util import mkdir
from xen.xend import uuid
from xen.xend.XendError import XendError
from xen.xend.XendVDI import *
from xen.xend.XendTask import XendTask
from xen.xend.XendStorageRepository import XendStorageRepository
from xen.xend.XendStateStore import XendStateStore
from xen.xend.XendOptions import instance as xendoptions
MB = 1024 * 1024
log = logging.getLogger("xend.XendLocalStorageRepo")
class XendLocalStorageRepo(XendStorageRepository):
"""A backwards compatibility storage repository so that
traditional file:/dir/file.img and phy:/dev/hdxx images can
still be represented in terms of the Xen API.
"""
def __init__(self, sr_uuid, sr_type = 'local',
name_label = 'Local',
name_description = 'Traditional Local Storage Repo'):
"""
@ivar images: mapping of all the images.
@type images: dictionary by image uuid.
@ivar lock: lock to provide thread safety.
"""
XendStorageRepository.__init__(self, sr_uuid, sr_type,
name_label, name_description,
'/')
self.state = XendStateStore(xendoptions().get_xend_state_path()
+ '/local_sr')
stored_images = self.state.load_state('vdi')
if stored_images:
for image_uuid, image in stored_images.items():
self.images[image_uuid] = XendLocalVDI(image)
def create_vdi(self, vdi_struct):
""" Creates a fake VDI image for a traditional image string.
The image uri is stored in the attribute 'uri'
"""
vdi_struct['uuid'] = uuid.createString()
vdi_struct['SR'] = self.uuid
new_image = XendLocalVDI(vdi_struct)
self.images[new_image.uuid] = new_image
self.save_state()
return new_image.uuid
def save_state(self):
vdi_records = dict([(k, v.get_record(transient = False))
for k, v in self.images.items()])
self.state.save_state('vdi', vdi_records)
def destroy_vdi(self, vdi_uuid):
if vdi_uuid in self.images:
del self.images[vdi_uuid]
self.save_state()
+68 -16
View File
@@ -25,7 +25,8 @@ from xen.util import Brctl
from xen.xend import uuid
from xen.xend.XendError import XendError, NetworkAlreadyConnected
from xen.xend.XendOptions import instance as xendoptions
from xen.xend.XendStorageRepository import XendStorageRepository
from xen.xend.XendQCoWStorageRepo import XendQCoWStorageRepo
from xen.xend.XendLocalStorageRepo import XendLocalStorageRepo
from xen.xend.XendLogging import log
from xen.xend.XendPIF import *
from xen.xend.XendNetwork import *
@@ -88,7 +89,8 @@ class XendNode:
self.pifs = {}
self.networks = {}
self.srs = {}
# initialise networks
saved_networks = self.state_store.load_state('network')
if saved_networks:
@@ -121,13 +123,23 @@ class XendNode:
self.PIF_create(name, mtu, '', mac, network, False)
# initialise storage
saved_sr = self.state_store.load_state('sr')
if saved_sr and len(saved_sr) == 1:
sr_uuid = saved_sr.keys()[0]
self.sr = XendStorageRepository(sr_uuid)
else:
sr_uuid = uuid.createString()
self.sr = XendStorageRepository(sr_uuid)
saved_srs = self.state_store.load_state('sr')
if saved_srs:
for sr_uuid, sr_cfg in saved_srs.items():
if sr_cfg['type'] == 'qcow_file':
self.srs[sr_uuid] = XendQCoWStorageRepo(sr_uuid)
elif sr_cfg['type'] == 'local_image':
self.srs[sr_uuid] = XendLocalStorageRepo(sr_uuid)
# Create missing SRs if they don't exist
if not self.get_sr_by_type('local_image'):
image_sr_uuid = uuid.createString()
self.srs[image_sr_uuid] = XendLocalStorageRepo(image_sr_uuid)
if not self.get_sr_by_type('qcow_file'):
qcow_sr_uuid = uuid.createString()
self.srs[qcow_sr_uuid] = XendQCowStorageRepo(qcow_sr_uuid)
def network_create(self, name_label, name_description,
@@ -184,9 +196,7 @@ class XendNode:
self.state_store.save_state('cpu', self.cpus)
self.save_PIFs()
self.save_networks()
sr_record = {self.sr.uuid: self.sr.get_record()}
self.state_store.save_state('sr', sr_record)
self.save_SRs()
def save_PIFs(self):
pif_records = dict([(k, v.get_record(transient = False))
@@ -198,6 +208,11 @@ class XendNode:
for k, v in self.networks.items()])
self.state_store.save_state('network', net_records)
def save_SRs(self):
sr_records = dict([(k, v.get_record(transient = False))
for k, v in self.srs.items()])
self.state_store.save_state('sr', sr_records)
def shutdown(self):
return 0
@@ -206,7 +221,6 @@ class XendNode:
def notify(self, _):
return 0
#
# Ref validation
@@ -221,12 +235,50 @@ class XendNode:
def is_valid_network(self, network_ref):
return (network_ref in self.networks)
def is_valid_sr(self, sr_ref):
return (sr_ref in self.srs)
def is_valid_vdi(self, vdi_ref):
for sr in self.srs.values():
if sr.is_valid_vdi(vdi_ref):
return True
return False
#
# Storage Repo
# Storage Repositories
#
def get_sr(self):
return self.sr
def get_sr(self, sr_uuid):
return self.srs.get(sr_uuid)
def get_sr_by_type(self, sr_type):
return [sr.uuid for sr in self.srs.values() if sr.type == sr_type]
def get_sr_by_name(self, name):
return [sr.uuid for sr in self.srs.values() if sr.name_label == name]
def get_all_sr_uuid(self):
return self.srs.keys()
def get_vdi_by_uuid(self, vdi_uuid):
for sr in self.srs.values():
if sr.is_valid_vdi(vdi_uuid):
return sr.get_vdi_by_uuid(vdi_uuid)
return None
def get_vdi_by_name_label(self, name):
for sr in self.srs.values():
vdi = sr.get_vdi_by_name_label(name)
if vdi:
return vdi
return None
def get_sr_containing_vdi(self, vdi_uuid):
for sr in self.srs.values():
if sr.is_valid_vdi(vdi_uuid):
return sr
return None
#
# Host Functions
+9 -1
View File
@@ -104,6 +104,9 @@ class XendOptions:
"""Default xend management state storage."""
xend_state_path_default = '/var/lib/xend/state'
"""Default xend QCoW storage repository location."""
xend_storage_path_default = '/var/lib/xend/storage'
"""Default type of backend network interfaces"""
netback_type = osdep.netback_type
@@ -211,7 +214,12 @@ class XendOptions:
def get_xend_state_path(self):
""" Get the path for persistent domain configuration storage
"""
return self.get_config_string("xend-state-path", self.xend_state_path_default)
return self.get_config_string("xend-state-path", self.xend_state_path_default)
def get_xend_storage_path(self):
""" Get the path for persistent domain configuration storage
"""
return self.get_config_string("xend-storage-path", self.xend_storage_path_default)
def get_network_script(self):
"""@return the script used to alter the network configuration when
@@ -0,0 +1,366 @@
#!/usr/bin/python
#============================================================================
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#============================================================================
# Copyright (C) 2006,2007 XenSource Ltd.
#============================================================================
#
# The default QCOW Xen API Storage Repository
#
import commands
import logging
import os
import stat
import threading
import re
import sys
import struct
from xen.util import mkdir
from xen.xend import uuid
from xen.xend.XendError import XendError
from xen.xend.XendVDI import *
from xen.xend.XendTask import XendTask
from xen.xend.XendStorageRepository import XendStorageRepository
from xen.xend.XendOptions import instance as xendoptions
XEND_STORAGE_NO_MAXIMUM = sys.maxint
XEND_STORAGE_QCOW_FILENAME = "%s.qcow"
XEND_STORAGE_VDICFG_FILENAME = "%s.vdi.xml"
QCOW_CREATE_COMMAND = "/usr/sbin/qcow-create -r %d %s"
MB = 1024 * 1024
log = logging.getLogger("xend.XendQCowStorageRepo")
def qcow_virtual_size(qcow_file):
"""Read the first 32 bytes of the QCoW header to determine its size.
See: http://www.gnome.org/~markmc/qcow-image-format.html.
"""
try:
qcow_header = open(qcow_file, 'rb').read(32)
parts = struct.unpack('>IIQIIQ', qcow_header)
return parts[-1]
except IOError:
return -1
class XendQCoWStorageRepo(XendStorageRepository):
"""A simple file backed QCOW Storage Repository.
This class exposes the interface to create VDI's via the
Xen API. The backend is a file-backed QCOW format that is stored
in XEND_STORAGE_DIR or any that is specified in the constructor.
The actual images are created in the format <uuid>.img and <uuid>.qcow.
"""
def __init__(self, sr_uuid,
sr_type = "qcow_file",
name_label = "QCoW",
name_description = "Xend QCoW Storage Repository",
location = xendoptions().get_xend_storage_path(),
storage_max = XEND_STORAGE_NO_MAXIMUM):
"""
@keyword storage_max: Maximum disk space to use in bytes.
@type storage_max: int
@ivar storage_free: storage space free for this repository
@ivar images: mapping of all the images.
@type images: dictionary by image uuid.
@ivar lock: lock to provide thread safety.
"""
XendStorageRepository.__init__(self, sr_uuid, sr_type, name_label,
name_description, location,
storage_max)
self.storage_free = 0
self._refresh()
def get_record(self, transient = True):
retval = {'uuid': self.uuid,
'name_label': self.name_label,
'name_description': self.name_description,
'virtual_allocation': self.virtual_allocation,
'physical_utilisation': self.physical_utilisation,
'physical_size': self.physical_size,
'type': self.type,
'location': self.location,
'VDIs': self.images.keys()}
if self.physical_size == XEND_STORAGE_NO_MAXIMUM:
stfs = os.statvfs(self.location)
retval['physical_size'] = stfs.f_blocks * stfs.f_frsize
return retval
def _refresh(self):
"""Internal function that refreshes the state of the disk and
updates the list of images available.
"""
self.lock.acquire()
try:
mkdir.parents(self.location, stat.S_IRWXU)
# scan the directory and populate self.images
virtual_alloc = 0
physical_used = 0
seen_images = []
for filename in os.listdir(self.location):
if filename[-5:] == XEND_STORAGE_QCOW_FILENAME[-5:]:
image_uuid = filename[:-5]
seen_images.append(image_uuid)
qcow_file = XEND_STORAGE_QCOW_FILENAME % image_uuid
cfg_file = XEND_STORAGE_VDICFG_FILENAME % image_uuid
qcow_path = os.path.join(self.location, qcow_file)
cfg_path = os.path.join(self.location, cfg_file)
phys_size = os.stat(qcow_path).st_size
virt_size = qcow_virtual_size(qcow_path)
# add this image if we haven't seen it before
if image_uuid not in self.images:
vdi = XendQCoWVDI(image_uuid, self.uuid,
qcow_path, cfg_path,
virt_size, phys_size)
if cfg_path and os.path.exists(cfg_path):
try:
vdi.load_config(cfg_path)
except:
log.error('Corrupt VDI configuration file %s' %
cfg_path)
self.images[image_uuid] = vdi
physical_used += phys_size
virtual_alloc += virt_size
# remove images that aren't valid
for image_uuid in self.images.keys():
if image_uuid not in seen_images:
try:
os.unlink(self.images[image_uuid].qcow_path)
except OSError:
pass
del self.images[image_uuid]
self.virtual_allocation = virtual_alloc
self.physical_utilisation = physical_used
# update free storage if we have to track that
if self.physical_size == XEND_STORAGE_NO_MAXIMUM:
self.storage_free = self._get_free_space()
else:
self.storage_free = self.physical_size - self.virtual_allocation
finally:
self.lock.release()
def _get_free_space(self):
"""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.
@rtype: int
"""
stfs = os.statvfs(self.location)
return stfs.f_bavail * stfs.f_frsize
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.
@rtype: bool
"""
if self.physical_size != XEND_STORAGE_NO_MAXIMUM:
return self.storage_free > size_bytes
bytes_free = self._get_free_space()
if size_bytes < bytes_free:
return True
return False
def _create_image_files(self, desired_size_bytes):
"""Create an image and return its assigned UUID.
@param desired_size_bytes: Desired image size in bytes
@type desired_size_bytes: int
@rtype: string
@return: uuid
@raises XendError: If an error occurs.
"""
self.lock.acquire()
try:
if not self._has_space_available_for(desired_size_bytes):
raise XendError("Not enough space")
image_uuid = uuid.createString()
qcow_path = os.path.join(self.location,
XEND_STORAGE_QCOW_FILENAME % image_uuid)
if qcow_path and os.path.exists(qcow_path):
raise XendError("Image with same UUID alreaady exists:" %
image_uuid)
cmd = QCOW_CREATE_COMMAND % (desired_size_bytes/MB, qcow_path)
rc, output = commands.getstatusoutput(cmd)
if rc != 0:
# cleanup the image file
os.unlink(qcow_path)
raise XendError("Failed to create QCOW Image: %s" % output)
self._refresh()
return image_uuid
finally:
self.lock.release()
def destroy_vdi(self, image_uuid):
"""Destroy an image that is managed by this storage repository.
@param image_uuid: Image UUID
@type image_uuid: String
@rtype: String
"""
self.lock.acquire()
try:
if image_uuid in self.images:
# TODO: check if it is being used?
qcow_path = self.images[image_uuid].qcow_path
cfg_path = self.images[image_uuid].cfg_path
try:
os.unlink(qcow_path)
if cfg_path and os.path.exists(cfg_path):
os.unlink(cfg_path)
except OSError:
log.exception("Failed to destroy image")
del self.images[image_uuid]
return True
finally:
self.lock.release()
return False
def list_images(self):
""" List all the available images by UUID.
@rtype: list of strings.
@return: list of UUIDs
"""
self.lock.acquire()
try:
return self.images.keys()
finally:
self.lock.release()
def free_space_bytes(self):
"""Returns the amount of available space in KB.
@rtype: int
"""
self.lock.acquire()
try:
return self.storage_free
finally:
self.lock.release()
def total_space_bytes(self):
"""Returns the total usable space of the storage repo in KB.
@rtype: int
"""
self.lock.acquire()
try:
if self.physical_size == XEND_STORAGE_NO_MAXIMUM:
stfs = os.statvfs(self.location)
return stfs.f_blocks * stfs.f_frsize
else:
return self.physical_size
finally:
self.lock.release()
def used_space_bytes(self):
"""Returns the total amount of space used by this storage repository.
@rtype: int
"""
self.lock.acquire()
try:
return self.physical_utilisation
finally:
self.lock.release()
def virtual_allocation(self):
"""Returns the total virtual space allocated within the storage repo.
@rtype: int
"""
self.lock.acquire()
try:
return self.virtual_allocation
finally:
self.lock.release()
def create_vdi(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(self.location, cfg_filename)
image.save_config(cfg_path)
except Exception, e:
# cleanup before raising exception
if image_uuid:
self.destroy_vdi(image_uuid)
raise
return image_uuid
# remove everything below this line!! for testing only
if __name__ == "__main__":
xsr = XendStorageRepository()
print 'Free Space: %d MB' % (xsr.free_space_bytes()/MB)
print "Create Image:",
print xsr._create_image_files(10 * MB)
print 'Delete all images:'
for image_uuid in xsr.list_images():
print image_uuid,
xsr._destroy_image_files(image_uuid)
print
+31 -320
View File
@@ -13,65 +13,28 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#============================================================================
# Copyright (C) 2006 XenSource Ltd.
# Copyright (C) 2006, 2007 XenSource Ltd.
#============================================================================
#
# The default QCOW Xen API Storage Repository
# Abstract class for XendStorageRepositories
#
import commands
import logging
import os
import stat
import threading
import re
import sys
import struct
from xen.util import mkdir
from xen.xend import uuid
from xen.xend.XendError import XendError
from xen.xend.XendVDI import *
XEND_STORAGE_NO_MAXIMUM = sys.maxint
XEND_STORAGE_DIR = "/var/lib/xend/storage/"
XEND_STORAGE_QCOW_FILENAME = "%s.qcow"
XEND_STORAGE_VDICFG_FILENAME = "%s.vdi.xml"
QCOW_CREATE_COMMAND = "/usr/sbin/qcow-create -r %d %s"
MB = 1024 * 1024
log = logging.getLogger("xend.XendStorageRepository")
def qcow_virtual_size(qcow_file):
"""Read the first 32 bytes of the QCoW header to determine its size.
See: http://www.gnome.org/~markmc/qcow-image-format.html.
"""
try:
qcow_header = open(qcow_file, 'rb').read(32)
parts = struct.unpack('>IIQIIQ', qcow_header)
return parts[-1]
except IOError:
return -1
class XendStorageRepository:
"""A simple file backed QCOW Storage Repository.
""" Base class for Storage Repos. """
This class exposes the interface to create VDI's via the
Xen API. The backend is a file-backed QCOW format that is stored
in XEND_STORAGE_DIR or any that is specified in the constructor.
The actual images are created in the format <uuid>.img and <uuid>.qcow.
"""
def __init__(self, uuid,
sr_type = "qcow_file",
name_label = "Local",
name_description = "Xend Storage Repository",
location = XEND_STORAGE_DIR,
sr_type = "unknown",
name_label = 'Unknown',
name_description = 'Not Implemented',
location = '',
storage_max = XEND_STORAGE_NO_MAXIMUM):
"""
@keyword storage_max: Maximum disk space to use in bytes.
@@ -91,303 +54,51 @@ class XendStorageRepository:
self.name_description = name_description
self.images = {}
self.storage_max = storage_max
self.storage_free = 0
self.storage_used = 0
self.storage_alloc = 0
self.physical_size = storage_max
self.physical_utilisation = 0
self.virtual_allocation = 0
self.lock = threading.RLock()
self._refresh()
def get_record(self):
def get_record(self, transient = True):
retval = {'uuid': self.uuid,
'name_label': self.name_label,
'name_description': self.name_description,
'virtual_allocation': self.storage_alloc,
'physical_utilisation': self.storage_used,
'physical_size': self.storage_max,
'virtual_allocation': self.virtual_allocation,
'physical_utilisation': self.physical_utilisation,
'physical_size': self.physical_size,
'type': self.type,
'location': self.location,
'VDIs': self.images.keys()}
if self.storage_max == XEND_STORAGE_NO_MAXIMUM:
stfs = os.statvfs(self.location)
retval['physical_size'] = stfs.f_blocks * stfs.f_frsize
return retval
def _refresh(self):
"""Internal function that refreshes the state of the disk and
updates the list of images available.
"""
self.lock.acquire()
try:
mkdir.parents(self.location, stat.S_IRWXU)
# scan the directory and populate self.images
virtual_alloc = 0
physical_used = 0
seen_images = []
for filename in os.listdir(self.location):
if filename[-5:] == XEND_STORAGE_QCOW_FILENAME[-5:]:
image_uuid = filename[:-5]
seen_images.append(image_uuid)
qcow_file = XEND_STORAGE_QCOW_FILENAME % image_uuid
cfg_file = XEND_STORAGE_VDICFG_FILENAME % image_uuid
qcow_path = os.path.join(self.location, qcow_file)
cfg_path = os.path.join(self.location, cfg_file)
phys_size = os.stat(qcow_path).st_size
virt_size = qcow_virtual_size(qcow_path)
# add this image if we haven't seen it before
if image_uuid not in self.images:
vdi = XendQCOWVDI(image_uuid, self.uuid,
qcow_path, cfg_path,
virt_size, phys_size)
if cfg_path and os.path.exists(cfg_path):
try:
vdi.load_config(cfg_path)
except:
log.error('Corrupt VDI configuration file %s' %
cfg_path)
self.images[image_uuid] = vdi
physical_used += phys_size
virtual_alloc += virt_size
# remove images that aren't valid
for image_uuid in self.images.keys():
if image_uuid not in seen_images:
try:
os.unlink(self.images[image_uuid].qcow_path)
except OSError:
pass
del self.images[image_uuid]
self.storage_alloc = virtual_alloc
self.storage_used = physical_used
# update free storage if we have to track that
if self.storage_max == XEND_STORAGE_NO_MAXIMUM:
self.storage_free = self._get_free_space()
else:
self.storage_free = self.storage_max - self.storage_alloc
finally:
self.lock.release()
def _get_free_space(self):
"""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.
@rtype: int
"""
stfs = os.statvfs(self.location)
return stfs.f_bavail * stfs.f_frsize
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.
@rtype: bool
"""
if self.storage_max != XEND_STORAGE_NO_MAXIMUM:
return self.storage_free > size_bytes
bytes_free = self._get_free_space()
if size_bytes < bytes_free:
return True
return False
def _create_image_files(self, desired_size_bytes):
"""Create an image and return its assigned UUID.
@param desired_size_bytes: Desired image size in bytes
@type desired_size_bytes: int
@rtype: string
@return: uuid
@raises XendError: If an error occurs.
"""
self.lock.acquire()
try:
if not self._has_space_available_for(desired_size_bytes):
raise XendError("Not enough space")
image_uuid = uuid.createString()
qcow_path = os.path.join(self.location,
XEND_STORAGE_QCOW_FILENAME % image_uuid)
if qcow_path and os.path.exists(qcow_path):
raise XendError("Image with same UUID alreaady exists:" %
image_uuid)
cmd = QCOW_CREATE_COMMAND % (desired_size_bytes/MB, qcow_path)
rc, output = commands.getstatusoutput(cmd)
if rc != 0:
# cleanup the image file
os.unlink(qcow_path)
raise XendError("Failed to create QCOW Image: %s" % output)
self._refresh()
return image_uuid
finally:
self.lock.release()
def destroy_image(self, image_uuid):
"""Destroy an image that is managed by this storage repository.
@param image_uuid: Image UUID
@type image_uuid: String
@rtype: String
"""
self.lock.acquire()
try:
if image_uuid in self.images:
# TODO: check if it is being used?
qcow_path = self.images[image_uuid].qcow_path
cfg_path = self.images[image_uuid].cfg_path
try:
os.unlink(qcow_path)
if cfg_path and os.path.exists(cfg_path):
os.unlink(cfg_path)
except OSError:
log.exception("Failed to destroy image")
del self.images[image_uuid]
return True
finally:
self.lock.release()
return False
def list_images(self):
""" List all the available images by UUID.
@rtype: list of strings.
@return: list of UUIDs
"""
self.lock.acquire()
try:
return self.images.keys()
finally:
self.lock.release()
def free_space_bytes(self):
"""Returns the amount of available space in KB.
@rtype: int
"""
self.lock.acquire()
try:
return self.storage_free
finally:
self.lock.release()
def total_space_bytes(self):
"""Returns the total usable space of the storage repo in KB.
@rtype: int
"""
self.lock.acquire()
try:
if self.storage_max == XEND_STORAGE_NO_MAXIMUM:
stfs = os.statvfs(self.location)
return stfs.f_blocks * stfs.f_frsize
else:
return self.storage_max
finally:
self.lock.release()
def used_space_bytes(self):
"""Returns the total amount of space used by this storage repository.
@rtype: int
"""
self.lock.acquire()
try:
return self.storage_used
finally:
self.lock.release()
def virtual_allocation(self):
"""Returns the total virtual space allocated within the storage repo.
@rtype: int
"""
self.lock.acquire()
try:
return self.storage_alloc
finally:
self.lock.release()
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(self.location, 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_name_label(self, label):
def get_vdi_by_uuid(self, image_uuid):
self.lock.acquire()
try:
for image_uuid, val in self.images.items():
if val.name_label == label:
return self.images.get(image_uuid)
finally:
self.lock.release()
def get_vdi_by_name_label(self, label):
self.lock.acquire()
try:
for image_uuid, image in self.images.items():
if image.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()
def get_vdis(self):
return self.images.keys()
# remove everything below this line!!
if __name__ == "__main__":
xsr = XendStorageRepository()
print 'Free Space: %d MB' % (xsr.free_space_bytes()/MB)
print "Create Image:",
print xsr._create_image_files(10 * MB)
print 'Delete all images:'
for image_uuid in xsr.list_images():
print image_uuid,
xsr._destroy_image_files(image_uuid)
def create_vdi(self, vdi_struct):
raise NotImplementedError()
print
def destroy_vdi(self, vdi_struct):
raise NotImplementedError()
+29 -4
View File
@@ -141,7 +141,7 @@ class XendVDI(AutoSaveObject):
return True
def get_record(self):
def get_record(self, transient = True):
return {'uuid': self.uuid,
'name_label': self.name_label,
'name_description': self.name_description,
@@ -152,12 +152,14 @@ class XendVDI(AutoSaveObject):
'children': [],
'sharable': False,
'readonly': False,
'SR': self.sr.get_uuid(),
'SR': self.sr_uuid,
'VBDs': []}
def get_image_uri(self):
raise NotImplementedError()
class XendQCOWVDI(XendVDI):
class XendQCoWVDI(XendVDI):
def __init__(self, uuid, sr_uuid, qcow_path, cfg_path, vsize, psize):
XendVDI.__init__(self, uuid, sr_uuid)
self.auto_save = False
@@ -168,3 +170,26 @@ class XendQCOWVDI(XendVDI):
self.sector_size = 512
self.auto_save = True
def get_image_uri(self):
return 'tap:qcow:%s' % self.qcow_path
class XendLocalVDI(XendVDI):
def __init__(self, vdi_struct):
vdi_uuid = vdi_struct['uuid']
sr_uuid = vdi_struct['SR']
XendVDI.__init__(self, vdi_uuid, sr_uuid)
self.auto_save = False
self.cfg_path = None
self.name_label = vdi_struct.get('name_label','')
self.name_description = vdi_struct.get('name_description', '')
self.physical_utilisation = 0
self.virtual_size = 0
self.sector_size = 0
self.type = vdi_struct.get('type', '')
self.sharable = vdi_struct.get('sharable', False)
self.read_only = vdi_struct.get('read_only', False)
self.image_uri = vdi_struct.get('uri', 'file:/dev/null')
def get_image_uri(self):
return self.image_uri
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.