summaryrefslogtreecommitdiffstats
path: root/plugins
diff options
context:
space:
mode:
authorRick Harris <rick.harris@rackspace.com>2011-01-06 23:46:36 -0600
committerRick Harris <rick.harris@rackspace.com>2011-01-06 23:46:36 -0600
commitecb6363533251cb7f1c22e87e36db8fcf6d64eb1 (patch)
treecc5cc1fc721fa1eab638861c8b6c37883ec38e4d /plugins
parent8de96296dfb22d0e6c491fcaf072210dfbaa67e8 (diff)
parente33102d23ec8f357c08e2583f8d9e3c1753bab4d (diff)
downloadnova-ecb6363533251cb7f1c22e87e36db8fcf6d64eb1.tar.gz
nova-ecb6363533251cb7f1c22e87e36db8fcf6d64eb1.tar.xz
nova-ecb6363533251cb7f1c22e87e36db8fcf6d64eb1.zip
Merging trunk
Diffstat (limited to 'plugins')
-rwxr-xr-xplugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py28
-rwxr-xr-xplugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py180
2 files changed, 199 insertions, 9 deletions
diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py
index 2d323a016..8e7a829d5 100755
--- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py
@@ -45,6 +45,7 @@ class PluginError(Exception):
def __init__(self, *args):
Exception.__init__(self, *args)
+
class ArgumentError(PluginError):
"""Raised when required arguments are missing, argument values are invalid,
or incompatible arguments are given.
@@ -67,6 +68,7 @@ def ignore_failure(func, *args, **kwargs):
ARGUMENT_PATTERN = re.compile(r'^[a-zA-Z0-9_:\.\-,]+$')
+
def validate_exists(args, key, default=None):
"""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
@@ -76,20 +78,24 @@ def validate_exists(args, key, default=None):
"""
if key in args:
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]):
- 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] == '-':
- 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]
elif default is not None:
return default
else:
raise ArgumentError('Argument %s is required.' % key)
+
def validate_bool(args, key, default=None):
- """Validates that a string argument to a RPC method call is a boolean string,
- with an optional default value in case it does not exist.
+ """Validates that a string argument to a RPC method call is a boolean
+ string, with an optional default value in case it does not exist.
Returns the python boolean value.
"""
@@ -99,7 +105,9 @@ def validate_bool(args, key, default=None):
elif value.lower() == 'false':
return False
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):
"""Validates that a freeform string argument to a RPC method call is given.
@@ -110,6 +118,7 @@ def exists(args, key):
else:
raise ArgumentError('Argument %s is required.' % key)
+
def optional(args, key):
"""If the given key is in args, return the corresponding value, otherwise
return None"""
@@ -122,13 +131,14 @@ def get_this_host(session):
def get_domain_0(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]
def create_vdi(session, sr_ref, name_label, virtual_size, read_only):
vdi_ref = session.xenapi.VDI.create(
- { 'name_label': name_label,
+ {'name_label': name_label,
'name_description': '',
'SR': sr_ref,
'virtual_size': str(virtual_size),
@@ -138,7 +148,7 @@ def create_vdi(session, sr_ref, name_label, virtual_size, read_only):
'xenstore_data': {},
'other_config': {},
'sm_config': {},
- 'tags': [] })
+ 'tags': []})
logging.debug('Created VDI %s (%s, %s, %s) on %s.', vdi_ref, name_label,
virtual_size, read_only, sr_ref)
return vdi_ref
diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py
new file mode 100755
index 000000000..695bf3448
--- /dev/null
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py
@@ -0,0 +1,180 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2010 Citrix Systems, Inc.
+# Copyright 2010 OpenStack LLC.
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+#
+# XenAPI plugin for reading/writing information to xenstore
+#
+
+try:
+ import json
+except ImportError:
+ import simplejson as json
+import subprocess
+
+import XenAPIPlugin
+
+import pluginlib_nova as pluginlib
+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):
+ """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
+ optinally include a key 'ignore_missing_path'; if this is present
+ and boolean True, attempting to read a non-existent path will return
+ the string 'None' instead of raising an exception.
+ """
+ cmd = "xenstore-read /local/domain/%(dom_id)s/%(path)s" % arg_dict
+ try:
+ return _run_command(cmd).rstrip("\n")
+ except pluginlib.PluginError, e:
+ if arg_dict.get("ignore_missing_path", False):
+ cmd = "xenstore-exists /local/domain/%(dom_id)s/%(path)s; echo $?"
+ cmd = cmd % arg_dict
+ ret = _run_command(cmd).strip()
+ # If the path exists, the cmd should return "0"
+ if ret != "0":
+ # No such path, so ignore the error and return the
+ # string 'None', since None can't be marshalled
+ # over RPC.
+ return "None"
+ # Either we shouldn't ignore path errors, or another
+ # error was hit. Re-raise.
+ raise
+
+
+@jsonify
+def write_record(self, arg_dict):
+ """Writes to xenstore at the specified path. If there is information
+ already stored in that location, it is overwritten. As in read_record,
+ the dom_id and path must be specified in the arg_dict; additionally,
+ 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.
+ """
+ cmd = "xenstore-write /local/domain/%(dom_id)s/%(path)s '%(value)s'"
+ cmd = cmd % arg_dict
+ _run_command(cmd)
+ return arg_dict["value"]
+
+
+@jsonify
+def list_records(self, arg_dict):
+ """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
+ path as the key and the stored value as the value. If the path
+ doesn't exist, an empty dict is returned.
+ """
+ cmd = "xenstore-ls /local/domain/%(dom_id)s/%(path)s" % arg_dict
+ cmd = cmd.rstrip("/")
+ try:
+ recs = _run_command(cmd)
+ except pluginlib.PluginError, e:
+ if "No such file or directory" in "%s" % e:
+ # Path doesn't exist.
+ return {}
+ return str(e)
+ raise
+ base_path = arg_dict["path"]
+ paths = _paths_from_ls(recs)
+ ret = {}
+ for path in paths:
+ if base_path:
+ arg_dict["path"] = "%s/%s" % (base_path, path)
+ else:
+ arg_dict["path"] = path
+ rec = read_record(self, arg_dict)
+ try:
+ val = json.loads(rec)
+ except ValueError:
+ val = rec
+ ret[path] = val
+ return ret
+
+
+@jsonify
+def delete_record(self, arg_dict):
+ """Just like it sounds: it removes the record for the specified
+ VM and the specified path from xenstore.
+ """
+ cmd = "xenstore-rm /local/domain/%(dom_id)s/%(path)s" % arg_dict
+ return _run_command(cmd)
+
+
+def _paths_from_ls(recs):
+ """The xenstore-ls command returns a listing that isn't terribly
+ useful. This method cleans that up into a dict with each path
+ as the key, and the associated string as the value.
+ """
+ ret = {}
+ last_nm = ""
+ level = 0
+ path = []
+ ret = []
+ for ln in recs.splitlines():
+ nm, val = ln.rstrip().split(" = ")
+ barename = nm.lstrip()
+ this_level = len(nm) - len(barename)
+ if this_level == 0:
+ ret.append(barename)
+ level = 0
+ path = []
+ elif this_level == level:
+ # child of same parent
+ ret.append("%s/%s" % ("/".join(path), barename))
+ elif this_level > level:
+ path.append(last_nm)
+ ret.append("%s/%s" % ("/".join(path), barename))
+ level = this_level
+ elif this_level < level:
+ path = path[:this_level]
+ ret.append("%s/%s" % ("/".join(path), barename))
+ level = this_level
+ last_nm = barename
+ return ret
+
+
+def _run_command(cmd):
+ """Abstracts out the basics of issuing system commands. If the command
+ returns anything in stderr, a PluginError is raised with that information.
+ Otherwise, the output from stdout is returned.
+ """
+ pipe = subprocess.PIPE
+ proc = subprocess.Popen([cmd], shell=True, stdin=pipe, stdout=pipe,
+ stderr=pipe, close_fds=True)
+ proc.wait()
+ err = proc.stderr.read()
+ if err:
+ raise pluginlib.PluginError(err)
+ return proc.stdout.read()
+
+
+if __name__ == "__main__":
+ XenAPIPlugin.dispatch(
+ {"read_record": read_record,
+ "write_record": write_record,
+ "list_records": list_records,
+ "delete_record": delete_record})