/usr/lib/python2.7/dist-packages/os_xenapi/dom0/etc/xapi.d/plugins/xenhost.py is in python-os-xenapi 0.3.1-0ubuntu1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 | #!/usr/bin/env python
# Copyright 2011 OpenStack Foundation
# 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.
# NOTE: XenServer still only supports Python 2.4 in it's dom0 userspace
# which means the Nova xenapi plugins must use only Python 2.4 features
# TODO(sfinucan): Resolve all 'noqa' items once the above is no longer true
#
# XenAPI plugin for host operations
#
try:
import json
except ImportError:
import simplejson as json
import logging
import re
import sys
import time
import utils
import dom0_pluginlib as pluginlib
import XenAPI
import XenAPIPlugin
try:
import xmlrpclib
except ImportError:
import six.moves.xmlrpc_client as xmlrpclib
pluginlib.configure_logging("xenhost")
host_data_pattern = re.compile(r"\s*(\S+) \([^\)]+\) *: ?(.*)")
config_file_path = "/usr/etc/xenhost.conf"
DEFAULT_TRIES = 23
DEFAULT_SLEEP = 10
def jsonify(fnc):
def wrapper(*args, **kwargs):
return json.dumps(fnc(*args, **kwargs))
return wrapper
class TimeoutError(StandardError):
pass
def _run_command(cmd, cmd_input=None):
"""Wrap utils.run_command to raise PluginError on failure"""
try:
return utils.run_command(cmd, cmd_input=cmd_input)
except utils.SubprocessException, e: # noqa
raise pluginlib.PluginError(e.err)
def _resume_compute(session, compute_ref, compute_uuid):
"""Resume compute node on slave host after pool join.
This has to happen regardless of the success or failure of the join
operation.
"""
try:
# session is valid if the join operation has failed
session.xenapi.VM.start(compute_ref, False, True)
except XenAPI.Failure:
# if session is invalid, e.g. xapi has restarted, then the pool
# join has been successful, wait for xapi to become alive again
for c in range(0, DEFAULT_TRIES):
try:
_run_command(["xe", "vm-start", "uuid=%s" % compute_uuid])
return
except pluginlib.PluginError:
logging.exception('Waited %d seconds for the slave to '
'become available.' % (c * DEFAULT_SLEEP))
time.sleep(DEFAULT_SLEEP)
raise pluginlib.PluginError('Unrecoverable error: the host has '
'not come back for more than %d seconds'
% (DEFAULT_SLEEP * (DEFAULT_TRIES + 1)))
@jsonify
def set_host_enabled(self, arg_dict):
"""Sets this host's ability to accept new instances.
It will otherwise continue to operate normally.
"""
enabled = arg_dict.get("enabled")
if enabled is None:
raise pluginlib.PluginError(
"Missing 'enabled' argument to set_host_enabled")
host_uuid = arg_dict['host_uuid']
if enabled == "true":
result = _run_command(["xe", "host-enable", "uuid=%s" % host_uuid])
elif enabled == "false":
result = _run_command(["xe", "host-disable", "uuid=%s" % host_uuid])
else:
raise pluginlib.PluginError("Illegal enabled status: %s" % enabled)
# Should be empty string
if result:
raise pluginlib.PluginError(result)
# Return the current enabled status
cmd = ["xe", "host-param-get", "uuid=%s" % host_uuid, "param-name=enabled"]
host_enabled = _run_command(cmd)
if host_enabled == "true":
status = "enabled"
else:
status = "disabled"
return {"status": status}
def _write_config_dict(dct):
conf_file = file(config_file_path, "w")
json.dump(dct, conf_file)
conf_file.close()
def _get_config_dict():
"""Returns a dict containing the key/values in the config file.
If the file doesn't exist, it is created, and an empty dict
is returned.
"""
try:
conf_file = file(config_file_path)
config_dct = json.load(conf_file)
conf_file.close()
except IOError:
# File doesn't exist
config_dct = {}
# Create the file
_write_config_dict(config_dct)
return config_dct
@jsonify
def get_config(self, arg_dict):
"""Return the value stored for the specified key, or None if no match."""
conf = _get_config_dict()
params = arg_dict["params"]
try:
dct = json.loads(params)
except Exception:
dct = params
key = dct["key"]
ret = conf.get(key)
if ret is None:
# Can't jsonify None
return "None"
return ret
@jsonify
def set_config(self, arg_dict):
"""Write the specified key/value pair, overwriting any existing value."""
conf = _get_config_dict()
params = arg_dict["params"]
try:
dct = json.loads(params)
except Exception:
dct = params
key = dct["key"]
val = dct["value"]
if val is None:
# Delete the key, if present
conf.pop(key, None)
else:
conf.update({key: val})
_write_config_dict(conf)
def iptables_config(session, args):
# command should be either save or restore
logging.debug("iptables_config:enter")
logging.debug("iptables_config: args=%s", args)
cmd_args = pluginlib.exists(args, 'cmd_args')
logging.debug("iptables_config: cmd_args=%s", cmd_args)
process_input = pluginlib.optional(args, 'process_input')
logging.debug("iptables_config: process_input=%s", process_input)
cmd = json.loads(cmd_args)
cmd = map(str, cmd)
# either execute iptable-save or iptables-restore
# command must be only one of these two
# process_input must be used only with iptables-restore
if len(cmd) > 0 and cmd[0] in ('iptables-save',
'iptables-restore',
'ip6tables-save',
'ip6tables-restore'):
result = _run_command(cmd, process_input)
ret_str = json.dumps(dict(out=result, err=''))
logging.debug("iptables_config:exit")
return ret_str
# else don't do anything and return an error
else:
raise pluginlib.PluginError("Invalid iptables command")
def _ovs_add_patch_port(args):
bridge_name = pluginlib.exists(args, 'bridge_name')
port_name = pluginlib.exists(args, 'port_name')
peer_port_name = pluginlib.exists(args, 'peer_port_name')
cmd_args = ['ovs-vsctl', '--', '--if-exists', 'del-port',
port_name, '--', 'add-port', bridge_name, port_name,
'--', 'set', 'interface', port_name,
'type=patch', 'options:peer=%s' % peer_port_name]
return _run_command(cmd_args)
def _ovs_del_port(args):
bridge_name = pluginlib.exists(args, 'bridge_name')
port_name = pluginlib.exists(args, 'port_name')
cmd_args = ['ovs-vsctl', '--', '--if-exists', 'del-port',
bridge_name, port_name]
return _run_command(cmd_args)
def _ovs_del_br(args):
bridge_name = pluginlib.exists(args, 'bridge_name')
cmd_args = ['ovs-vsctl', '--', '--if-exists',
'del-br', bridge_name]
return _run_command(cmd_args)
def _ovs_set_if_external_id(args):
interface = pluginlib.exists(args, 'interface')
extneral_id = pluginlib.exists(args, 'extneral_id')
value = pluginlib.exists(args, 'value')
cmd_args = ['ovs-vsctl', 'set', 'Interface', interface,
'external-ids:%s=%s' % (extneral_id, value)]
return _run_command(cmd_args)
def _ovs_add_port(args):
bridge_name = pluginlib.exists(args, 'bridge_name')
port_name = pluginlib.exists(args, 'port_name')
cmd_args = ['ovs-vsctl', '--', '--if-exists', 'del-port', port_name,
'--', 'add-port', bridge_name, port_name]
return _run_command(cmd_args)
def _ovs_create_port(args):
bridge = pluginlib.exists(args, 'bridge')
port = pluginlib.exists(args, 'port')
iface_id = pluginlib.exists(args, 'iface-id')
mac = pluginlib.exists(args, 'mac')
status = pluginlib.exists(args, 'status')
cmd_args = ['ovs-vsctl', '--', '--if-exists', 'del-port', port,
'--', 'add-port', bridge, port,
'--', 'set', 'Interface', port,
'external_ids:iface-id=%s' % iface_id,
'external_ids:iface-status=%s' % status,
'external_ids:attached-mac=%s' % mac,
'external_ids:xs-vif-uuid=%s' % iface_id]
return _run_command(cmd_args)
def _ip_link_get_dev(args):
device_name = pluginlib.exists(args, 'device_name')
cmd_args = ['ip', 'link', 'show', device_name]
return _run_command(cmd_args)
def _ip_link_del_dev(args):
device_name = pluginlib.exists(args, 'device_name')
cmd_args = ['ip', 'link', 'delete', device_name]
return _run_command(cmd_args)
def _ip_link_add_veth_pair(args):
dev1_name = pluginlib.exists(args, 'dev1_name')
dev2_name = pluginlib.exists(args, 'dev2_name')
cmd_args = ['ip', 'link', 'add', dev1_name, 'type', 'veth', 'peer',
'name', dev2_name]
return _run_command(cmd_args)
def _ip_link_set_dev(args):
device_name = pluginlib.exists(args, 'device_name')
option = pluginlib.exists(args, 'option')
cmd_args = ['ip', 'link', 'set', device_name, option]
return _run_command(cmd_args)
def _ip_link_set_promisc(args):
device_name = pluginlib.exists(args, 'device_name')
option = pluginlib.exists(args, 'option')
cmd_args = ['ip', 'link', 'set', device_name, 'promisc', option]
return _run_command(cmd_args)
def _brctl_add_br(args):
bridge_name = pluginlib.exists(args, 'bridge_name')
cmd_args = ['brctl', 'addbr', bridge_name]
return _run_command(cmd_args)
def _brctl_del_br(args):
bridge_name = pluginlib.exists(args, 'bridge_name')
cmd_args = ['brctl', 'delbr', bridge_name]
return _run_command(cmd_args)
def _brctl_set_fd(args):
bridge_name = pluginlib.exists(args, 'bridge_name')
fd = pluginlib.exists(args, 'fd')
cmd_args = ['brctl', 'setfd', bridge_name, fd]
return _run_command(cmd_args)
def _brctl_set_stp(args):
bridge_name = pluginlib.exists(args, 'bridge_name')
option = pluginlib.exists(args, 'option')
cmd_args = ['brctl', 'stp', bridge_name, option]
return _run_command(cmd_args)
def _brctl_add_if(args):
bridge_name = pluginlib.exists(args, 'bridge_name')
if_name = pluginlib.exists(args, 'interface_name')
cmd_args = ['brctl', 'addif', bridge_name, if_name]
return _run_command(cmd_args)
def _brctl_del_if(args):
bridge_name = pluginlib.exists(args, 'bridge_name')
if_name = pluginlib.exists(args, 'interface_name')
cmd_args = ['brctl', 'delif', bridge_name, if_name]
return _run_command(cmd_args)
ALLOWED_NETWORK_CMDS = {
# allowed cmds to config OVS bridge
'ovs_add_patch_port': _ovs_add_patch_port,
'ovs_add_port': _ovs_add_port,
'ovs_create_port': _ovs_create_port,
'ovs_del_port': _ovs_del_port,
'ovs_del_br': _ovs_del_br,
'ovs_set_if_external_id': _ovs_set_if_external_id,
'ip_link_add_veth_pair': _ip_link_add_veth_pair,
'ip_link_del_dev': _ip_link_del_dev,
'ip_link_get_dev': _ip_link_get_dev,
'ip_link_set_dev': _ip_link_set_dev,
'ip_link_set_promisc': _ip_link_set_promisc,
'brctl_add_br': _brctl_add_br,
'brctl_add_if': _brctl_add_if,
'brctl_del_br': _brctl_del_br,
'brctl_del_if': _brctl_del_if,
'brctl_set_fd': _brctl_set_fd,
'brctl_set_stp': _brctl_set_stp
}
def network_config(session, args):
"""network config functions"""
cmd = pluginlib.exists(args, 'cmd')
if not isinstance(cmd, basestring):
msg = "invalid command '%s'" % str(cmd)
raise pluginlib.PluginError(msg)
return
if cmd not in ALLOWED_NETWORK_CMDS:
msg = "Dom0 execution of '%s' is not permitted" % cmd
raise pluginlib.PluginError(msg)
return
cmd_args = pluginlib.exists(args, 'args')
return ALLOWED_NETWORK_CMDS[cmd](cmd_args)
def _power_action(action, arg_dict):
# Host must be disabled first
host_uuid = arg_dict['host_uuid']
result = _run_command(["xe", "host-disable", "uuid=%s" % host_uuid])
if result:
raise pluginlib.PluginError(result)
# All running VMs must be shutdown
result = _run_command(["xe", "vm-shutdown", "--multiple",
"resident-on=%s" % host_uuid])
if result:
raise pluginlib.PluginError(result)
cmds = {"reboot": "host-reboot",
"startup": "host-power-on",
"shutdown": "host-shutdown"}
result = _run_command(["xe", cmds[action], "uuid=%s" % host_uuid])
# Should be empty string
if result:
raise pluginlib.PluginError(result)
return {"power_action": action}
@jsonify
def host_reboot(self, arg_dict):
"""Reboots the host."""
return _power_action("reboot", arg_dict)
@jsonify
def host_shutdown(self, arg_dict):
"""Reboots the host."""
return _power_action("shutdown", arg_dict)
@jsonify
def host_start(self, arg_dict):
"""Starts the host.
Currently not feasible, since the host runs on the same machine as
Xen.
"""
return _power_action("startup", arg_dict)
@jsonify
def host_join(self, arg_dict):
"""Join a remote host into a pool.
The pool's master is the host where the plugin is called from. The
following constraints apply:
- The host must have no VMs running, except nova-compute, which
will be shut down (and restarted upon pool-join) automatically,
- The host must have no shared storage currently set up,
- The host must have the same license of the master,
- The host must have the same supplemental packs as the master.
"""
session = XenAPI.Session(arg_dict.get("url"))
session.login_with_password(arg_dict.get("user"),
arg_dict.get("password"))
compute_ref = session.xenapi.VM.get_by_uuid(arg_dict.get('compute_uuid'))
session.xenapi.VM.clean_shutdown(compute_ref)
try:
if arg_dict.get("force", "false") == "false":
session.xenapi.pool.join(arg_dict.get("master_addr"),
arg_dict.get("master_user"),
arg_dict.get("master_pass"))
else:
session.xenapi.pool.join_force(arg_dict.get("master_addr"),
arg_dict.get("master_user"),
arg_dict.get("master_pass"))
finally:
_resume_compute(session, compute_ref, arg_dict.get("compute_uuid"))
@jsonify
def host_data(self, arg_dict):
# Runs the commands on the xenstore host to return the current status
# information.
host_uuid = arg_dict['host_uuid']
resp = _run_command(["xe", "host-param-list", "uuid=%s" % host_uuid])
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)
# Add any config settings
config = _get_config_dict()
ret_dict.update(config)
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
@jsonify
def host_uptime(self, arg_dict):
"""Returns the result of the uptime command on the xenhost."""
return {"uptime": _run_command(['uptime'])}
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["enabled"] = dct.get("enabled", "true") == "true"
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_memory"] = omm
# 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["enabled"] = dct.get("enabled", True)
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", "")
ocd = {}
if oc:
for oc_fld in oc.split("; "):
ock, ocv = strip_kv(oc_fld)
ocd[ock] = ocv
out["host_other-config"] = ocd
capabilities = dct.get("capabilities", "")
out["host_capabilities"] = capabilities.replace(";", "").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", "")
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_cpu_info"] = ocp
# out["host_edition"] = dct.get("edition", "")
# out["host_external-auth-service-name"] = dct.get(
# "external-auth-service-name", "")
return out
def query_gc(session, sr_uuid, vdi_uuid):
result = _run_command(["/opt/xensource/sm/cleanup.py",
"-q", "-u", sr_uuid])
# Example output: "Currently running: True"
return result[19:].strip() == "True"
def get_pci_device_details(session):
"""Returns a string that is a list of pci devices with details.
This string is obtained by running the command lspci. With -vmm option,
it dumps PCI device data in machine readable form. This verbose format
display a sequence of records separated by a blank line. We will also
use option "-n" to get vendor_id and device_id as numeric values and
the "-k" option to get the kernel driver used if any.
"""
return _run_command(["lspci", "-vmmnk"])
def get_pci_type(session, pci_device):
"""Returns the type of the PCI device (type-PCI, type-VF or type-PF).
pci-device -- The address of the pci device
"""
# We need to add the domain if it is missing
if pci_device.count(':') == 1:
pci_device = "0000:" + pci_device
output = _run_command(["ls", "/sys/bus/pci/devices/" + pci_device + "/"])
if "physfn" in output:
return "type-VF"
if "virtfn" in output:
return "type-PF"
return "type-PCI"
if __name__ == "__main__":
# Support both serialized and non-serialized plugin approaches
_, methodname = xmlrpclib.loads(sys.argv[1])
if methodname in ['query_gc', 'get_pci_device_details', 'get_pci_type',
'network_config']:
utils.register_plugin_calls(query_gc,
get_pci_device_details,
get_pci_type,
network_config)
XenAPIPlugin.dispatch(
{"host_data": host_data,
"set_host_enabled": set_host_enabled,
"host_shutdown": host_shutdown,
"host_reboot": host_reboot,
"host_start": host_start,
"host_join": host_join,
"get_config": get_config,
"set_config": set_config,
"iptables_config": iptables_config,
"host_uptime": host_uptime})
|