1
# Copyright (c) 2010 Citrix Systems, Inc.
3
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
# not use this file except in compliance with the License. You may obtain
5
# a copy of the License at
7
# http://www.apache.org/licenses/LICENSE-2.0
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
# License for the specific language governing permissions and limitations
16
# Helper functions for the Nova xapi plugins. In time, this will merge
17
# with the pluginlib.py shipped with xapi, but for now, that file is not
18
# very stable, so it's easiest just to have a copy of all the functions
24
import logging.handlers
31
def configure_logging(name):
32
log = logging.getLogger()
33
log.setLevel(logging.DEBUG)
34
sysh = logging.handlers.SysLogHandler('/dev/log')
35
sysh.setLevel(logging.DEBUG)
36
formatter = logging.Formatter('%s: %%(levelname)-8s %%(message)s' % name)
37
sysh.setFormatter(formatter)
43
class PluginError(Exception):
44
"""Base Exception class for all plugin errors."""
45
def __init__(self, *args):
46
Exception.__init__(self, *args)
48
class ArgumentError(PluginError):
49
"""Raised when required arguments are missing, argument values are invalid,
50
or incompatible arguments are given.
52
def __init__(self, *args):
53
PluginError.__init__(self, *args)
58
def ignore_failure(func, *args, **kwargs):
60
return func(*args, **kwargs)
61
except XenAPI.Failure, e:
62
logging.error('Ignoring XenAPI.Failure %s', e)
66
##### Argument validation
68
ARGUMENT_PATTERN = re.compile(r'^[a-zA-Z0-9_:\.\-,]+$')
70
def validate_exists(args, key, default=None):
71
"""Validates that a string argument to a RPC method call is given, and
72
matches the shell-safe regex, with an optional default value in case it
78
if len(args[key]) == 0:
79
raise ArgumentError('Argument %r value %r is too short.' % (key, args[key]))
80
if not ARGUMENT_PATTERN.match(args[key]):
81
raise ArgumentError('Argument %r value %r contains invalid characters.' % (key, args[key]))
82
if args[key][0] == '-':
83
raise ArgumentError('Argument %r value %r starts with a hyphen.' % (key, args[key]))
85
elif default is not None:
88
raise ArgumentError('Argument %s is required.' % key)
90
def validate_bool(args, key, default=None):
91
"""Validates that a string argument to a RPC method call is a boolean string,
92
with an optional default value in case it does not exist.
94
Returns the python boolean value.
96
value = validate_exists(args, key, default)
97
if value.lower() == 'true':
99
elif value.lower() == 'false':
102
raise ArgumentError("Argument %s may not take value %r. Valid values are ['true', 'false']." % (key, value))
104
def exists(args, key):
105
"""Validates that a freeform string argument to a RPC method call is given.
111
raise ArgumentError('Argument %s is required.' % key)
113
def optional(args, key):
114
"""If the given key is in args, return the corresponding value, otherwise
116
return key in args and args[key] or None
119
def get_this_host(session):
120
return session.xenapi.session.get_this_host(session.handle)
123
def get_domain_0(session):
124
this_host_ref = get_this_host(session)
125
expr = 'field "is_control_domain" = "true" and field "resident_on" = "%s"' % this_host_ref
126
return session.xenapi.VM.get_all_records_where(expr).keys()[0]
129
def create_vdi(session, sr_ref, name_label, virtual_size, read_only):
130
vdi_ref = session.xenapi.VDI.create(
131
{ 'name_label': name_label,
132
'name_description': '',
134
'virtual_size': str(virtual_size),
137
'read_only': read_only,
142
logging.debug('Created VDI %s (%s, %s, %s) on %s.', vdi_ref, name_label,
143
virtual_size, read_only, sr_ref)
147
def with_vdi_in_dom0(session, vdi, read_only, f):
148
dom0 = get_domain_0(session)
152
vbd_rec['userdevice'] = 'autodetect'
153
vbd_rec['bootable'] = False
154
vbd_rec['mode'] = read_only and 'RO' or 'RW'
155
vbd_rec['type'] = 'disk'
156
vbd_rec['unpluggable'] = True
157
vbd_rec['empty'] = False
158
vbd_rec['other_config'] = {}
159
vbd_rec['qos_algorithm_type'] = ''
160
vbd_rec['qos_algorithm_params'] = {}
161
vbd_rec['qos_supported_algorithms'] = []
162
logging.debug('Creating VBD for VDI %s ... ', vdi)
163
vbd = session.xenapi.VBD.create(vbd_rec)
164
logging.debug('Creating VBD for VDI %s done.', vdi)
166
logging.debug('Plugging VBD %s ... ', vbd)
167
session.xenapi.VBD.plug(vbd)
168
logging.debug('Plugging VBD %s done.', vbd)
169
return f(session.xenapi.VBD.get_device(vbd))
171
logging.debug('Destroying VBD for VDI %s ... ', vdi)
172
vbd_unplug_with_retry(session, vbd)
173
ignore_failure(session.xenapi.VBD.destroy, vbd)
174
logging.debug('Destroying VBD for VDI %s done.', vdi)
177
def vbd_unplug_with_retry(session, vbd):
178
"""Call VBD.unplug on the given VBD, with a retry if we get
179
DEVICE_DETACH_REJECTED. For reasons which I don't understand, we're
180
seeing the device still in use, even when all processes using the device
184
session.xenapi.VBD.unplug(vbd)
185
logging.debug('VBD.unplug successful first time.')
187
except XenAPI.Failure, e:
188
if (len(e.details) > 0 and
189
e.details[0] == 'DEVICE_DETACH_REJECTED'):
190
logging.debug('VBD.unplug rejected: retrying...')
192
elif (len(e.details) > 0 and
193
e.details[0] == 'DEVICE_ALREADY_DETACHED'):
194
logging.debug('VBD.unplug successful eventually.')
197
logging.error('Ignoring XenAPI.Failure in VBD.unplug: %s', e)
201
def with_http_connection(proto, netloc, f):
202
conn = (proto == 'https' and
203
httplib.HTTPSConnection(netloc) or
204
httplib.HTTPConnection(netloc))
211
def with_file(dest_path, mode, f):
212
dest = open(dest_path, mode)