summaryrefslogtreecommitdiffstats
path: root/plugins
diff options
context:
space:
mode:
authorJustin SB <justin@fathomdb.com>2011-05-19 21:03:15 -0700
committerJustin SB <justin@fathomdb.com>2011-05-19 21:03:15 -0700
commit732eb413cc404ba4c1ad5a2581c5efb864874d3b (patch)
treea7f0aecf632383206e01dc3a4f907cff4fc82bc7 /plugins
parent4ab6962fb7461573119297aa3508f7df8c6efa42 (diff)
parent330b3febe9970a0358cbc145ea88faeb3da121d5 (diff)
downloadnova-732eb413cc404ba4c1ad5a2581c5efb864874d3b.tar.gz
nova-732eb413cc404ba4c1ad5a2581c5efb864874d3b.tar.xz
nova-732eb413cc404ba4c1ad5a2581c5efb864874d3b.zip
Merged with trunk
Diffstat (limited to 'plugins')
-rwxr-xr-xplugins/xenserver/xenapi/etc/xapi.d/plugins/agent86
-rw-r--r--plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost183
-rwxr-xr-xplugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py39
3 files changed, 281 insertions, 27 deletions
diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent
index 94eaabe73..9e761f264 100755
--- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent
@@ -22,6 +22,8 @@
# XenAPI plugin for reading/writing information to xenstore
#
+import base64
+import commands
try:
import json
except ImportError:
@@ -51,7 +53,6 @@ class TimeoutError(StandardError):
pass
-@jsonify
def key_init(self, arg_dict):
"""Handles the Diffie-Hellman key exchange with the agent to
establish the shared secret key used to encrypt/decrypt sensitive
@@ -66,11 +67,10 @@ def key_init(self, arg_dict):
try:
resp = _wait_for_agent(self, request_id, arg_dict)
except TimeoutError, e:
- raise PluginError("%s" % e)
+ raise PluginError(e)
return resp
-@jsonify
def password(self, arg_dict):
"""Writes a request to xenstore that tells the agent to set
the root password for the given VM. The password should be
@@ -78,7 +78,6 @@ def password(self, arg_dict):
previous call to key_init. The encrypted password value should
be passed as the value for the 'enc_pass' key in arg_dict.
"""
- pub = int(arg_dict["pub"])
enc_pass = arg_dict["enc_pass"]
arg_dict["value"] = json.dumps({"name": "password", "value": enc_pass})
request_id = arg_dict["id"]
@@ -87,7 +86,7 @@ def password(self, arg_dict):
try:
resp = _wait_for_agent(self, request_id, arg_dict)
except TimeoutError, e:
- raise PluginError("%s" % e)
+ raise PluginError(e)
return resp
@@ -102,6 +101,75 @@ def resetnetwork(self, arg_dict):
xenstore.write_record(self, arg_dict)
+@jsonify
+def inject_file(self, arg_dict):
+ """Expects a file path and the contents of the file to be written. Both
+ should be base64-encoded in order to eliminate errors as they are passed
+ through the stack. Writes that information to xenstore for the agent,
+ which will decode the file and intended path, and create it on the
+ instance. The original agent munged both of these into a single entry;
+ the new agent keeps them separate. We will need to test for the new agent,
+ and write the xenstore records to match the agent version. We will also
+ need to test to determine if the file injection method on the agent has
+ been disabled, and raise a NotImplemented error if that is the case.
+ """
+ b64_path = arg_dict["b64_path"]
+ b64_file = arg_dict["b64_file"]
+ request_id = arg_dict["id"]
+ if self._agent_has_method("file_inject"):
+ # New version of the agent. Agent should receive a 'value'
+ # key whose value is a dictionary containing 'b64_path' and
+ # 'b64_file'. See old version below.
+ arg_dict["value"] = json.dumps({"name": "file_inject",
+ "value": {"b64_path": b64_path, "b64_file": b64_file}})
+ elif self._agent_has_method("injectfile"):
+ # Old agent requires file path and file contents to be
+ # combined into one base64 value.
+ raw_path = base64.b64decode(b64_path)
+ raw_file = base64.b64decode(b64_file)
+ new_b64 = base64.b64encode("%s,%s") % (raw_path, raw_file)
+ arg_dict["value"] = json.dumps({"name": "injectfile",
+ "value": new_b64})
+ else:
+ # Either the methods don't exist in the agent, or they
+ # have been disabled.
+ raise NotImplementedError(_("NOT IMPLEMENTED: Agent does not"
+ " support file injection."))
+ arg_dict["path"] = "data/host/%s" % request_id
+ xenstore.write_record(self, arg_dict)
+ try:
+ resp = _wait_for_agent(self, request_id, arg_dict)
+ except TimeoutError, e:
+ raise PluginError(e)
+ return resp
+
+
+def _agent_has_method(self, method):
+ """Check that the agent has a particular method by checking its
+ features. Cache the features so we don't have to query the agent
+ every time we need to check.
+ """
+ try:
+ self._agent_methods
+ except AttributeError:
+ self._agent_methods = []
+ if not self._agent_methods:
+ # Haven't been defined
+ tmp_id = commands.getoutput("uuidgen")
+ dct = {}
+ dct["value"] = json.dumps({"name": "features", "value": ""})
+ dct["path"] = "data/host/%s" % tmp_id
+ xenstore.write_record(self, dct)
+ try:
+ resp = _wait_for_agent(self, tmp_id, dct)
+ except TimeoutError, e:
+ raise PluginError(e)
+ response = json.loads(resp)
+ # The agent returns a comma-separated list of methods.
+ self._agent_methods = response.split(",")
+ return method in self._agent_methods
+
+
def _wait_for_agent(self, request_id, arg_dict):
"""Periodically checks xenstore for a response from the agent.
The request is always written to 'data/host/{id}', and
@@ -119,9 +187,8 @@ def _wait_for_agent(self, request_id, arg_dict):
# First, delete the request record
arg_dict["path"] = "data/host/%s" % request_id
xenstore.delete_record(self, arg_dict)
- raise TimeoutError(
- "TIMEOUT: No response from agent within %s seconds." %
- AGENT_TIMEOUT)
+ raise TimeoutError(_("TIMEOUT: No response from agent within"
+ " %s seconds.") % AGENT_TIMEOUT)
ret = xenstore.read_record(self, arg_dict)
# Note: the response for None with be a string that includes
# double quotes.
@@ -136,4 +203,5 @@ if __name__ == "__main__":
XenAPIPlugin.dispatch(
{"key_init": key_init,
"password": password,
- "resetnetwork": resetnetwork})
+ "resetnetwork": resetnetwork,
+ "inject_file": inject_file})
diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost
new file mode 100644
index 000000000..a8428e841
--- /dev/null
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost
@@ -0,0 +1,183 @@
+#!/usr/bin/env python
+
+# Copyright 2011 OpenStack LLC.
+# Copyright 2011 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 os
+import random
+import re
+import subprocess
+import tempfile
+import time
+
+import XenAPIPlugin
+
+from pluginlib_nova import *
+configure_logging("xenhost")
+
+host_data_pattern = re.compile(r"\s*(\S+) \([^\)]+\) *: ?(.*)")
+
+
+def jsonify(fnc):
+ def wrapper(*args, **kwargs):
+ return json.dumps(fnc(*args, **kwargs))
+ return wrapper
+
+
+class TimeoutError(StandardError):
+ pass
+
+
+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()
+
+
+@jsonify
+def host_data(self, arg_dict):
+ """Runs the commands on the xenstore host to return the current status
+ information.
+ """
+ cmd = "xe host-list | grep uuid"
+ resp = _run_command(cmd)
+ host_uuid = resp.split(":")[-1].strip()
+ cmd = "xe host-param-list uuid=%s" % host_uuid
+ resp = _run_command(cmd)
+ parsed_data = parse_response(resp)
+ # We have the raw dict of values. Extract those that we need,
+ # and convert the data types as needed.
+ ret_dict = cleanup(parsed_data)
+ return ret_dict
+
+
+def parse_response(resp):
+ data = {}
+ for ln in resp.splitlines():
+ if not ln:
+ continue
+ mtch = host_data_pattern.match(ln.strip())
+ try:
+ k, v = mtch.groups()
+ data[k] = v
+ except AttributeError:
+ # Not a valid line; skip it
+ continue
+ return data
+
+
+def cleanup(dct):
+ """Take the raw KV pairs returned and translate them into the
+ appropriate types, discarding any we don't need.
+ """
+ def safe_int(val):
+ """Integer values will either be string versions of numbers,
+ or empty strings. Convert the latter to nulls.
+ """
+ try:
+ return int(val)
+ except ValueError:
+ return None
+
+ def strip_kv(ln):
+ return [val.strip() for val in ln.split(":", 1)]
+
+ out = {}
+
+# sbs = dct.get("supported-bootloaders", "")
+# out["host_supported-bootloaders"] = sbs.split("; ")
+# out["host_suspend-image-sr-uuid"] = dct.get("suspend-image-sr-uuid", "")
+# out["host_crash-dump-sr-uuid"] = dct.get("crash-dump-sr-uuid", "")
+# out["host_local-cache-sr"] = dct.get("local-cache-sr", "")
+ out["host_memory"] = omm = {}
+ omm["total"] = safe_int(dct.get("memory-total", ""))
+ omm["overhead"] = safe_int(dct.get("memory-overhead", ""))
+ omm["free"] = safe_int(dct.get("memory-free", ""))
+ omm["free-computed"] = safe_int(
+ dct.get("memory-free-computed", ""))
+
+# out["host_API-version"] = avv = {}
+# avv["vendor"] = dct.get("API-version-vendor", "")
+# avv["major"] = safe_int(dct.get("API-version-major", ""))
+# avv["minor"] = safe_int(dct.get("API-version-minor", ""))
+
+ out["host_uuid"] = dct.get("uuid", None)
+ out["host_name-label"] = dct.get("name-label", "")
+ out["host_name-description"] = dct.get("name-description", "")
+# out["host_host-metrics-live"] = dct.get(
+# "host-metrics-live", "false") == "true"
+ out["host_hostname"] = dct.get("hostname", "")
+ out["host_ip_address"] = dct.get("address", "")
+ oc = dct.get("other-config", "")
+ out["host_other-config"] = ocd = {}
+ if oc:
+ for oc_fld in oc.split("; "):
+ ock, ocv = strip_kv(oc_fld)
+ ocd[ock] = ocv
+# out["host_capabilities"] = dct.get("capabilities", "").split("; ")
+# out["host_allowed-operations"] = dct.get(
+# "allowed-operations", "").split("; ")
+# lsrv = dct.get("license-server", "")
+# out["host_license-server"] = ols = {}
+# if lsrv:
+# for lspart in lsrv.split("; "):
+# lsk, lsv = lspart.split(": ")
+# if lsk == "port":
+# ols[lsk] = safe_int(lsv)
+# else:
+# ols[lsk] = lsv
+# sv = dct.get("software-version", "")
+# out["host_software-version"] = osv = {}
+# if sv:
+# for svln in sv.split("; "):
+# svk, svv = strip_kv(svln)
+# osv[svk] = svv
+ cpuinf = dct.get("cpu_info", "")
+ out["host_cpu_info"] = ocp = {}
+ if cpuinf:
+ for cpln in cpuinf.split("; "):
+ cpk, cpv = strip_kv(cpln)
+ if cpk in ("cpu_count", "family", "model", "stepping"):
+ ocp[cpk] = safe_int(cpv)
+ else:
+ ocp[cpk] = cpv
+# out["host_edition"] = dct.get("edition", "")
+# out["host_external-auth-service-name"] = dct.get(
+# "external-auth-service-name", "")
+ return out
+
+
+if __name__ == "__main__":
+ XenAPIPlugin.dispatch(
+ {"host_data": host_data})
diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py
index a35ccd6ab..6c589ed29 100755
--- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py
@@ -56,16 +56,17 @@ def read_record(self, arg_dict):
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
+ cmd = ["xenstore-read", "/local/domain/%(dom_id)s/%(path)s" % arg_dict]
try:
- return _run_command(cmd).rstrip("\n")
+ ret, result = _run_command(cmd)
+ return result.strip()
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()
+ cmd = ["xenstore-exists",
+ "/local/domain/%(dom_id)s/%(path)s" % arg_dict]
+ ret, result = _run_command(cmd)
# If the path exists, the cmd should return "0"
- if ret != "0":
+ if ret != 0:
# No such path, so ignore the error and return the
# string 'None', since None can't be marshalled
# over RPC.
@@ -83,8 +84,9 @@ def write_record(self, arg_dict):
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
+ cmd = ["xenstore-write",
+ "/local/domain/%(dom_id)s/%(path)s" % arg_dict,
+ arg_dict["value"]]
_run_command(cmd)
return arg_dict["value"]
@@ -96,10 +98,10 @@ def list_records(self, arg_dict):
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("/")
+ dirpath = "/local/domain/%(dom_id)s/%(path)s" % arg_dict
+ cmd = ["xenstore-ls", dirpath.rstrip("/")]
try:
- recs = _run_command(cmd)
+ ret, recs = _run_command(cmd)
except pluginlib.PluginError, e:
if "No such file or directory" in "%s" % e:
# Path doesn't exist.
@@ -128,8 +130,9 @@ 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)
+ cmd = ["xenstore-rm", "/local/domain/%(dom_id)s/%(path)s" % arg_dict]
+ ret, result = _run_command(cmd)
+ return result
def _paths_from_ls(recs):
@@ -168,16 +171,16 @@ def _paths_from_ls(recs):
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.
+ Otherwise, a tuple of (return code, stdout data) is returned.
"""
pipe = subprocess.PIPE
- proc = subprocess.Popen([cmd], shell=True, stdin=pipe, stdout=pipe,
- stderr=pipe, close_fds=True)
- proc.wait()
+ proc = subprocess.Popen(cmd, stdin=pipe, stdout=pipe, stderr=pipe,
+ close_fds=True)
+ ret = proc.wait()
err = proc.stderr.read()
if err:
raise pluginlib.PluginError(err)
- return proc.stdout.read()
+ return (ret, proc.stdout.read())
if __name__ == "__main__":