Merge "Fuel plugins: Add support for using neutron vlan network"

This commit is contained in:
Jenkins 2016-03-24 14:38:10 +00:00 committed by Gerrit Code Review
commit cfa8c609ac
6 changed files with 872 additions and 55 deletions

View File

@ -1,15 +1,14 @@
#!/usr/bin/env python
import ConfigParser
import logging
import netifaces
import os
from logging import debug, info, warning, DEBUG, basicConfig
from subprocess import Popen, PIPE
import yaml
from shutil import rmtree
from tempfile import mkstemp, mkdtemp
import re
from socket import inet_ntoa
from struct import pack
import netifaces
import re
import subprocess
import yaml
ASTUTE_PATH = '/etc/astute.yaml'
@ -17,22 +16,27 @@ ASTUTE_SECTION = 'fuel-plugin-xenserver'
LOG_ROOT = '/var/log/fuel-plugin-xenserver'
LOG_FILE = 'compute_post_deployment.log'
HIMN_IP = '169.254.0.1'
INT_BRIDGE = 'br-int'
XS_PLUGIN_ISO = 'xenserverplugins-liberty.iso'
DIST_PACKAGES_DIR = '/usr/lib/python2.7/dist-packages/'
if not os.path.exists(LOG_ROOT):
os.mkdir(LOG_ROOT)
basicConfig(filename=os.path.join(LOG_ROOT, LOG_FILE), level=DEBUG)
logging.basicConfig(filename=os.path.join(LOG_ROOT, LOG_FILE),
level=logging.DEBUG)
def reportError(err):
warning(err)
logging.warning(err)
raise Exception(err)
def execute(*cmd, **kwargs):
cmd = map(str, cmd)
info(' '.join(cmd))
proc = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
logging.info(' '.join(cmd))
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
if 'prompt' in kwargs:
prompt = kwargs.get('prompt')
@ -45,7 +49,7 @@ def execute(*cmd, **kwargs):
(out, err) = (out.replace('\n', ''), err.replace('\n', ''))
if out:
debug(out)
logging.debug(out)
if proc.returncode is not None and proc.returncode != 0:
reportError(err)
@ -91,13 +95,13 @@ def astute_get(dct, keys, default=None, fail_if_missing=True):
def get_options(astute, astute_section):
"""Return username and password filled in plugin."""
if not astute_section in astute:
if astute_section not in astute:
reportError('%s not found' % astute_section)
options = astute[astute_section]
info('username: {username}'.format(**options))
info('password: {password}'.format(**options))
info('install_xapi: {install_xapi}'.format(**options))
logging.info('username: {username}'.format(**options))
logging.info('password: {password}'.format(**options))
logging.info('install_xapi: {install_xapi}'.format(**options))
return options['username'], options['password'], \
options['install_xapi']
@ -112,8 +116,8 @@ def get_endpoints(astute):
endpoints[k]['IP'][0]
) for k in endpoints])
info('storage network: {storage}'.format(**endpoints))
info('mgmt network: {mgmt}'.format(**endpoints))
logging.info('storage network: {storage}'.format(**endpoints))
logging.info('mgmt network: {mgmt}'.format(**endpoints))
return endpoints
@ -128,7 +132,7 @@ def init_eth():
himn_mac = execute(
'xenstore-read',
'/local/domain/%s/vm-data/himn_mac' % domid)
info('himn_mac: %s' % himn_mac)
logging.info('himn_mac: %s' % himn_mac)
_mac = lambda eth: \
netifaces.ifaddresses(eth).get(netifaces.AF_LINK)[0]['addr']
@ -137,7 +141,7 @@ def init_eth():
reportError('Cannot find eth matches himn_mac')
eth = eths[0]
info('himn_eth: %s' % eth)
logging.info('himn_eth: %s' % eth)
ip = netifaces.ifaddresses(eth).get(netifaces.AF_INET)
@ -149,7 +153,7 @@ def init_eth():
'post-up route del default dev {eth}').format(eth=eth)
with open(fname, 'w') as f:
f.write(s)
info('%s created' % fname)
logging.info('%s created' % fname)
execute('ifdown', eth)
execute('ifup', eth)
ip = netifaces.ifaddresses(eth).get(netifaces.AF_INET)
@ -158,7 +162,7 @@ def init_eth():
himn_local = ip[0]['addr']
himn_xs = '.'.join(himn_local.split('.')[:-1] + ['1'])
if HIMN_IP == himn_xs:
info('himn_local: %s' % himn_local)
logging.info('himn_local: %s' % himn_local)
return eth, himn_local
reportError('HIMN failed to get IP address from XenServer')
@ -173,23 +177,11 @@ def check_hotfix_exists(himn, username, password, hotfix):
def install_xenapi_sdk():
"""Install XenAPI Python SDK"""
execute('cp', 'XenAPI.py', '/usr/lib/python2.7/dist-packages/')
execute('cp', 'XenAPI.py', DIST_PACKAGES_DIR)
def create_novacompute_conf(himn, username, password, public_ip):
"""Fill nova-compute.conf with HIMN IP and root password. """
template = '\n'.join([
'[DEFAULT]',
'compute_driver=xenapi.XenAPIDriver',
'force_config_drive=True',
'novncproxy_base_url=https://%s:6080/vnc_auto.html',
'vncserver_proxyclient_address=%s',
'[xenserver]',
'connection_url=http://%s',
'connection_username="%s"',
'connection_password="%s"'
])
mgmt_if = netifaces.ifaddresses('br-mgmt')
if mgmt_if and mgmt_if.get(netifaces.AF_INET) \
and mgmt_if.get(netifaces.AF_INET)[0]['addr']:
@ -197,19 +189,27 @@ def create_novacompute_conf(himn, username, password, public_ip):
else:
reportError('Cannot get IP Address on Management Network')
s = template % (public_ip, mgmt_ip, himn, username, password)
fname = '/etc/nova/nova-compute.conf'
with open(fname, 'w') as f:
f.write(s)
info('%s created' % fname)
def restart_nova_services():
"""Restart nova services"""
execute('stop', 'nova-compute')
execute('start', 'nova-compute')
execute('stop', 'nova-network')
execute('start', 'nova-network')
filename = '/etc/nova/nova-compute.conf'
cf = ConfigParser.ConfigParser()
try:
cf.read(filename)
cf.set('DEFAULT', 'compute_driver', 'xenapi.XenAPIDriver')
cf.set('DEFAULT', 'force_config_drive', 'True')
cf.set('DEFAULT', 'novncproxy_base_url',
'https://%s:6080/vnc_auto.html' % public_ip)
cf.set('DEFAULT', 'vncserver_proxyclient_address', mgmt_ip)
if not cf.has_section('xenserver'):
cf.add_section('xenserver')
cf.set('xenserver', 'connection_url', 'http://%s' % himn)
cf.set('xenserver', 'connection_username', username)
cf.set('xenserver', 'connection_password', password)
cf.set('xenserver', 'vif_driver',
'nova.virt.xenapi.vif.XenAPIOpenVswitchDriver')
cf.set('xenserver', 'ovs_integration_bridge', INT_BRIDGE)
cf.write(open(filename, 'w'))
except Exception:
reportError('Cannot set configurations to %s' % filename)
logging.info('%s created' % filename)
def route_to_compute(endpoints, himn_xs, himn_local, username, password):
@ -238,17 +238,17 @@ def route_to_compute(endpoints, himn_xs, himn_local, username, password):
% ' '.join(params)
ssh(himn_xs, username, password, sh)
else:
info('%s network ip is missing' % endpoint_name)
logging.info('%s network ip is missing' % endpoint_name)
def install_suppack(himn, username, password):
"""Install xapi driver supplemental pack. """
# TODO: check if installed
scp(himn, username, password, '/tmp/', 'novaplugins-kilo.iso')
out = ssh(
# TODO(Johnhua): check if installed
scp(himn, username, password, '/tmp/', XS_PLUGIN_ISO)
ssh(
himn, username, password, 'xe-install-supplemental-pack',
'/tmp/novaplugins-kilo.iso', prompt='Y\n')
ssh(himn, username, password, 'rm', '/tmp/novaplugins-kilo.iso')
'/tmp/%s' % XS_PLUGIN_ISO, prompt='Y\n')
ssh(himn, username, password, 'rm', '/tmp/%s' % XS_PLUGIN_ISO)
def forward_from_himn(eth):
@ -301,6 +301,78 @@ def install_logrotate_script(himn, username, password):
CRONTAB''')
def modify_neutron_rootwrap_conf(himn, username, password):
"""Set xenapi configurations"""
filename = '/etc/neutron/rootwrap.conf'
cf = ConfigParser.ConfigParser()
try:
cf.read(filename)
cf.set('xenapi', 'xenapi_connection_url', 'http://%s' % himn)
cf.set('xenapi', 'xenapi_connection_username', username)
cf.set('xenapi', 'xenapi_connection_password', password)
cf.write(open(filename, 'w'))
except Exception:
reportError("Fail to modify file %s", filename)
logging.info('Modify file %s successfully', filename)
def modify_neutron_ovs_agent_conf(int_br, br_mappings):
filename = '/etc/neutron/plugins/ml2/ml2_conf.ini'
cf = ConfigParser.ConfigParser()
try:
cf.read(filename)
cf.set('agent', 'root_helper',
'neutron-rootwrap-xen-dom0 /etc/neutron/rootwrap.conf')
cf.set('agent', 'root_helper_daemon', '')
cf.set('agent', 'minimize_polling', False)
cf.set('ovs', 'integration_bridge', int_br)
cf.set('ovs', 'bridge_mappings', br_mappings)
cf.write(open(filename, 'w'))
except Exception:
reportError("Fail to modify %s", filename)
logging.info('Modify %s successfully', filename)
def get_private_network_ethX():
# find out bridge which is used for private network
values = astute['network_scheme']['transformations']
for item in values:
if item['action'] == 'add-port' and item['bridge'] == 'br-aux':
return item['name']
def find_bridge_mappings(astute, himn, username, password):
ethX = get_private_network_ethX()
if not ethX:
reportError("Cannot find eth used for private network")
# find the ethX mac in /sys/class/net/ethX/address
fo = open('/sys/class/net/%s/address' % ethX, 'r')
mac = fo.readline()
fo.close()
network_uuid = ssh(himn, username, password,
'xe vif-list params=network-uuid minimal=true MAC=%s' % mac)
bridge = ssh(himn, username, password,
'xe network-param-get param-name=bridge uuid=%s' % network_uuid)
# find physical network name
phynet_setting = astute['quantum_settings']['L2']['phys_nets']
physnet = phynet_setting.keys()[0]
return physnet + ':' + bridge
def restart_services(service_name):
execute('stop', service_name)
execute('start', service_name)
def enable_linux_bridge(himn, username, password):
# When using OVS under XS6.5, it will prevent use of Linux bridge in
# Dom0, but neutron-openvswitch-agent in compute node will use Linux
# bridge, so we remove this restriction here
ssh(himn, username, password, 'rm -f /etc/modprobe.d/blacklist-bridge')
if __name__ == '__main__':
install_xenapi_sdk()
astute = get_astute(ASTUTE_PATH)
@ -318,12 +390,20 @@ if __name__ == '__main__':
endpoints, HIMN_IP, himn_local, username, password)
if install_xapi:
install_suppack(HIMN_IP, username, password)
enable_linux_bridge(HIMN_IP, username, password)
forward_from_himn(himn_eth)
# port forwarding for novnc
forward_port('br-mgmt', himn_eth, HIMN_IP, '80')
create_novacompute_conf(HIMN_IP, username, password, public_ip)
restart_nova_services()
restart_services('nova-compute')
install_logrotate_script(HIMN_IP, username, password)
# neutron-l2-agent in compute node
modify_neutron_rootwrap_conf(HIMN_IP, username, password)
br_mappings = find_bridge_mappings(astute, HIMN_IP,
username, password)
modify_neutron_ovs_agent_conf(INT_BRIDGE, br_mappings)
restart_services('neutron-plugin-openvswitch-agent')

View File

@ -0,0 +1,33 @@
# build-xenserver-suppack.sh
This script is used to build iso for XenServer Dom0 xapi plugin.
It will build both Nova and Neutron Dom0 plugin RPM packages firstly,
and then make them in one ISO.
## usage:
#####./build-xenserver-suppack.sh $xs-version $xs-build $os-git-branch $os-plugin-version
* xs-version: XenServer version which can be used for this plugin
* xs-build: XenServer build number
* os-git-branch: OpenStack branch that's used for building this plugin
* os-plugin-version: OpenStack XenServer Dom0 plguin version
*NOTE: If no input parameters given, default values are used*
*xs-version: 6.5*
*xs-build: 90233c*
*os-git-branch: stable/liberty*
*os-plugin-version: 2015.1*

View File

@ -0,0 +1,140 @@
#!/bin/bash
set -eux
# =============================================
# Usage of this script:
# ./build-xenserver-suppack.sh xs-version xs-build git-branch plugin-version
# or
# ./build-xenserver-suppack.sh
#
# You can provide explict input parameters or you can use the default ones:
# XenServer version
# XenServer build
# OpenStack release branch
# XenServer OpenStack plugin version
THIS_FILE=$(readlink -f $0)
FUELPLUG_UTILS_ROOT=$(dirname $THIS_FILE)
DEPLOYMENT_SCRIPT_ROOT=$(dirname $FUELPLUG_UTILS_ROOT)
cd $FUELPLUG_UTILS_ROOT
rm -rf xenserver-suppack
mkdir -p xenserver-suppack && cd xenserver-suppack
# =============================================
# Configurable items
# xenserver version info
XS_VERSION=${1:-"6.5"}
XS_BUILD=${2:-"90233c"}
# branch info
GITBRANCH=${3:-"stable/liberty"}
# nova and neutron xenserver dom0 plugin version
XS_PLUGIN_VERSION=${4:-"2015.1"}
# OpenStack release
OS_RELEASE=liberty
# repository info
NOVA_GITREPO="https://git.openstack.org/openstack/nova"
NEUTRON_GITREPO="https://git.openstack.org/openstack/neutron"
DDK_ROOT_URL="http://copper.eng.hq.xensource.com/builds/ddk-xs6_2.tgz"
RPM_BUILDER_REPO="https://github.com/citrix-openstack/xenserver-nova-suppack-builder"
# Update system and install dependencies
export DEBIAN_FRONTEND=noninteractive
# =============================================
# Check out rpm packaging repo
rm -rf xenserver-nova-suppack-builder
git clone $RPM_BUILDER_REPO
# =============================================
# Create nova rpm file
rm -rf nova
git clone "$NOVA_GITREPO" nova
cd nova
git fetch origin "$GITBRANCH"
git checkout FETCH_HEAD
# patch xenhost as this file is not merged to liberty
cp $DEPLOYMENT_SCRIPT_ROOT/patchset/xenhost plugins/xenserver/xenapi/etc/xapi.d/plugins/
cd ..
cp -r xenserver-nova-suppack-builder/plugins/xenserver/xenapi/* nova/plugins/xenserver/xenapi/
cd nova/plugins/xenserver/xenapi/contrib
./build-rpm.sh $XS_PLUGIN_VERSION
cd $FUELPLUG_UTILS_ROOT/xenserver-suppack/
RPMFILE=$(find -name "openstack-xen-plugins-*.noarch.rpm" -print)
# =============================================
# Create neutron rpm file
rm -rf neutron
git clone "$NEUTRON_GITREPO" neutron
cd neutron
git fetch origin "$GITBRANCH"
git checkout FETCH_HEAD
# patch netwrap as this file is not merged to liberty
cp $DEPLOYMENT_SCRIPT_ROOT/patchset/netwrap \
neutron/plugins/ml2/drivers/openvswitch/agent/xenapi/etc/xapi.d/plugins/
chmod +x neutron/plugins/ml2/drivers/openvswitch/agent/xenapi/etc/xapi.d/plugins/netwrap
cd ..
cp -r xenserver-nova-suppack-builder/neutron/* \
neutron/neutron/plugins/ml2/drivers/openvswitch/agent/xenapi/
cd neutron/neutron/plugins/ml2/drivers/openvswitch/agent/xenapi/contrib
./build-rpm.sh $XS_PLUGIN_VERSION
cd $FUELPLUG_UTILS_ROOT/xenserver-suppack/
NEUTRON_RPMFILE=$(find -name "openstack-neutron-xen-plugins-*.noarch.rpm" -print)
# =============================================
# Create Supplemental pack
rm -rf suppack
mkdir suppack
DDKROOT=$(mktemp -d)
wget -qO - "$DDK_ROOT_URL" | sudo tar -xzf - -C "$DDKROOT"
sudo mkdir $DDKROOT/mnt/host
sudo mount --bind $(pwd) $DDKROOT/mnt/host
sudo tee $DDKROOT/buildscript.py << EOF
from xcp.supplementalpack import *
from optparse import OptionParser
parser = OptionParser()
parser.add_option('--pdn', dest="product_name")
parser.add_option('--pdv', dest="product_version")
parser.add_option('--bld', dest="build")
parser.add_option('--out', dest="outdir")
(options, args) = parser.parse_args()
xs = Requires(originator='xs', name='main', test='ge',
product='XenServer', version='$XS_VERSION',
build='$XS_BUILD')
setup(originator='xs', name='xenserverplugins-$OS_RELEASE', product='XenServer',
version=options.product_version, build=options.build, vendor='Citrix Systems, Inc.',
description="OpenStack XenServer Plugins", packages=args, requires=[xs],
outdir=options.outdir, output=['iso'])
EOF
sudo chroot $DDKROOT python buildscript.py \
--pdn=xenserverplugins \
--pdv=$OS_RELEASE \
--bld=0 \
--out=/mnt/host/suppack \
/mnt/host/$RPMFILE \
/mnt/host/$NEUTRON_RPMFILE
# Cleanup
sudo umount $DDKROOT/mnt/host
sudo rm -rf "$DDKROOT"

View File

@ -0,0 +1,82 @@
#!/usr/bin/env python
# Copyright 2012 OpenStack Foundation
# Copyright 2012 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 executing network commands (ovs, iptables, etc) on dom0
# Changes in this file merged post liberty, CommitID:
# b0cef88866db3d325974b1691ac3e1030144ee19
#
import gettext
gettext.install('neutron', unicode=1)
try:
import json
except ImportError:
import simplejson as json
import subprocess
import XenAPIPlugin
ALLOWED_CMDS = [
'ip',
'ipset',
'iptables-save',
'iptables-restore',
'ip6tables-save',
'ip6tables-restore',
'sysctl',
# NOTE(yamamoto): of_interface=native doesn't use ovs-ofctl
'ovs-ofctl',
'ovs-vsctl',
'ovsdb-client',
]
class PluginError(Exception):
"""Base Exception class for all plugin errors."""
def __init__(self, *args):
Exception.__init__(self, *args)
def _run_command(cmd, cmd_input):
"""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=False, stdin=pipe, stdout=pipe,
stderr=pipe, close_fds=True)
(out, err) = proc.communicate(cmd_input)
if proc.returncode != 0:
raise PluginError(err)
return out
def run_command(session, args):
cmd = json.loads(args.get('cmd'))
if cmd and cmd[0] not in ALLOWED_CMDS:
msg = _("Dom0 execution of '%s' is not permitted") % cmd[0]
raise PluginError(msg)
result = _run_command(cmd, json.loads(args.get('cmd_input', 'null')))
return json.dumps(result)
if __name__ == "__main__":
XenAPIPlugin.dispatch({"run_command": run_command})

View File

@ -0,0 +1,482 @@
#!/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
#
# XenAPI plugin for host operations
# Changes in this file will be merged post liberty
# OVS interim bridge: https://review.openstack.org/#/c/242846/29
# Neutron security group: https://review.openstack.org/#/c/251271/10
#
try:
import json
except ImportError:
import simplejson as json
import logging
import re
import sys
import time
import utils
import pluginlib_nova as pluginlib
import XenAPI
import XenAPIPlugin
try:
import xmlrpclib
except ImportError:
import six.moves.xmlrpc_client as xmlrpclib
pluginlib.configure_logging("xenhost")
_ = pluginlib._
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:
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, e:
# 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 xrange(0, DEFAULT_TRIES):
try:
_run_command(["xe", "vm-start", "uuid=%s" % compute_uuid])
return
except pluginlib.PluginError, e:
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, e:
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, e:
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:
# else don't do anything and return an error
raise pluginlib.PluginError(_("Invalid iptables command"))
def network_config(session, args):
# function to config OVS bridge and Linux bridge
ALLOWED_CMDS = [
'ovs-vsctl',
'brctl',
'ip'
]
cmd = json.loads(args.get('cmd'))
if cmd is None or cmd == []:
msg = _("empty command is supplied")
raise pluginlib.PluginError(msg)
if cmd[0] not in ALLOWED_CMDS:
msg = _("Dom0 execution of '%s' is not permitted") % cmd[0]
raise pluginlib.PluginError(msg)
result = _run_command(cmd, json.loads(args.get('cmd_input', 'null')))
return json.dumps(result)
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 whose 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"):
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"
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["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", "")
out["host_other-config"] = ocd = {}
if oc:
for oc_fld in oc.split("; "):
ock, ocv = strip_kv(oc_fld)
ocd[ock] = ocv
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", "")
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
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']:
utils.register_plugin_calls(query_gc,
get_pci_device_details,
get_pci_type)
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,
"network_config": network_config,
"host_uptime": host_uptime})