diff --git a/Makefile b/Makefile index 699d05a..e5d160b 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,8 @@ BRANDING=branding.inc include ${BRANDING} +OPENSTACK_RELEASE:=mitaka + PLUGIN_VERSION:=$(shell ./get_plugin_version.sh ${BRANDING} | cut -d' ' -f1) PLUGIN_REVISION:=$(shell ./get_plugin_version.sh ${BRANDING} | cut -d' ' -f2) @@ -17,8 +19,10 @@ rpm: output/${RPM_NAME} docs: $(DOC_NAMES:%=output/${PLUGIN_NAME}-${PLUGIN_VERSION}-%.pdf) -iso: plugin_source/deployment_scripts/patchset/xenhost - suppack/build-xenserver-suppack.sh +iso: suppack/xenapi-plugins-${OPENSTACK_RELEASE}.iso + +suppack/xenapi-plugins-${OPENSTACK_RELEASE}.iso: plugin_source/deployment_scripts/patchset/xenhost + suppack/build-xenserver-suppack.sh ${OPENSTACK_RELEASE} ${BUILDROOT}/${PLUGIN_NAME}: ${BRANDING} iso mkdir -p ${BUILDROOT}/${PLUGIN_NAME} @@ -44,6 +48,7 @@ ${BUILDROOT}/doc/source ${BUILDROOT}/doc/Makefile: ${BRANDING} output/${RPM_NAME}: ${BUILDROOT}/${PLUGIN_NAME} mkdir -p output + (cd ${BUILDROOT}; which flake8 > /dev/null && flake8 ${PLUGIN_NAME}/deployment_scripts --exclude=XenAPI.py) (cd ${BUILDROOT}; fpb --check ${PLUGIN_NAME}) (cd ${BUILDROOT}; fpb --build ${PLUGIN_NAME}) cp ${BUILDROOT}/${PLUGIN_NAME}/${RPM_NAME} $@ @@ -56,4 +61,4 @@ output/${PLUGIN_NAME}-${PLUGIN_VERSION}-%.pdf: ${BUILDROOT}/doc/build/latex/%.pd cp $^ $@ clean: - rm -rf ${BUILDROOT} output + rm -rf ${BUILDROOT} output suppack/xenapi-plugins-${OPENSTACK_RELEASE}* suppack/build diff --git a/plugin_source/deployment_scripts/compute_post_deployment.py b/plugin_source/deployment_scripts/compute_post_deployment.py index 1860343..2e37c5e 100755 --- a/plugin_source/deployment_scripts/compute_post_deployment.py +++ b/plugin_source/deployment_scripts/compute_post_deployment.py @@ -7,126 +7,25 @@ import os import re from socket import inet_ntoa from struct import pack -import subprocess # nosec import sys -import stat -import yaml +import utils +from utils import HIMN_IP -XS_RSA = '/root/.ssh/xs_rsa' -ASTUTE_PATH = '/etc/astute.yaml' -ASTUTE_SECTION = '@PLUGIN_NAME@' -LOG_ROOT = '/var/log/@PLUGIN_NAME@' -LOG_FILE = 'compute_post_deployment.log' -HIMN_IP = '169.254.0.1' +LOG_FILE = os.path.join(utils.LOG_ROOT, 'compute_post_deployment.log') INT_BRIDGE = 'br-int' XS_PLUGIN_ISO = 'xenapi-plugins-mitaka.iso' DIST_PACKAGES_DIR = '/usr/lib/python2.7/dist-packages/' -PLATFORM_VERSION = '1.9' -if not os.path.exists(LOG_ROOT): - os.mkdir(LOG_ROOT) +if not os.path.exists(utils.LOG_ROOT): + os.mkdir(utils.LOG_ROOT) -logging.basicConfig(filename=os.path.join(LOG_ROOT, LOG_FILE), +logging.basicConfig(filename=LOG_FILE, level=logging.DEBUG) -def reportError(err): - logging.error(err) - raise Exception(err) - - -def execute(*cmd, **kwargs): - cmd = map(str, cmd) - _env = kwargs.get('env') - env_prefix = '' - if _env: - env_prefix = ''.join(['%s=%s ' % (k, _env[k]) for k in _env]) - - env = dict(os.environ) - env.update(_env) - else: - env = None - logging.info(env_prefix + ' '.join(cmd)) - proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, # nosec - stderr=subprocess.PIPE, env=env) - - if 'prompt' in kwargs: - prompt = kwargs.get('prompt') - proc.stdout.flush() - (out, err) = proc.communicate(prompt) - else: - out = proc.stdout.readlines() - err = proc.stderr.readlines() - (out, err) = map(' '.join, [out, err]) - - # Both if/else need to deal with "\n" scenario - (out, err) = (out.replace('\n', ''), err.replace('\n', '')) - - if out: - logging.debug(out) - if err: - logging.error(err) - - if proc.returncode is not None and proc.returncode != 0: - raise Exception(err) - - return out - - -def ssh(host, username, *cmd, **kwargs): - cmd = map(str, cmd) - - return execute('ssh', '-i', XS_RSA, - '-o', 'StrictHostKeyChecking=no', - '%s@%s' % (username, host), *cmd, - prompt=kwargs.get('prompt')) - - -def scp(host, username, target_path, filename): - return execute('scp', '-i', XS_RSA, - '-o', 'StrictHostKeyChecking=no', filename, - '%s@%s:%s' % (username, host, target_path)) - - -def get_astute(astute_path): - """Return the root object read from astute.yaml""" - if not os.path.exists(astute_path): - reportError('%s not found' % astute_path) - with open(astute_path) as f: - astute = yaml.safe_load(f) - return astute - - -def astute_get(dct, keys, default=None, fail_if_missing=True): - """A safe dictionary getter""" - for key in keys: - if key in dct: - dct = dct[key] - else: - if fail_if_missing: - reportError('Value of "%s" is missing' % key) - return default - return dct - - -def get_options(astute, astute_section): - """Return username and password filled in plugin.""" - if astute_section not in astute: - reportError('%s not found' % astute_section) - - options = astute[astute_section] - 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'] - - def get_endpoints(astute): - """Return the IP addresses of the endpoints connected to - storage/mgmt network. - """ + """Return the IP addresses of the storage/mgmt endpoints.""" endpoints = astute['network_scheme']['endpoints'] endpoints = dict([( k.replace('br-', ''), @@ -138,56 +37,9 @@ def get_endpoints(astute): return endpoints -def init_eth(): - """Initialize the net interface connected to HIMN - - Returns: - the IP addresses of local host and hypervisor. - """ - - domid = execute('xenstore-read', 'domid') - himn_mac = execute( - 'xenstore-read', - '/local/domain/%s/vm-data/himn_mac' % domid) - logging.info('himn_mac: %s' % himn_mac) - - _mac = lambda eth: \ - netifaces.ifaddresses(eth).get(netifaces.AF_LINK)[0]['addr'] - eths = [eth for eth in netifaces.interfaces() if _mac(eth) == himn_mac] - if len(eths) != 1: - reportError('Cannot find eth matches himn_mac') - - eth = eths[0] - logging.info('himn_eth: %s' % eth) - - ip = netifaces.ifaddresses(eth).get(netifaces.AF_INET) - - if not ip: - execute('dhclient', eth) - fname = '/etc/network/interfaces.d/ifcfg-' + eth - s = ('auto {eth}\n' - 'iface {eth} inet dhcp\n' - 'post-up route del default dev {eth}').format(eth=eth) - with open(fname, 'w') as f: - f.write(s) - logging.info('%s created' % fname) - execute('ifdown', eth) - execute('ifup', eth) - ip = netifaces.ifaddresses(eth).get(netifaces.AF_INET) - - if ip: - himn_local = ip[0]['addr'] - himn_xs = '.'.join(himn_local.split('.')[:-1] + ['1']) - if HIMN_IP == himn_xs: - logging.info('himn_local: %s' % himn_local) - return eth, himn_local - - reportError('HIMN failed to get IP address from Hypervisor') - - def install_xenapi_sdk(): """Install XenAPI Python SDK""" - execute('cp', 'XenAPI.py', DIST_PACKAGES_DIR) + utils.execute('cp', 'XenAPI.py', DIST_PACKAGES_DIR) def create_novacompute_conf(himn, username, password, public_ip, services_ssl): @@ -197,7 +49,7 @@ def create_novacompute_conf(himn, username, password, public_ip, services_ssl): and mgmt_if.get(netifaces.AF_INET)[0]['addr']: mgmt_ip = mgmt_if.get(netifaces.AF_INET)[0]['addr'] else: - reportError('Cannot get IP Address on Management Network') + utils.reportError('Cannot get IP Address on Management Network') filename = '/etc/nova/nova-compute.conf' cf = ConfigParser.ConfigParser() @@ -221,24 +73,31 @@ def create_novacompute_conf(himn, username, password, public_ip, services_ssl): with open(filename, 'w') as configfile: cf.write(configfile) except Exception: - reportError('Cannot set configurations to %s' % filename) + utils.reportError('Cannot set configurations to %s' % filename) logging.info('%s created' % filename) def route_to_compute(endpoints, himn_xs, himn_local, username): """Route storage/mgmt requests to compute nodes. """ - out = ssh(himn_xs, username, 'route', '-n') - _net = lambda ip: '.'.join(ip.split('.')[:-1] + ['0']) - _mask = lambda cidr: inet_ntoa(pack( - '>I', 0xffffffff ^ (1 << 32 - int(cidr)) - 1)) - _routed = lambda net, mask, gw: re.search(r'%s\s+%s\s+%s\s+' % ( - net.replace('.', r'\.'), - gw.replace('.', r'\.'), - mask - ), out) - ssh(himn_xs, username, - 'printf "#!/bin/bash\nsleep 5\n" > /etc/udev/scripts/reroute.sh') + def _net(ip): + return '.'.join(ip.split('.')[:-1] + ['0']) + + def _mask(cidr): + return inet_ntoa(pack('>I', 0xffffffff ^ (1 << 32 - int(cidr)) - 1)) + + def _routed(net, mask, gw): + return re.search(r'%s\s+%s\s+%s\s+' % ( + net.replace('.', r'\.'), + gw.replace('.', r'\.'), + mask + ), out) + + out = utils.ssh(himn_xs, username, 'route', '-n') + + utils.ssh(himn_xs, username, + ('printf "#!/bin/bash\nsleep 5\n" >' + '/etc/udev/scripts/reroute.sh')) endpoint_names = ['storage', 'mgmt'] for endpoint_name in endpoint_names: endpoint = endpoints.get(endpoint_name) @@ -248,79 +107,82 @@ def route_to_compute(endpoints, himn_xs, himn_local, username): if not _routed(net, mask, himn_local): params = ['route', 'add', '-net', '"%s"' % net, 'netmask', '"%s"' % mask, 'gw', himn_local] - ssh(himn_xs, username, *params) + utils.ssh(himn_xs, username, *params) # Always add the route to the udev, even if it's currently active cmd = ( - "printf 'if !(/sbin/route -n | /bin/grep -q -F \"{net}\"); then\n" - "/sbin/route add -net \"{net}\" netmask \"{mask}\" gw {himn_local};\n" + "printf 'if !(/sbin/route -n | /bin/grep -q -F \"{net}\"); " + "then\n" + "/sbin/route add -net \"{net}\" netmask " + "\"{mask}\" gw {himn_local};\n" "fi\n' >> /etc/udev/scripts/reroute.sh" ) cmd = cmd.format(net=net, mask=mask, himn_local=himn_local) - ssh(himn_xs, username, cmd) + utils.ssh(himn_xs, username, cmd) else: logging.info('%s network ip is missing' % endpoint_name) - ssh(himn_xs, username, 'chmod +x /etc/udev/scripts/reroute.sh') - ssh(himn_xs, username, ('echo \'SUBSYSTEM=="net" ACTION=="add" ' - 'KERNEL=="xenapi" RUN+="/etc/udev/scripts/reroute.sh"\' ' - '> /etc/udev/rules.d/90-reroute.rules')) + utils.ssh(himn_xs, username, 'chmod +x /etc/udev/scripts/reroute.sh') + utils.ssh(himn_xs, username, + ('echo \'SUBSYSTEM=="net" ACTION=="add" ' + 'KERNEL=="xenapi" RUN+="/etc/udev/scripts/reroute.sh"\' ' + '> /etc/udev/rules.d/90-reroute.rules')) def install_suppack(himn, username): """Install xapi driver supplemental pack. """ - tmp = ssh(himn, username, 'mktemp', '-d') - scp(himn, username, tmp, XS_PLUGIN_ISO) - ssh(himn, username, 'xe-install-supplemental-pack', tmp + '/' + XS_PLUGIN_ISO, - prompt='Y\n') - ssh(himn, username, 'rm', tmp, '-rf') + tmp = utils.ssh(himn, username, 'mktemp', '-d') + utils.scp(himn, username, tmp, XS_PLUGIN_ISO) + utils.ssh(himn, username, 'xe-install-supplemental-pack', + tmp + '/' + XS_PLUGIN_ISO, prompt='Y\n') + utils.ssh(himn, username, 'rm', tmp, '-rf') def forward_from_himn(eth): """Forward packets from HIMN to storage/mgmt network. """ - execute('sed', '-i', 's/#net.ipv4.ip_forward/net.ipv4.ip_forward/g', - '/etc/sysctl.conf') - execute('sysctl', '-p', '/etc/sysctl.conf') + utils.execute('sed', '-i', 's/#net.ipv4.ip_forward/net.ipv4.ip_forward/g', + '/etc/sysctl.conf') + utils.execute('sysctl', '-p', '/etc/sysctl.conf') endpoint_names = ['br-storage', 'br-mgmt'] for endpoint_name in endpoint_names: - execute('iptables', '-t', 'nat', '-A', 'POSTROUTING', - '-o', endpoint_name, '-j', 'MASQUERADE') - execute('iptables', '-A', 'FORWARD', - '-i', endpoint_name, '-o', eth, - '-m', 'state', '--state', 'RELATED,ESTABLISHED', - '-j', 'ACCEPT') - execute('iptables', '-A', 'FORWARD', - '-i', eth, '-o', endpoint_name, - '-j', 'ACCEPT') + utils.execute('iptables', '-t', 'nat', '-A', 'POSTROUTING', + '-o', endpoint_name, '-j', 'MASQUERADE') + utils.execute('iptables', '-A', 'FORWARD', + '-i', endpoint_name, '-o', eth, + '-m', 'state', '--state', 'RELATED,ESTABLISHED', + '-j', 'ACCEPT') + utils.execute('iptables', '-A', 'FORWARD', + '-i', eth, '-o', endpoint_name, + '-j', 'ACCEPT') - execute('iptables', '-A', 'INPUT', '-i', eth, '-j', 'ACCEPT') - execute('iptables', '-t', 'filter', '-S', 'FORWARD') - execute('iptables', '-t', 'nat', '-S', 'POSTROUTING') - execute('service', 'iptables-persistent', 'save') + utils.execute('iptables', '-A', 'INPUT', '-i', eth, '-j', 'ACCEPT') + utils.execute('iptables', '-t', 'filter', '-S', 'FORWARD') + utils.execute('iptables', '-t', 'nat', '-S', 'POSTROUTING') + utils.execute('service', 'iptables-persistent', 'save') def forward_port(eth_in, eth_out, target_host, target_port): """Forward packets from eth_in to eth_out on target_host:target_port. """ - execute('iptables', '-t', 'nat', '-A', 'PREROUTING', - '-i', eth_in, '-p', 'tcp', '--dport', target_port, - '-j', 'DNAT', '--to', target_host) - execute('iptables', '-A', 'FORWARD', - '-i', eth_out, '-o', eth_in, - '-m', 'state', '--state', 'RELATED,ESTABLISHED', - '-j', 'ACCEPT') - execute('iptables', '-A', 'FORWARD', - '-i', eth_in, '-o', eth_out, - '-j', 'ACCEPT') + utils.execute('iptables', '-t', 'nat', '-A', 'PREROUTING', + '-i', eth_in, '-p', 'tcp', '--dport', target_port, + '-j', 'DNAT', '--to', target_host) + utils.execute('iptables', '-A', 'FORWARD', + '-i', eth_out, '-o', eth_in, + '-m', 'state', '--state', 'RELATED,ESTABLISHED', + '-j', 'ACCEPT') + utils.execute('iptables', '-A', 'FORWARD', + '-i', eth_in, '-o', eth_out, + '-j', 'ACCEPT') - execute('iptables', '-t', 'filter', '-S', 'FORWARD') - execute('iptables', '-t', 'nat', '-S', 'POSTROUTING') - execute('service', 'iptables-persistent', 'save') + utils.execute('iptables', '-t', 'filter', '-S', 'FORWARD') + utils.execute('iptables', '-t', 'nat', '-S', 'POSTROUTING') + utils.execute('service', 'iptables-persistent', 'save') def install_logrotate_script(himn, username): "Install console logrotate script" - scp(himn, username, '/root/', 'rotate_xen_guest_logs.sh') - ssh(himn, username, 'mkdir -p /var/log/xen/guest') - ssh(himn, username, '''crontab - << CRONTAB + utils.scp(himn, username, '/root/', 'rotate_xen_guest_logs.sh') + utils.ssh(himn, username, 'mkdir -p /var/log/xen/guest') + utils.ssh(himn, username, '''crontab - << CRONTAB * * * * * /root/rotate_xen_guest_logs.sh CRONTAB''') @@ -337,7 +199,7 @@ def modify_neutron_rootwrap_conf(himn, username, password): with open(filename, 'w') as configfile: cf.write(configfile) except Exception: - reportError("Fail to modify file %s", filename) + utils.reportError("Fail to modify file %s", filename) logging.info('Modify file %s successfully', filename) @@ -355,7 +217,7 @@ def modify_neutron_ovs_agent_conf(int_br, br_mappings): with open(filename, 'w') as configfile: cf.write(configfile) except Exception: - reportError("Fail to modify %s", filename) + utils.reportError("Fail to modify %s", filename) logging.info('Modify %s successfully', filename) @@ -374,18 +236,21 @@ def get_private_network_ethX(): if item['action'] == 'add-port' and item['bridge'] == 'br-ex': return item['name'] + def find_bridge_mappings(astute, himn, username): ethX = get_private_network_ethX() if not ethX: - reportError("Cannot find eth used for private network") + utils.reportError("Cannot find eth used for private network") # find the ethX mac in /sys/class/net/ethX/address with open('/sys/class/net/%s/address' % ethX, 'r') as fo: mac = fo.readline() - network_uuid = ssh(himn, username, - 'xe vif-list params=network-uuid minimal=true MAC=%s' % mac) - bridge = ssh(himn, username, - 'xe network-param-get param-name=bridge uuid=%s' % network_uuid) + network_uuid = utils.ssh(himn, username, + ('xe vif-list params=network-uuid ' + 'minimal=true MAC=%s') % mac) + bridge = utils.ssh(himn, username, + ('xe network-param-get param-name=bridge ' + 'uuid=%s') % network_uuid) # find physical network name phynet_setting = astute['quantum_settings']['L2']['phys_nets'] @@ -394,33 +259,36 @@ def find_bridge_mappings(astute, himn, username): def restart_services(service_name): - execute('stop', service_name) - execute('start', service_name) + utils.execute('stop', service_name) + utils.execute('start', service_name) def enable_linux_bridge(himn, username): # 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, 'rm -f /etc/modprobe.d/blacklist-bridge*') + utils.ssh(himn, username, 'rm -f /etc/modprobe.d/blacklist-bridge*') def patch_ceilometer(): - """ - Add patches which are not MOS with order: + """Add patches which are not merged to upstream + + Order of patches applied: ceilometer-poll-cpu-util.patch """ patchset_dir = sys.path[0] patchfile_list = [ - '%s/patchset/ceilometer-poll-cpu-util.patch' % patchset_dir, - ] + '%s/patchset/ceilometer-poll-cpu-util.patch' % patchset_dir, + ] for patch_file in patchfile_list: - execute('patch', '-d', DIST_PACKAGES_DIR, '-p1', '-i', patch_file) + utils.execute('patch', '-d', DIST_PACKAGES_DIR, '-p1', '-i', + patch_file) def patch_compute_xenapi(): - """ - Add patches which are not merged to upstream with order: + """Add patches which are not merged to upstream + + Order of patches applied: support-disable-image-cache.patch speed-up-config-drive.patch ovs-interim-bridge.patch @@ -428,36 +296,36 @@ def patch_compute_xenapi(): """ patchset_dir = sys.path[0] patchfile_list = [ - '%s/patchset/support-disable-image-cache.patch' % patchset_dir, - '%s/patchset/speed-up-config-drive.patch' % patchset_dir, - '%s/patchset/ovs-interim-bridge.patch' % patchset_dir, - '%s/patchset/neutron-security-group.patch' % patchset_dir - ] + '%s/patchset/support-disable-image-cache.patch' % patchset_dir, + '%s/patchset/speed-up-config-drive.patch' % patchset_dir, + '%s/patchset/ovs-interim-bridge.patch' % patchset_dir, + '%s/patchset/neutron-security-group.patch' % patchset_dir] for patch_file in patchfile_list: - execute('patch', '-d', DIST_PACKAGES_DIR, '-p1', '-i', patch_file) + utils.execute('patch', '-d', DIST_PACKAGES_DIR, '-p1', '-i', + patch_file) def patch_neutron_ovs_agent(): - """ - MO8's patch is not needed, keep the func here to add conntrack patch later - """ + # MO8's patch is not needed, keep the func here to add + # conntrack patch later pass def reconfig_multipath(): + """Ignore local disks for multipathd + + Change devnode rule from "^hd[a-z]" to "^(hd|xvd)[a-z]" """ - Ignore local disks for multipathd by changing devnode rule from - "^hd[a-z]" to "^(hd|xvd)[a-z]" - """ - execute('sed', '-i', r's/"\^hd\[a-z\]"/"^(hd|xvd)[a-z]"/', '/etc/multipath.conf') - execute('service', 'multipath-tools', 'restart') + utils.execute('sed', '-i', r's/"\^hd\[a-z\]"/"^(hd|xvd)[a-z]"/', + '/etc/multipath.conf') + utils.execute('service', 'multipath-tools', 'restart') def check_and_setup_ceilometer(himn, username, password): """Set xenapi configuration for ceilometer service""" filename = '/etc/ceilometer/ceilometer.conf' if not os.path.exists(filename): - reportError("The file: %s doesn't exist" % filename) + utils.reportError("The file: %s doesn't exist" % filename) return patch_ceilometer() @@ -473,23 +341,23 @@ def check_and_setup_ceilometer(himn, username, password): cf.write(configfile) logging.info('Modify file %s successfully', filename) except Exception: - reportError("Fail to modify file %s", filename) + utils.reportError("Fail to modify file %s", filename) return restart_services('ceilometer-polling') if __name__ == '__main__': install_xenapi_sdk() - astute = get_astute(ASTUTE_PATH) + astute = utils.get_astute() if astute: - username, password, install_xapi = get_options(astute, ASTUTE_SECTION) + username, password, install_xapi = utils.get_options(astute) endpoints = get_endpoints(astute) - himn_eth, himn_local = init_eth() + himn_eth, himn_local = utils.init_eth() - public_ip = astute_get( + public_ip = utils.astute_get( astute, ('network_metadata', 'vips', 'public', 'ipaddr')) - services_ssl = astute_get( + services_ssl = utils.astute_get( astute, ('public_ssl', 'services')) if username and password and endpoints and himn_local: @@ -502,7 +370,8 @@ if __name__ == '__main__': # port forwarding for novnc forward_port('br-mgmt', himn_eth, HIMN_IP, '80') - create_novacompute_conf(HIMN_IP, username, password, public_ip, services_ssl) + create_novacompute_conf(HIMN_IP, username, password, public_ip, + services_ssl) patch_compute_xenapi() restart_services('nova-compute') @@ -518,8 +387,8 @@ if __name__ == '__main__': reconfig_multipath() # Add xenapi specific setup for ceilometer if service is enabled. - is_ceilometer_enabled = astute_get(astute, - ('ceilometer', 'enabled')) + is_ceilometer_enabled = utils.astute_get(astute, + ('ceilometer', 'enabled')) if is_ceilometer_enabled: check_and_setup_ceilometer(HIMN_IP, username, password) else: diff --git a/plugin_source/deployment_scripts/compute_pre_test.py b/plugin_source/deployment_scripts/compute_pre_test.py index 0014581..db7311a 100755 --- a/plugin_source/deployment_scripts/compute_pre_test.py +++ b/plugin_source/deployment_scripts/compute_pre_test.py @@ -1,94 +1,22 @@ #!/usr/bin/env python -import ConfigParser import logging -import netifaces import os -import re -from socket import inet_ntoa -from struct import pack -import subprocess # nosec -import sys import stat -import yaml - +import utils +from utils import HIMN_IP XS_RSA = '/root/.ssh/xs_rsa' -ASTUTE_PATH = '/etc/astute.yaml' -ASTUTE_SECTION = '@PLUGIN_NAME@' -LOG_ROOT = '/var/log/@PLUGIN_NAME@' -LOG_FILE = 'compute_pre_deployment.log' -HIMN_IP = '169.254.0.1' -INT_BRIDGE = 'br-int' -XS_PLUGIN_ISO = 'xenapi-plugins-mitaka.iso' -DIST_PACKAGES_DIR = '/usr/lib/python2.7/dist-packages/' +LOG_FILE = os.path.join(utils.LOG_ROOT, 'compute_pre_deployment.log') PLATFORM_VERSION = '1.9' -if not os.path.exists(LOG_ROOT): - os.mkdir(LOG_ROOT) +if not os.path.exists(utils.LOG_ROOT): + os.mkdir(utils.LOG_ROOT) -logging.basicConfig(filename=os.path.join(LOG_ROOT, LOG_FILE), +logging.basicConfig(filename=LOG_FILE, level=logging.DEBUG) -def reportError(err): - logging.error(err) - raise Exception(err) - - -def execute(*cmd, **kwargs): - cmd = map(str, cmd) - _env = kwargs.get('env') - env_prefix = '' - if _env: - env_prefix = ''.join(['%s=%s ' % (k, _env[k]) for k in _env]) - - env = dict(os.environ) - env.update(_env) - else: - env = None - logging.info(env_prefix + ' '.join(cmd)) - proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, # nosec - stderr=subprocess.PIPE, env=env) - - if 'prompt' in kwargs: - prompt = kwargs.get('prompt') - proc.stdout.flush() - (out, err) = proc.communicate(prompt) - else: - out = proc.stdout.readlines() - err = proc.stderr.readlines() - (out, err) = map(' '.join, [out, err]) - - # Both if/else need to deal with "\n" scenario - (out, err) = (out.replace('\n', ''), err.replace('\n', '')) - - if out: - logging.debug(out) - if err: - logging.error(err) - - if proc.returncode is not None and proc.returncode != 0: - raise Exception(err) - - return out - - -def ssh(host, username, *cmd, **kwargs): - cmd = map(str, cmd) - - return execute('ssh', '-i', XS_RSA, - '-o', 'StrictHostKeyChecking=no', - '%s@%s' % (username, host), *cmd, - prompt=kwargs.get('prompt')) - - -def scp(host, username, target_path, filename): - return execute('scp', '-i', XS_RSA, - '-o', 'StrictHostKeyChecking=no', filename, - '%s@%s:%s' % (username, host, target_path)) - - def ssh_copy_id(host, username, password): ssh_askpass = "askpass.sh" @@ -102,127 +30,48 @@ def ssh_copy_id(host, username, password): os.remove(XS_RSA) if os.path.exists(XS_RSA + ".pub"): os.remove(XS_RSA + ".pub") - execute('ssh-keygen', '-f', XS_RSA, '-t', 'rsa', '-N', '') + utils.execute('ssh-keygen', '-f', XS_RSA, '-t', 'rsa', '-N', '') env = { "HOME": "/root", "SSH_ASKPASS": os.path.abspath(ssh_askpass), "DISPLAY": ":.", } - execute("setsid", "ssh-copy-id", "-o", "StrictHostKeyChecking=no", - "-i", XS_RSA, "%s@%s" % (username, host), env=env) - - -def get_astute(astute_path): - """Return the root object read from astute.yaml""" - if not os.path.exists(astute_path): - reportError('%s not found' % astute_path) - with open(astute_path) as f: - astute = yaml.safe_load(f) - return astute - - -def astute_get(dct, keys, default=None, fail_if_missing=True): - """A safe dictionary getter""" - for key in keys: - if key in dct: - dct = dct[key] - else: - if fail_if_missing: - reportError('Value of "%s" is missing' % key) - return default - return dct - - -def get_options(astute, astute_section): - """Return username and password filled in plugin.""" - if astute_section not in astute: - reportError('%s not found' % astute_section) - - options = astute[astute_section] - 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'] - - -def init_eth(): - """Initialize the net interface connected to HIMN - - Returns: - the IP addresses of local host and hypervisor. - """ - - domid = execute('xenstore-read', 'domid') - himn_mac = execute( - 'xenstore-read', - '/local/domain/%s/vm-data/himn_mac' % domid) - logging.info('himn_mac: %s' % himn_mac) - - _mac = lambda eth: \ - netifaces.ifaddresses(eth).get(netifaces.AF_LINK)[0]['addr'] - eths = [eth for eth in netifaces.interfaces() if _mac(eth) == himn_mac] - if len(eths) != 1: - reportError('Cannot find eth matches himn_mac') - - eth = eths[0] - logging.info('himn_eth: %s' % eth) - - ip = netifaces.ifaddresses(eth).get(netifaces.AF_INET) - - if not ip: - execute('dhclient', eth) - fname = '/etc/network/interfaces.d/ifcfg-' + eth - s = ('auto {eth}\n' - 'iface {eth} inet dhcp\n' - 'post-up route del default dev {eth}').format(eth=eth) - with open(fname, 'w') as f: - f.write(s) - logging.info('%s created' % fname) - execute('ifdown', eth) - execute('ifup', eth) - ip = netifaces.ifaddresses(eth).get(netifaces.AF_INET) - - if ip: - himn_local = ip[0]['addr'] - himn_xs = '.'.join(himn_local.split('.')[:-1] + ['1']) - if HIMN_IP == himn_xs: - logging.info('himn_local: %s' % himn_local) - return eth, himn_local - - reportError('HIMN failed to get IP address from Hypervisor') + utils.execute("setsid", "ssh-copy-id", "-o", "StrictHostKeyChecking=no", + "-i", XS_RSA, "%s@%s" % (username, host), env=env) def check_host_compatibility(himn, username): hotfix = 'XS65ESP1013' - installed = ssh(himn, username, - 'xe patch-list name-label=%s --minimal' % hotfix) - ver = ssh(himn, username, - ('xe host-param-get uuid=$(xe host-list --minimal) ' - 'param-name=software-version param-key=platform_version')) + installed = utils.ssh(himn, username, + 'xe patch-list name-label=%s --minimal' % hotfix) + ver = utils.ssh(himn, username, + ('xe host-param-get uuid=$(xe host-list --minimal) ' + 'param-name=software-version param-key=platform_version')) if not installed and ver[:3] == PLATFORM_VERSION: - reportError(('Hotfix %s has not been installed ' - 'and product version is %s') % (hotfix, ver)) + utils.reportError(('Hotfix %s has not been installed ' + 'and product version is %s') % (hotfix, ver)) def check_local_sr(himn, username): - sr_type = ssh(himn, username, - ('xe sr-param-get param-name=type uuid=`xe pool-list params=default-SR --minimal`')) + sr_type = utils.ssh(himn, username, + ('xe sr-param-get param-name=type ' + 'uuid=`xe pool-list params=default-SR --minimal`')) if sr_type != "ext" and sr_type != "nfs": - reportError(('Default SR type should be EXT or NFS. If using local storage, Please make sure thin' - ' provisioning is enabled on your host during installation.')) + utils.reportError(('Default SR type should be EXT or NFS. If using ' + 'local storage, Please make sure thin provisioning ' + 'is enabled on your host during installation.')) if __name__ == '__main__': - astute = get_astute(ASTUTE_PATH) + astute = utils.get_astute() if astute: - username, password, install_xapi = get_options(astute, ASTUTE_SECTION) - himn_eth, himn_local = init_eth() + username, password, install_xapi = utils.get_options(astute) + himn_eth, himn_local = utils.init_eth() if username and password and himn_local: ssh_copy_id(HIMN_IP, username, password) check_host_compatibility(HIMN_IP, username) - check_local_sr(HIMN_IP, username) \ No newline at end of file + check_local_sr(HIMN_IP, username) diff --git a/plugin_source/deployment_scripts/utils.py b/plugin_source/deployment_scripts/utils.py new file mode 100644 index 0000000..983a956 --- /dev/null +++ b/plugin_source/deployment_scripts/utils.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python + +import logging +import netifaces +import os +import subprocess +import yaml + +XS_RSA = '/root/.ssh/xs_rsa' +ASTUTE_PATH = '/etc/astute.yaml' +ASTUTE_SECTION = '@PLUGIN_NAME@' +LOG_ROOT = '/var/log/@PLUGIN_NAME@' +HIMN_IP = '169.254.0.1' + + +class ExecutionError(Exception): + pass + + +class FatalException(Exception): + pass + + +def reportError(err): + logging.error(err) + raise FatalException(err) + + +def execute(*cmd, **kwargs): + cmd = map(str, cmd) + _env = kwargs.get('env') + env_prefix = '' + if _env: + env_prefix = ''.join(['%s=%s ' % (k, _env[k]) for k in _env]) + + env = dict(os.environ) + env.update(_env) + else: + env = None + logging.info(env_prefix + ' '.join(cmd)) + proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, # nosec + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, env=env) + + if 'prompt' in kwargs: + prompt = kwargs.get('prompt') + proc.stdout.flush() + (out, err) = proc.communicate(prompt) + else: + out = proc.stdout.readlines() + err = proc.stderr.readlines() + (out, err) = map(' '.join, [out, err]) + + # Both if/else need to deal with "\n" scenario + (out, err) = (out.replace('\n', ''), err.replace('\n', '')) + + if out: + logging.debug(out) + if err: + logging.error(err) + + if proc.returncode is not None and proc.returncode != 0: + raise ExecutionError(err) + + return out + + +def ssh(host, username, *cmd, **kwargs): + cmd = map(str, cmd) + + return execute('ssh', '-i', XS_RSA, + '-o', 'StrictHostKeyChecking=no', + '%s@%s' % (username, host), *cmd, + prompt=kwargs.get('prompt')) + + +def scp(host, username, target_path, filename): + return execute('scp', '-i', XS_RSA, + '-o', 'StrictHostKeyChecking=no', filename, + '%s@%s:%s' % (username, host, target_path)) + + +def get_astute(astute_path=ASTUTE_PATH): + """Return the root object read from astute.yaml""" + if not os.path.exists(astute_path): + reportError('%s not found' % astute_path) + with open(astute_path) as f: + astute = yaml.safe_load(f) + return astute + + +def astute_get(dct, keys, default=None, fail_if_missing=True): + """A safe dictionary getter""" + for key in keys: + if key in dct: + dct = dct[key] + else: + if fail_if_missing: + reportError('Value of "%s" is missing' % key) + return default + return dct + + +def get_options(astute, astute_section=ASTUTE_SECTION): + """Return username and password filled in plugin.""" + if astute_section not in astute: + reportError('%s not found' % astute_section) + + options = astute[astute_section] + 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'] + + +def init_eth(): + """Initialize the net interface connected to HIMN + + Returns: + the IP addresses of local host and hypervisor. + """ + + def _mac(eth): + return netifaces.ifaddresses(eth).get(netifaces.AF_LINK)[0]['addr'] + + domid = execute('xenstore-read', 'domid') + himn_mac = execute( + 'xenstore-read', + '/local/domain/%s/vm-data/himn_mac' % domid) + logging.info('himn_mac: %s' % himn_mac) + + eths = [eth for eth in netifaces.interfaces() if _mac(eth) == himn_mac] + if len(eths) != 1: + reportError('Cannot find eth matches himn_mac') + + eth = eths[0] + + logging.info('himn_eth: %s' % eth) + + ip = netifaces.ifaddresses(eth).get(netifaces.AF_INET) + + if not ip: + execute('dhclient', eth) + fname = '/etc/network/interfaces.d/ifcfg-' + eth + s = ('auto {eth}\n' + 'iface {eth} inet dhcp\n' + 'post-up route del default dev {eth}').format(eth=eth) + with open(fname, 'w') as f: + f.write(s) + logging.info('%s created' % fname) + execute('ifdown', eth) + execute('ifup', eth) + ip = netifaces.ifaddresses(eth).get(netifaces.AF_INET) + + if ip: + himn_local = ip[0]['addr'] + himn_xs = '.'.join(himn_local.split('.')[:-1] + ['1']) + if HIMN_IP == himn_xs: + logging.info('himn_local: %s' % himn_local) + return eth, himn_local + + reportError('HIMN failed to get IP address from Hypervisor') diff --git a/suppack/build-xenserver-suppack.sh b/suppack/build-xenserver-suppack.sh index 1f00768..d3376dc 100755 --- a/suppack/build-xenserver-suppack.sh +++ b/suppack/build-xenserver-suppack.sh @@ -17,9 +17,9 @@ set -eux THIS_FILE=$(readlink -f $0) FUELPLUG_UTILS_ROOT=$(dirname $THIS_FILE) -cd $FUELPLUG_UTILS_ROOT -rm -rf xenserver-suppack -mkdir -p xenserver-suppack && cd xenserver-suppack +BUILDROOT=${FUELPLUG_UTILS_ROOT}/build +rm -rf $BUILDROOT +mkdir -p $BUILDROOT && cd $BUILDROOT # ============================================= @@ -48,66 +48,85 @@ export DEBIAN_FRONTEND=noninteractive # ============================================= # Install suppack builder +RPM_ROOT=http://coltrane.uk.xensource.com/usr/groups/release/XenServer-7.x/XS-7.0/RTM-125380/binary-packages/RPMS/domain0/RPMS/noarch +wget $RPM_ROOT/supp-pack-build-2.1.0-xs55.noarch.rpm -O supp-pack-build.rpm +wget $RPM_ROOT/xcp-python-libs-1.9.0-159.noarch.rpm -O xcp-python-libs.rpm + +# Don't install the RPM as we may not have root. +rpm2cpio supp-pack-build.rpm | cpio -idm +rpm2cpio xcp-python-libs.rpm | cpio -idm +# Work around dodgy requirements for xcp.supplementalpack.setup function +# Note that either root or a virtual env is needed here. venvs are better :) +cp -f usr/bin/* . + +# If we are in a venv, we can potentially work with genisoimage and not mkisofs +venv_prefix=$(python -c 'import sys; print sys.prefix if hasattr(sys, "real_prefix") else ""') set +e -rpm -q supp-pack-build-2.1.0-xs55.noarch &> /dev/null -if [ $? -ne 0 ]; then - rpm -i http://coltrane.uk.xensource.com/usr/groups/release/XenServer-7.x/XS-7.0/RTM-125380/binary-packages/RPMS/domain0/RPMS/noarch/supp-pack-build-2.1.0-xs55.noarch.rpm -fi -rpm -q xcp-python-libs-1.9.0-159.noarch &> /dev/null -if [ $? -ne 0 ]; then - rpm -i http://coltrane.uk.xensource.com/usr/groups/release/XenServer-7.x/XS-7.0/RTM-125380/binary-packages/RPMS/domain0/RPMS/noarch/xcp-python-libs-1.9.0-159.noarch.rpm -fi +mkisofs=`which mkisofs` set -e +if [ -n "$venv_prefix" -a -z "$mkisofs" ]; then + # Some systems (e.g. debian) only have genisofsimage. + set +e + genisoimage=`which genisoimage` + set -e + [ -n "$genisoimage" ] && ln -s $genisoimage $venv_prefix/bin/mkisofs +fi + +# Now we must have mkisofs as the supp pack builder just invokes it +which mkisofs || (echo "mkisofs not installed" && exit 1) + # ============================================= # Check out rpm packaging repo rm -rf xenserver-nova-suppack-builder git clone $RPM_BUILDER_REPO xenserver-nova-suppack-builder -cd xenserver-nova-suppack-builder +pushd xenserver-nova-suppack-builder git checkout -b mos_suppack_builder "$GITBRANCH" -cd .. +popd # ============================================= # Create nova rpm file rm -rf nova git clone "$NOVA_GITREPO" nova -cd nova +pushd nova git checkout -b mos_nova "$GITBRANCH" # patch xenhost as this file is not merged into this release cp $FUELPLUG_UTILS_ROOT/../plugin_source/deployment_scripts/patchset/xenhost plugins/xenserver/xenapi/etc/xapi.d/plugins/ -cd .. +popd cp -r xenserver-nova-suppack-builder/plugins/xenserver/xenapi/* nova/plugins/xenserver/xenapi/ -cd nova/plugins/xenserver/xenapi/contrib +pushd nova/plugins/xenserver/xenapi/contrib ./build-rpm.sh $XS_PLUGIN_VERSION -cd $FUELPLUG_UTILS_ROOT/ -RPMFILE=$(find -name "openstack-xen-plugins-*.noarch.rpm" -print) -cd xenserver-suppack +popd + +RPMFILE=$(find $FUELPLUG_UTILS_ROOT -name "openstack-xen-plugins-*.noarch.rpm" -print) # ============================================= # Create neutron rpm file rm -rf neutron git clone "$NEUTRON_GITREPO" neutron -cd neutron +pushd neutron git checkout -b mos_neutron "$GITBRANCH" -cd .. +popd 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 +pushd neutron/neutron/plugins/ml2/drivers/openvswitch/agent/xenapi/contrib ./build-rpm.sh $XS_PLUGIN_VERSION -cd $FUELPLUG_UTILS_ROOT/ +popd -NEUTRON_RPMFILE=$(find -name "openstack-neutron-xen-plugins-*.noarch.rpm" -print) +NEUTRON_RPMFILE=$(find $FUELPLUG_UTILS_ROOT -name "openstack-neutron-xen-plugins-*.noarch.rpm" -print) # ============================================= # Create Supplemental pack -rm -rf suppack -mkdir -p suppack +#rm -rf suppack +#mkdir -p suppack tee buildscript.py << EOF +import sys +sys.path.append('$BUILDROOT/usr/lib/python2.7/site-packages') from xcp.supplementalpack import * from optparse import OptionParser @@ -133,5 +152,5 @@ python buildscript.py \ --pdv=$OS_RELEASE \ --bld=0 \ --out=$FUELPLUG_UTILS_ROOT \ -$FUELPLUG_UTILS_ROOT/$RPMFILE \ -$FUELPLUG_UTILS_ROOT/$NEUTRON_RPMFILE +$RPMFILE \ +$NEUTRON_RPMFILE