Made the plugin output fully json-ified, so I could remove the exception handlers in vmops.py. Cleaned up some pep8 issues that weren't caught in earlier runs.
This commit is contained in:
parent
8147c6bbc6
commit
02c86d1e11
@ -289,11 +289,7 @@ class VMOps(object):
|
|||||||
found at that path, returns None.
|
found at that path, returns None.
|
||||||
"""
|
"""
|
||||||
ret = self._make_xenstore_call('list_records', vm, path)
|
ret = self._make_xenstore_call('list_records', vm, path)
|
||||||
try:
|
return json.loads(ret)
|
||||||
return json.loads(ret)
|
|
||||||
except ValueError:
|
|
||||||
# Not a valid JSON value
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def read_from_xenstore(self, vm, path):
|
def read_from_xenstore(self, vm, path):
|
||||||
"""Returns the value stored in the xenstore record for the given VM
|
"""Returns the value stored in the xenstore record for the given VM
|
||||||
@ -305,14 +301,11 @@ class VMOps(object):
|
|||||||
{'ignore_missing_path': 'True'})
|
{'ignore_missing_path': 'True'})
|
||||||
except self.XenAPI.Failure, e:
|
except self.XenAPI.Failure, e:
|
||||||
return None
|
return None
|
||||||
try:
|
ret = json.loads(ret)
|
||||||
return json.loads(ret)
|
if ret == "None":
|
||||||
except ValueError:
|
# Can't marshall None over RPC calls.
|
||||||
# Not a JSON object
|
return None
|
||||||
if ret == "None":
|
return ret
|
||||||
# Can't marshall None over RPC calls.
|
|
||||||
return None
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def write_to_xenstore(self, vm, path, value):
|
def write_to_xenstore(self, vm, path, value):
|
||||||
"""Writes the passed value to the xenstore record for the given VM
|
"""Writes the passed value to the xenstore record for the given VM
|
||||||
|
@ -45,6 +45,7 @@ class PluginError(Exception):
|
|||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
Exception.__init__(self, *args)
|
Exception.__init__(self, *args)
|
||||||
|
|
||||||
|
|
||||||
class ArgumentError(PluginError):
|
class ArgumentError(PluginError):
|
||||||
"""Raised when required arguments are missing, argument values are invalid,
|
"""Raised when required arguments are missing, argument values are invalid,
|
||||||
or incompatible arguments are given.
|
or incompatible arguments are given.
|
||||||
@ -67,6 +68,7 @@ def ignore_failure(func, *args, **kwargs):
|
|||||||
|
|
||||||
ARGUMENT_PATTERN = re.compile(r'^[a-zA-Z0-9_:\.\-,]+$')
|
ARGUMENT_PATTERN = re.compile(r'^[a-zA-Z0-9_:\.\-,]+$')
|
||||||
|
|
||||||
|
|
||||||
def validate_exists(args, key, default=None):
|
def validate_exists(args, key, default=None):
|
||||||
"""Validates that a string argument to a RPC method call is given, and
|
"""Validates that a string argument to a RPC method call is given, and
|
||||||
matches the shell-safe regex, with an optional default value in case it
|
matches the shell-safe regex, with an optional default value in case it
|
||||||
@ -76,20 +78,24 @@ def validate_exists(args, key, default=None):
|
|||||||
"""
|
"""
|
||||||
if key in args:
|
if key in args:
|
||||||
if len(args[key]) == 0:
|
if len(args[key]) == 0:
|
||||||
raise ArgumentError('Argument %r value %r is too short.' % (key, args[key]))
|
raise ArgumentError('Argument %r value %r is too short.' %
|
||||||
|
(key, args[key]))
|
||||||
if not ARGUMENT_PATTERN.match(args[key]):
|
if not ARGUMENT_PATTERN.match(args[key]):
|
||||||
raise ArgumentError('Argument %r value %r contains invalid characters.' % (key, args[key]))
|
raise ArgumentError('Argument %r value %r contains invalid '
|
||||||
|
'characters.' % (key, args[key]))
|
||||||
if args[key][0] == '-':
|
if args[key][0] == '-':
|
||||||
raise ArgumentError('Argument %r value %r starts with a hyphen.' % (key, args[key]))
|
raise ArgumentError('Argument %r value %r starts with a hyphen.'
|
||||||
|
% (key, args[key]))
|
||||||
return args[key]
|
return args[key]
|
||||||
elif default is not None:
|
elif default is not None:
|
||||||
return default
|
return default
|
||||||
else:
|
else:
|
||||||
raise ArgumentError('Argument %s is required.' % key)
|
raise ArgumentError('Argument %s is required.' % key)
|
||||||
|
|
||||||
|
|
||||||
def validate_bool(args, key, default=None):
|
def validate_bool(args, key, default=None):
|
||||||
"""Validates that a string argument to a RPC method call is a boolean string,
|
"""Validates that a string argument to a RPC method call is a boolean
|
||||||
with an optional default value in case it does not exist.
|
string, with an optional default value in case it does not exist.
|
||||||
|
|
||||||
Returns the python boolean value.
|
Returns the python boolean value.
|
||||||
"""
|
"""
|
||||||
@ -99,7 +105,9 @@ def validate_bool(args, key, default=None):
|
|||||||
elif value.lower() == 'false':
|
elif value.lower() == 'false':
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
raise ArgumentError("Argument %s may not take value %r. Valid values are ['true', 'false']." % (key, value))
|
raise ArgumentError("Argument %s may not take value %r. "
|
||||||
|
"Valid values are ['true', 'false']." % (key, value))
|
||||||
|
|
||||||
|
|
||||||
def exists(args, key):
|
def exists(args, key):
|
||||||
"""Validates that a freeform string argument to a RPC method call is given.
|
"""Validates that a freeform string argument to a RPC method call is given.
|
||||||
@ -110,6 +118,7 @@ def exists(args, key):
|
|||||||
else:
|
else:
|
||||||
raise ArgumentError('Argument %s is required.' % key)
|
raise ArgumentError('Argument %s is required.' % key)
|
||||||
|
|
||||||
|
|
||||||
def optional(args, key):
|
def optional(args, key):
|
||||||
"""If the given key is in args, return the corresponding value, otherwise
|
"""If the given key is in args, return the corresponding value, otherwise
|
||||||
return None"""
|
return None"""
|
||||||
@ -122,13 +131,14 @@ def get_this_host(session):
|
|||||||
|
|
||||||
def get_domain_0(session):
|
def get_domain_0(session):
|
||||||
this_host_ref = get_this_host(session)
|
this_host_ref = get_this_host(session)
|
||||||
expr = 'field "is_control_domain" = "true" and field "resident_on" = "%s"' % this_host_ref
|
expr = 'field "is_control_domain" = "true" and field "resident_on" = "%s"'
|
||||||
|
expr = expr % this_host_ref
|
||||||
return session.xenapi.VM.get_all_records_where(expr).keys()[0]
|
return session.xenapi.VM.get_all_records_where(expr).keys()[0]
|
||||||
|
|
||||||
|
|
||||||
def create_vdi(session, sr_ref, name_label, virtual_size, read_only):
|
def create_vdi(session, sr_ref, name_label, virtual_size, read_only):
|
||||||
vdi_ref = session.xenapi.VDI.create(
|
vdi_ref = session.xenapi.VDI.create(
|
||||||
{ 'name_label': name_label,
|
{'name_label': name_label,
|
||||||
'name_description': '',
|
'name_description': '',
|
||||||
'SR': sr_ref,
|
'SR': sr_ref,
|
||||||
'virtual_size': str(virtual_size),
|
'virtual_size': str(virtual_size),
|
||||||
@ -138,7 +148,7 @@ def create_vdi(session, sr_ref, name_label, virtual_size, read_only):
|
|||||||
'xenstore_data': {},
|
'xenstore_data': {},
|
||||||
'other_config': {},
|
'other_config': {},
|
||||||
'sm_config': {},
|
'sm_config': {},
|
||||||
'tags': [] })
|
'tags': []})
|
||||||
logging.debug('Created VDI %s (%s, %s, %s) on %s.', vdi_ref, name_label,
|
logging.debug('Created VDI %s (%s, %s, %s) on %s.', vdi_ref, name_label,
|
||||||
virtual_size, read_only, sr_ref)
|
virtual_size, read_only, sr_ref)
|
||||||
return vdi_ref
|
return vdi_ref
|
||||||
|
@ -34,10 +34,17 @@ import pluginlib_nova as pluginlib
|
|||||||
pluginlib.configure_logging("xenstore")
|
pluginlib.configure_logging("xenstore")
|
||||||
|
|
||||||
|
|
||||||
|
def jsonify(fnc):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
return json.dumps(fnc(*args, **kwargs))
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
@jsonify
|
||||||
def read_record(self, arg_dict):
|
def read_record(self, arg_dict):
|
||||||
"""Returns the value stored at the given path for the given dom_id.
|
"""Returns the value stored at the given path for the given dom_id.
|
||||||
These must be encoded as key/value pairs in arg_dict. You can
|
These must be encoded as key/value pairs in arg_dict. You can
|
||||||
optinally include a key 'ignore_missing_path'; if this is present
|
optinally include a key 'ignore_missing_path'; if this is present
|
||||||
and boolean True, attempting to read a non-existent path will return
|
and boolean True, attempting to read a non-existent path will return
|
||||||
the string 'None' instead of raising an exception.
|
the string 'None' instead of raising an exception.
|
||||||
"""
|
"""
|
||||||
@ -46,16 +53,21 @@ def read_record(self, arg_dict):
|
|||||||
return _run_command(cmd).rstrip("\n")
|
return _run_command(cmd).rstrip("\n")
|
||||||
except pluginlib.PluginError, e:
|
except pluginlib.PluginError, e:
|
||||||
if arg_dict.get("ignore_missing_path", False):
|
if arg_dict.get("ignore_missing_path", False):
|
||||||
cmd = "xenstore-exists /local/domain/%(dom_id)s/%(path)s; echo $?" % arg_dict
|
cmd = "xenstore-exists /local/domain/%(dom_id)s/%(path)s; echo $?"
|
||||||
|
cmd = cmd % arg_dict
|
||||||
ret = _run_command(cmd).strip()
|
ret = _run_command(cmd).strip()
|
||||||
# If the path exists, the cmd should return "0"
|
# If the path exists, the cmd should return "0"
|
||||||
if ret != "0":
|
if ret != "0":
|
||||||
# No such path, so ignore the error
|
# No such path, so ignore the error and return the
|
||||||
return "None"
|
# string 'None', since None can't be marshalled
|
||||||
|
# over RPC.
|
||||||
|
return "None"
|
||||||
# Either we shouldn't ignore path errors, or another
|
# Either we shouldn't ignore path errors, or another
|
||||||
# error was hit. Re-raise.
|
# error was hit. Re-raise.
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
@jsonify
|
||||||
def write_record(self, arg_dict):
|
def write_record(self, arg_dict):
|
||||||
"""Writes to xenstore at the specified path. If there is information
|
"""Writes to xenstore at the specified path. If there is information
|
||||||
already stored in that location, it is overwritten. As in read_record,
|
already stored in that location, it is overwritten. As in read_record,
|
||||||
@ -63,10 +75,13 @@ def write_record(self, arg_dict):
|
|||||||
you must specify a 'value' key, whose value must be a string. Typically,
|
you must specify a 'value' key, whose value must be a string. Typically,
|
||||||
you can json-ify more complex values and store the json output.
|
you can json-ify more complex values and store the json output.
|
||||||
"""
|
"""
|
||||||
cmd = "xenstore-write /local/domain/%(dom_id)s/%(path)s '%(value)s'" % arg_dict
|
cmd = "xenstore-write /local/domain/%(dom_id)s/%(path)s '%(value)s'"
|
||||||
|
cmd = cmd % arg_dict
|
||||||
_run_command(cmd)
|
_run_command(cmd)
|
||||||
return arg_dict["value"]
|
return arg_dict["value"]
|
||||||
|
|
||||||
|
|
||||||
|
@jsonify
|
||||||
def list_records(self, arg_dict):
|
def list_records(self, arg_dict):
|
||||||
"""Returns all the stored data at or below the given path for the
|
"""Returns all the stored data at or below the given path for the
|
||||||
given dom_id. The data is returned as a json-ified dict, with the
|
given dom_id. The data is returned as a json-ified dict, with the
|
||||||
@ -80,7 +95,8 @@ def list_records(self, arg_dict):
|
|||||||
except pluginlib.PluginError, e:
|
except pluginlib.PluginError, e:
|
||||||
if "No such file or directory" in "%s" % e:
|
if "No such file or directory" in "%s" % e:
|
||||||
# Path doesn't exist.
|
# Path doesn't exist.
|
||||||
return json.dumps({})
|
return {}
|
||||||
|
return str(e)
|
||||||
raise
|
raise
|
||||||
base_path = arg_dict["path"]
|
base_path = arg_dict["path"]
|
||||||
paths = _paths_from_ls(recs)
|
paths = _paths_from_ls(recs)
|
||||||
@ -96,8 +112,10 @@ def list_records(self, arg_dict):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
val = rec
|
val = rec
|
||||||
ret[path] = val
|
ret[path] = val
|
||||||
return json.dumps(ret)
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
@jsonify
|
||||||
def delete_record(self, arg_dict):
|
def delete_record(self, arg_dict):
|
||||||
"""Just like it sounds: it removes the record for the specified
|
"""Just like it sounds: it removes the record for the specified
|
||||||
VM and the specified path from xenstore.
|
VM and the specified path from xenstore.
|
||||||
@ -105,6 +123,7 @@ def delete_record(self, arg_dict):
|
|||||||
cmd = "xenstore-rm /local/domain/%(dom_id)s/%(path)s" % arg_dict
|
cmd = "xenstore-rm /local/domain/%(dom_id)s/%(path)s" % arg_dict
|
||||||
return _run_command(cmd)
|
return _run_command(cmd)
|
||||||
|
|
||||||
|
|
||||||
def _paths_from_ls(recs):
|
def _paths_from_ls(recs):
|
||||||
"""The xenstore-ls command returns a listing that isn't terribly
|
"""The xenstore-ls command returns a listing that isn't terribly
|
||||||
useful. This method cleans that up into a dict with each path
|
useful. This method cleans that up into a dict with each path
|
||||||
@ -137,13 +156,15 @@ def _paths_from_ls(recs):
|
|||||||
last_nm = barename
|
last_nm = barename
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def _run_command(cmd):
|
def _run_command(cmd):
|
||||||
"""Abstracts out the basics of issuing system commands. If the command
|
"""Abstracts out the basics of issuing system commands. If the command
|
||||||
returns anything in stderr, a PluginError is raised with that information.
|
returns anything in stderr, a PluginError is raised with that information.
|
||||||
Otherwise, the output from stdout is returned.
|
Otherwise, the output from stdout is returned.
|
||||||
"""
|
"""
|
||||||
pipe = subprocess.PIPE
|
pipe = subprocess.PIPE
|
||||||
proc = subprocess.Popen([cmd], shell=True, stdin=pipe, stdout=pipe, stderr=pipe, close_fds=True)
|
proc = subprocess.Popen([cmd], shell=True, stdin=pipe, stdout=pipe,
|
||||||
|
stderr=pipe, close_fds=True)
|
||||||
proc.wait()
|
proc.wait()
|
||||||
err = proc.stderr.read()
|
err = proc.stderr.read()
|
||||||
if err:
|
if err:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user