[XEND] XendConfig is an extended python dictionary that is used to exchange

VM configuration information to allow easy import and export to different
file types.

Signed-off-by: Alastair Tse <atse@xensource.com>
This commit is contained in:
Alastair Tse committed 2006-10-05 17:29:19 +01:00
1 parent 00a4566470
commit 9caccb8f48
1 file changed
+819
+819
View File
@@ -0,0 +1,819 @@
#===========================================================================
# 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 XenSource Ltd
#============================================================================
import re
import time
from xen.xend import sxp
from xen.xend import uuid
from xen.xend.XendError import VmError
from xen.xend.XendDevices import XendDevices
from xen.xend.XendLogging import log
from xen.xend.PrettyPrint import prettyprintstring
"""
XendConfig API
XendConfig will try to mirror as closely the Xen API VM Struct
providing a backwards compatibility mode for SXP dumping, loading.
XendConfig is a subclass of the python dict in order to emulate the
previous behaviour of the XendDomainInfo.info dictionary. However,
the new dictionary also exposes a set of attributes that implement
the Xen API VM configuration interface.
Example:
>>> cfg = XendConfig(cfg = dict_from_xc_domain_getinfo)
>>> cfg.name_label
Domain-0
>>> cfg['name']
Domain-0
>>> cfg.kernel_kernel
/boot/vmlinuz-xen
>>> cfg.kernel_initrd
/root/initrd
>>> cfg.kernel_args
root=/dev/sda1 ro
>>> cfg['image']
(linux
(kernel /boot/vmlinuz-xen)
(ramdisk /root/initrd)
(root '/dev/sda1 ro'))
>>>
Internally, XendConfig will make sure changes via the old 'dict'
interface get reflected, if possible, to the attribute store.
It does this by overriding __setitem__, __getitem__, __hasitem__,
__getattr__, __setattr__, __hasattr__.
What this means is that as code is moved from the SXP interface to
the Xen API interface, we can spot unported code by tracing calls
to __getitem__ and __setitem__.
"""
LEGACY_CFG_TO_XENAPI_CFG = {
'uuid': 'uuid',
'vcpus': 'vcpus_number',
'maxmem': 'memory_static_max',
'memory': 'memory_static_min',
'name': 'name_label',
'on_poweroff': 'actions_after_shutdown',
'on_reboot': 'actions_after_reboot',
'on_crash': 'actions_after_crash',
'bootloader': 'boot_method',
'kernel_kernel': 'kernel_kernel',
'kernel_initrd': 'kernel_initrd',
'kernel_args': 'kernel_args',
}
XENAPI_CFG_CUSTOM_TRANSLATE = [
'vifs',
'vbds',
]
XENAPI_UNSUPPORTED_IN_LEGACY_CFG = [
'name_description',
'user_version',
'is_a_template',
'memory_dynamic_min',
'memory_dynamic_max',
'memory_actual',
'vcpus_policy',
'vcpus_params',
'vcpus_features_required',
'vcpus_features_can_use',
'vcpus_features_force_on',
'vcpus_features_force_off',
'actions_after_suspend',
'tpm_instance',
'tpm_backends',
'bios_boot',
'platform_std_vga',
'platform_serial',
'platform_localtime',
'platform_clock_offset',
'platform_enable_audio',
'builder',
'grub_cmdline',
'pci_bus',
'otherconfig'
]
##
## Xend Configuration Parameters
##
# All parameters of VMs that may be configured on-the-fly, or at start-up.
VM_CONFIG_ENTRIES = [
('autostart', int),
('autostop', int),
('name', str),
('on_crash', str),
('on_poweroff', str),
('on_reboot', str),
('on_xend_stop', str),
]
# All entries written to the store. This is VM_CONFIG_ENTRIES, plus those
# entries written to the store that cannot be reconfigured on-the-fly.
VM_STORE_ENTRIES = [
('uuid', str),
('vcpus', int),
('vcpu_avail', int),
('memory', int),
('maxmem', int),
('start_time', float),
]
VM_STORED_ENTRIES = VM_CONFIG_ENTRIES + VM_STORE_ENTRIES
# Configuration entries that we expect to round-trip -- be read from the
# config file or xc, written to save-files (i.e. through sxpr), and reused as
# config on restart or restore, all without munging. Some configuration
# entries are munged for backwards compatibility reasons, or because they
# don't come out of xc in the same form as they are specified in the config
# file, so those are handled separately.
ROUNDTRIPPING_CONFIG_ENTRIES = [
('uuid', str),
('vcpus', int),
('vcpu_avail', int),
('cpu_weight', float),
('memory', int),
('shadow_memory', int),
('maxmem', int),
('bootloader', str),
('bootloader_args', str),
('features', str),
('localtime', int),
]
ROUNDTRIPPING_CONFIG_ENTRIES += VM_CONFIG_ENTRIES
## Static Configuration
STATIC_CONFIG_ENTRIES = [
('cpu', int),
('cpus', str),
('image', list),
('security', list), # TODO: what if null?
]
DEPRECATED_ENTRIES = [
('restart', str),
]
##
## Config Choices
##
CONFIG_RESTART_MODES = ('restart', 'destroy', 'preserve', 'rename-restart')
CONFIG_OLD_DOM_STATES = ('running', 'blocked', 'paused', 'shutdown',
'crashed', 'dying')
##
## Defaults
##
def DEFAULT_VCPUS(info):
if 'max_vcpu_id' in info: return int(info['max_vcpu_id']) + 1
else: return 1
DEFAULT_CONFIGURATION = (
('uuid', lambda info: uuid.createString()),
('name', lambda info: 'Domain-' + info['uuid']),
('on_poweroff', lambda info: 'destroy'),
('on_reboot', lambda info: 'restart'),
('on_crash', lambda info: 'restart'),
('features', lambda info: ''),
('memory', lambda info: 0),
('shadow_memory',lambda info: 0),
('maxmem', lambda info: 0),
('bootloader', lambda info: None),
('bootloader_args', lambda info: None),
('backend', lambda info: []),
('device', lambda info: {}),
('image', lambda info: None),
('security', lambda info: []),
('autostart', lambda info: 0),
('autostop', lambda info: 0),
('on_xend_stop', lambda info: 'shutdown'),
('cpus', lambda info: []),
('cpu_weight', lambda info: 1.0),
('vcpus', lambda info: DEFAULT_VCPUS(info)),
('online_vcpus', lambda info: info['vcpus']),
('max_vcpu_id', lambda info: info['vcpus']-1),
('vcpu_avail', lambda info: (1<<info['vcpus'])-1),
# New for Xen API
('kernel_kernel', lambda info: ''),
('kernel_initrd', lambda info: ''),
('kernel_args', lambda info: ''),
)
class XendConfigError(VmError):
def __str__(self):
return 'Invalid Configuration: %s' % str(self.value)
##
## XendConfig SXP Config Compat
##
class XendSXPConfig:
def get_domid(self):
pass
def get_handle(self):
return self['uuid']
##
## XendConfig Class (an extended dictionary)
##
class XendConfig(dict):
""" Generic Configuration Parser accepting SXP, Python or XML.
This is a dictionary-like object that is populated.
@ivar legacy: dictionary holding legacy xen domain info
@ivar xenapi: dictionary holding xen api config info
"""
def __init__(self, filename = None, fd = None,
sxp = None, xml = None, pycfg = None, xenapi_vm = None,
cfg = {}):
"""Constructor. Provide either the filename, fd or sxp.
@keyword filename: filename of an SXP file
@keyword fd: file descriptor of an SXP file
@keyword sxp: a list of list of a parsed SXP
@keyword xml: an XML tree object
@keyword xenapi_vm: a struct passed from an XMLRPC call (Xen API)
@keyword cfg: a dictionary of configuration (eg. from xc)
"""
format = 'unknown'
self.xenapi = {}
if filename and not fd:
fd = open(filename, 'r')
if fd:
format = self._detect_format(fd)
if fd:
if format == 'sxp':
sxp = self._read_sxp(fd)
elif format == 'python' and filename != None:
pycfg = self._read_python(filename)
elif format == 'python' and filename == None:
raise XendConfigError("Python files must be passed as a "
"filename rather than file descriptor.")
elif format == 'xml':
xml = self._read_xml(fd)
else:
raise XendConfigError("Unable to determine format of file")
if sxp:
cfg = self._populate_from_sxp(sxp)
if xml:
cfg = self._populate_from_xml(xml)
if pycfg:
cfg = self._populate_from_python_config(pycfg)
if xenapi_vm:
cfg = self._populate_from_xenapi_vm(xenapi_vm)
if cfg:
self.update(cfg)
if xenapi_vm:
self.xenapi.update(xenapi_vm)
log.debug('XendConfig: %s' % str(self))
self.validate()
#
# Xen API Attribute Access
#
def __getattr__(self, name):
try:
return dict.__getattr__(self, name)
except AttributeError:
try:
return self.__dict__['xenapi'][name]
except KeyError:
raise AttributeError("XendConfig Xen API has no attribute "
"'%s'" % name)
def __setattr__(self, name, value):
try:
return dict.__setattr__(self, name, value)
except AttributeError:
self.xenapi[name] = value
#self.set_legacy_api_with_xen_api_value(name, value)
def __delattr__(self, name):
try:
dict.__delattr__(self, name)
except AttributeError:
del self.xenapi[name]
#self.del_legacy_api_with_xen_api_key(name)
"""
#
# Legacy API Attribute Access
#
def __getitem__(self, key):
try:
return self.legacy[key]
except KeyError:
raise AttributeError, "XendConfig Legacy has no attribute '%s'"\
% key
def __setitem__(self, key, value):
self.legacy[key] = value
self.set_xen_api_with_legacy_api_value(key, value)
def __delitem__(self, key):
del self.legacy[key]
self.del_xen_api_with_legacy_api_key(key)
"""
def _detect_format(self, fd):
"""Detect the format of the configuration passed.
@param fd: file descriptor of contents to detect
@rtype: string, 'sxp', 'xml', 'python' or 'unknown'
"""
format = 'unknown'
fd.seek(0)
for line in fd:
stripped = line.strip()
if stripped:
if re.search(r'^\(', stripped):
format = 'sxp'
elif re.search(r'^\<?xml', stripped):
format = 'xml'
else:
format = 'python'
break
fd.seek(0)
return format
def _read_sxp(self, fd):
""" Read and parse SXP (from SXP to list of lists)
@rtype: list of lists.
"""
try:
parsed = sxp.parse(fd)[0]
return parsed
except:
raise
return None
def _read_xml(self, fd):
"""TODO: Read and parse XML (from XML to dict)
@rtype: dict
"""
raise NotImplementedError
def _read_python(self, filename):
"""Read and parse python module that represents the config.
@rtype: dict
"""
cfg_globals = {}
execfile(filename, cfg_globals, {})
return cfg_globals
def _populate_from_sxp(self, parsed):
""" Populate this XendConfig using the parsed SXP.
@rtype: dictionary
"""
cfg = {}
# First step is to convert deprecated options to
# current equivalents.
restart = sxp.child_value(parsed, 'restart')
if restart:
if restart == 'onreboot':
cfg['on_poweroff'] = 'destroy'
cfg['on_reboot'] = 'restart'
cfg['on_crash'] = 'destroy'
elif restart == 'always':
for opt in ('on_poweroff', 'on_reboot', 'on_crash'):
cfg[opt] = 'restart'
elif restart == 'never':
for opt in ('on_poweroff', 'on_reboot', 'on_crash'):
cfg[opt] = 'never'
else:
log.warn('Ignoring unrecognised value for deprecated option:'
'restart = \'%s\'', restart)
# Only extract options we know about.
all_params = VM_CONFIG_ENTRIES + ROUNDTRIPPING_CONFIG_ENTRIES + \
STATIC_CONFIG_ENTRIES
for key, typeconv in all_params:
val = sxp.child_value(parsed, key)
if val:
try:
cfg[key] = typeconv(val)
except ValueError:
pass
# Manually extract other complex configuration
# options.
cfg['backend'] = []
for c in sxp.children(parsed, 'backend'):
cfg['backend'].append(sxp.name(sxp.child0(c)))
cfg['device'] = {}
for dev in sxp.children(parsed, 'device'):
config = sxp.child0(dev)
dev_type = sxp.name(config)
dev_info = {}
for opt, val in config[1:]:
dev_info[opt] = val
# create uuid if it doesn't
dev_uuid = dev_info.get('uuid', uuid.createString())
dev_info['uuid'] = dev_uuid
cfg['device'][dev_uuid] = (dev_type, dev_info)
#cfg['device'].append((sxp.name(config), config))
# Extract missing data from configuration entries
if 'image' in cfg:
image_vcpus = sxp.child_value(cfg['image'], 'vcpus')
if image_vcpus is not None:
try:
if 'vcpus' not in cfg:
cfg['vcpus'] = int(image_vcpus)
elif cfg['vcpus'] != int(image_vcpus):
cfg['vcpus'] = int(image_vcpus)
log.warn('Overriding vcpus from %d to %d using image'
'vcpus value.', cfg['vcpus'])
except ValueError, e:
raise XendConfigError('integer expeceted: %s: %s' %
str(cfg['image']), e)
# Deprecated cpu configuration
if 'cpu' in cfg:
if 'cpus' in cfg:
cfg['cpus'] = "%s,%s" % (str(cfg['cpu']), cfg['cpus'])
else:
cfg['cpus'] = str(cfg['cpu'])
# convert 'cpus' string to list of ints
# 'cpus' supports a list of ranges (0-3), seperated by
# commas, and negation, (^1).
# Precedence is settled by order of the string:
# "0-3,^1" -> [0,2,3]
# "0-3,^1,1" -> [0,1,2,3]
try:
if 'cpus' in cfg:
cpus = []
for c in cfg['cpus'].split(','):
if c.find('-') != -1:
(x, y) = c.split('-')
for i in range(int(x), int(y)+1):
cpus.append(int(i))
else:
# remove this element from the list
if c[0] == '^':
cpus = [x for x in cpus if x != int(c[1:])]
else:
cpus.append(int(c))
cfg['cpus'] = cpus
except ValueError, e:
raise XendConfigError('cpus = %s: %s' % (cfg['cpus'], e))
# Parse image SXP outside of image.py
# - used to be only done in image.py
if 'image' in cfg:
cfg['kernel_kernel'] = sxp.child_value(cfg['image'], 'kernel','')
cfg['kernel_initrd'] = sxp.child_value(cfg['image'], 'ramdisk','')
kernel_args = sxp.child_value(cfg['image'], 'args', '')
# attempt to extract extra arguments from SXP config
arg_ip = sxp.child_value(cfg['image'], 'ip')
if arg_ip: kernel_args += ' ip=%s' % arg_ip
arg_root = sxp.child_value(cfg['image'], 'root')
if arg_root: kernel_args += ' root=%s' % arg_root
cfg['kernel_args'] = kernel_args
# TODO: get states
old_state = sxp.child_value(parsed, 'state')
if old_state:
for i in range(len(CONFIG_OLD_DOM_STATES)):
cfg[CONFIG_OLD_DOM_STATES[i]] = (old_state[i] != '-')
# Xen API extra cfgs
# ------------------
cfg['vif_refs'] = []
cfg['vbd_refs'] = []
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':
cfg['vbd_refs'].append(dev_uuid)
return cfg
def _populate_from_xenapi_vm(self, xenapi_vm):
cfg = {}
for cfgkey, apikey in LEGACY_CFG_TO_XENAPI_CFG.items():
try:
cfg[cfgkey] = xenapi_vm[apikey]
except KeyError:
pass
# Reconstruct image SXP
# TODO: get rid of SXP altogether from here
sxp_image = ['linux']
if xenapi_vm['kernel_kernel']:
sxp_image.append(['kernel', xenapi_vm['kernel_kernel']])
if xenapi_vm['kernel_initrd']:
sxp_image.append(['ramdisk', xenapi_vm['kernel_initrd']])
if xenapi_vm['kernel_args']:
sxp_image.append(['args', xenapi_vm['kernel_args']])
cfg['image'] = prettyprintstring(sxp_image)
# make sure device structures are there.
if 'device' not in cfg:
cfg['device'] = {}
if 'vif_refs' not in cfg:
cfg['vif_refs'] = []
if 'vbd_refs' not in cfg:
cfg['vbd_refs'] = []
return cfg
def _sync_xen_api_from_legacy_api(self):
""" Sync all the attributes that is supported by the Xen API
from the legacy API configuration.
"""
for cfgkey, apikey in LEGACY_CFG_TO_XENAPI_CFG.items():
if cfgkey in self:
self.xenapi[apikey] = self[cfgkey]
def _sync_legacy_api_from_xen_api(self):
for cfgkey, apikey in LEGACY_CFG_TO_XENAPI_CFG.items():
if apikey in self.xenapi:
self[cfgkey] = self.xenapi[apikey]
def _populate_from_xml(self, parsed_xml):
raise NotImplementedError
def _populate_from_python_config(self, parsed_py):
raise NotImplementedError
def get_sxp(self, domain = None, ignore_devices = False, ignore = []):
""" Get SXP representation of this config object.
Incompat: removed store_mfn, console_mfn
@keyword domain: (optional) XendDomainInfo to get extra information
from such as domid and running devices.
@type domain: XendDomainInfo
@keyword ignore: (optional) list of 'keys' that we do not want
to export.
@type ignore: list of strings
@rtype: list of list (SXP representation)
"""
sxpr = ['domain']
# TODO: domid/dom is the same thing but called differently
# depending if it is from xenstore or sxpr.
if domain.getDomid() != None:
sxpr.append(['domid', domain.getDomid()])
for cfg, typefunc in ROUNDTRIPPING_CONFIG_ENTRIES:
if cfg in self:
if self[cfg] != None:
sxpr.append([cfg, self[cfg]])
if 'image' in self:
sxpr.append(['image', self['image']])
if 'security' in self:
sxpr.append(['security', self['security']])
if 'shutdown_reason' in self:
sxpr.append(['shutdown_reason', self['shutdown_reason']])
if 'cpu_time' in self:
sxpr.append(['cpu_time', self['cpu_time']/1e9])
sxpr.append(['online_vcpus', self['online_vcpus']])
if 'start_time' in self:
uptime = time.time() - self['start_time']
sxpr.append(['up_time', str(uptime)])
sxpr.append(['start_time', str(self['start_time'])])
sxpr.append(['autostart', self.get('autostart', 0)])
sxpr.append(['autostop', self.get('autostop', 0)])
sxpr.append(['on_xend_stop', self.get('on_xend_stop', 'shutdown')])
sxpr.append(['status', domain.state])
# Marshall devices (running or from configuration)
if not ignore_devices:
for cls in XendDevices.valid_devices():
found = False
# figure if there is a device that is running
if domain:
try:
controller = domain.getDeviceController(cls)
configs = controller.configurations()
for config in configs:
sxpr.append(['device', config])
found = True
except:
log.exception("dumping sxp from device controllers")
pass
# if we didn't find that device, check the existing config
# for a device in the same class
if not found:
for dev_type, dev_info in self.all_devices_sxpr():
if dev_type == cls:
sxpr.append(['device', dev_info])
return sxpr
def validate(self):
""" Validate the configuration and fill in missing configuration
with defaults.
"""
# Fill in default values
for key, default_func in DEFAULT_CONFIGURATION:
if key not in self:
self[key] = default_func(self)
# Basic sanity checks
if 'image' in self and isinstance(self['image'], str):
self['image'] = sxp.from_string(self['image'])
if 'security' in self and isinstance(self['security'], str):
self['security'] = sxp.from_string(self['security'])
if self['memory'] == 0 and 'mem_kb' in self:
self['memory'] = (self['mem_kb'] + 1023)/1024
if self['memory'] <= 0:
raise XendConfigError('Invalid memory size: %s' %
str(self['memory']))
self['maxmem'] = max(self['memory'], self['maxmem'])
# Verify devices
for d_uuid, (d_type, d_info) in self['device'].items():
if d_type not in XendDevices.valid_devices():
raise XendConfigError('Invalid device (%s)' % d_type)
# Verify restart modes
for event in ('on_poweroff', 'on_reboot', 'on_crash'):
if self[event] not in CONFIG_RESTART_MODES:
raise XendConfigError('Invalid restart event: %s = %s' % \
(event, str(self[event])))
def device_add(self, dev_type, cfg_sxp = None, cfg_xenapi = None):
if dev_type not in XendDevices.valid_devices():
raise XendConfigError("XendConfig: %s not a valid device type" %
dev_type)
if cfg_sxp == None and cfg_xenapi == None:
raise XendConfigError("XendConfig: device_add requires some "
"config.")
if cfg_sxp:
config = sxp.child0(cfg_sxp)
dev_type = sxp.name(config)
dev_info = {}
try:
for opt, val in config[1:]:
dev_info[opt] = val
except ValueError:
log.debug('XendConfig.device_add: %s' % config)
pass # SXP has no options for this device
# create uuid if it doesn't exist
dev_uuid = dev_info.get('uuid', uuid.createString())
dev_info['uuid'] = dev_uuid
self['device'][dev_uuid] = (dev_type, dev_info)
return dev_uuid
if cfg_xenapi:
dev_info = {}
if dev_type == 'vif':
if cfg_xenapi.get('MAC'): # don't add if blank
dev_info['mac'] = cfg_xenapi.get('MAC')
# vifname is the name on the guest, not dom0
# TODO: we don't have the ability to find that out or
# change it from dom0
#if cfg_xenapi.get('device'): # don't add if blank
# dev_info['vifname'] = cfg_xenapi.get('device')
if cfg_xenapi.get('type'):
dev_info['type'] = cfg_xenapi.get('type')
dev_uuid = cfg_xenapi.get('uuid', uuid.createString())
dev_info['uuid'] = dev_uuid
self['device'][dev_uuid] = (dev_type, dev_info)
return dev_uuid
elif dev_type == 'vbd':
dev_info['uname'] = cfg_xenapi.get('image', None)
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)
return dev_uuid
return ''
def device_sxpr(self, dev_uuid = None, dev_type = None, dev_info = None):
"""Get Device SXPR by either giving the device UUID or (type, config).
@rtype: list of lists
@return: device config sxpr
"""
sxpr = []
if dev_uuid != None and dev_uuid in self['device']:
dev_type, dev_info = self['device']
if dev_type == None or dev_info == None:
raise XendConfigError("Required either UUID or device type and "
"configuration dictionary.")
sxpr.append(dev_type)
config = [(opt, val) for opt, val in dev_info.items() \
if opt != 'type']
sxpr += config
return sxpr
def all_devices_sxpr(self):
sxprs = []
for dev_type, dev_info in self['device'].values():
sxpr = self.device_sxpr(dev_type = dev_type, dev_info = dev_info)
sxprs.append((dev_type, sxpr))
return sxprs
#
# debugging
#
if __name__ == "__main__":
pass
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.