From d7a7c1cbbc814820c4efc9fca1fe5c94891db5b2 Mon Sep 17 00:00:00 2001 From: John Hua Date: Mon, 17 Oct 2016 11:58:37 +0800 Subject: [PATCH] Convert controller post script to python The old script is based on shell and not convenient to use in some cases. Some functions in utils script might also need to be adjusted. Change-Id: Ie1c9417363b07074f879e62b8856a455efb281c9 --- .../compute_post_deployment.py | 29 ++-- .../deployment_scripts/compute_pre_test.py | 9 +- .../controller_post_deployment.py | 146 ++++++++++++++++++ .../controller_post_deployment.sh | 99 ------------ plugin_source/deployment_scripts/utils.py | 47 ++++-- plugin_source/deployment_tasks.yaml | 2 +- 6 files changed, 192 insertions(+), 140 deletions(-) create mode 100755 plugin_source/deployment_scripts/controller_post_deployment.py delete mode 100755 plugin_source/deployment_scripts/controller_post_deployment.sh diff --git a/plugin_source/deployment_scripts/compute_post_deployment.py b/plugin_source/deployment_scripts/compute_post_deployment.py index d7b9195..cb4ae5f 100755 --- a/plugin_source/deployment_scripts/compute_post_deployment.py +++ b/plugin_source/deployment_scripts/compute_post_deployment.py @@ -2,7 +2,6 @@ import ConfigParser from distutils.version import LooseVersion -import logging import netifaces import os import re @@ -12,7 +11,6 @@ import utils from utils import HIMN_IP -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/' @@ -20,11 +18,8 @@ CONNTRACK_ISO = 'conntrack-tools.iso' CONNTRACK_CONF_SAMPLE =\ '/usr/share/doc/conntrack-tools-1.4.2/doc/stats/conntrackd.conf' -if not os.path.exists(utils.LOG_ROOT): - os.mkdir(utils.LOG_ROOT) - -logging.basicConfig(filename=LOG_FILE, - level=logging.DEBUG) +utils.setup_logging('compute_post_deployment.log') +LOG = utils.LOG def get_endpoints(astute): @@ -35,8 +30,8 @@ def get_endpoints(astute): endpoints[k]['IP'][0] ) for k in endpoints]) - logging.info('storage network: {storage}'.format(**endpoints)) - logging.info('mgmt network: {mgmt}'.format(**endpoints)) + LOG.info('storage network: {storage}'.format(**endpoints)) + LOG.info('mgmt network: {mgmt}'.format(**endpoints)) return endpoints @@ -77,7 +72,7 @@ def create_novacompute_conf(himn, username, password, public_ip, services_ssl): cf.write(configfile) except Exception: utils.reportError('Cannot set configurations to %s' % filename) - logging.info('%s created' % filename) + LOG.info('%s created' % filename) def route_to_compute(endpoints, himn_xs, himn_local, username): @@ -122,7 +117,7 @@ def route_to_compute(endpoints, himn_xs, himn_local, username): cmd = cmd.format(net=net, mask=mask, himn_local=himn_local) utils.ssh(himn_xs, username, cmd) else: - logging.info('%s network ip is missing' % endpoint_name) + LOG.info('%s network ip is missing' % endpoint_name) utils.ssh(himn_xs, username, 'chmod +x /etc/udev/scripts/reroute.sh') utils.ssh(himn_xs, username, ('echo \'SUBSYSTEM=="net" ACTION=="add" ' @@ -203,7 +198,7 @@ def modify_neutron_rootwrap_conf(himn, username, password): cf.write(configfile) except Exception: utils.reportError("Fail to modify file %s", filename) - logging.info('Modify file %s successfully', filename) + LOG.info('Modify file %s successfully', filename) def modify_neutron_ovs_agent_conf(int_br, br_mappings): @@ -221,7 +216,7 @@ def modify_neutron_ovs_agent_conf(int_br, br_mappings): cf.write(configfile) except Exception: utils.reportError("Fail to modify %s", filename) - logging.info('Modify %s successfully', filename) + LOG.info('Modify %s successfully', filename) def get_private_network_ethX(): @@ -345,7 +340,7 @@ def check_and_setup_ceilometer(himn, username, password): cf.set('xenapi', 'connection_password', password) with open(filename, 'w') as configfile: cf.write(configfile) - logging.info('Modify file %s successfully', filename) + LOG.info('Modify file %s successfully', filename) except Exception: utils.reportError("Fail to modify file %s", filename) return @@ -359,7 +354,7 @@ def enable_conntrack_service(himn, username): 'param-key=platform_version')) if LooseVersion(xcp_ver) < LooseVersion('2.1.0'): # Only support conntrack-tools since XS7.0(XCP2.1.0) and above - logging.info('No need to enable conntrack-tools with XCP %s' % xcp_ver) + LOG.info('No need to enable conntrack-tools with XCP %s' % xcp_ver) return conn_installed = utils.ssh(himn, username, @@ -429,5 +424,5 @@ if __name__ == '__main__': if is_ceilometer_enabled: check_and_setup_ceilometer(HIMN_IP, username, password) else: - logging.info('Skip ceilomter setup as this service is ' - 'disabled.') + LOG.info('Skip ceilomter setup as this service is ' + 'disabled.') diff --git a/plugin_source/deployment_scripts/compute_pre_test.py b/plugin_source/deployment_scripts/compute_pre_test.py index e932249..83cf9e3 100755 --- a/plugin_source/deployment_scripts/compute_pre_test.py +++ b/plugin_source/deployment_scripts/compute_pre_test.py @@ -1,21 +1,16 @@ #!/usr/bin/env python import json -import logging import os import stat import utils from utils import HIMN_IP XS_RSA = '/root/.ssh/xs_rsa' -LOG_FILE = os.path.join(utils.LOG_ROOT, 'compute_pre_deployment.log') VERSION_HOTFIXES = '@VERSION_HOTFIXES@' -if not os.path.exists(utils.LOG_ROOT): - os.mkdir(utils.LOG_ROOT) - -logging.basicConfig(filename=LOG_FILE, - level=logging.DEBUG) +utils.setup_logging('compute_pre_test.log') +LOG = utils.LOG def ssh_copy_id(host, username, password): diff --git a/plugin_source/deployment_scripts/controller_post_deployment.py b/plugin_source/deployment_scripts/controller_post_deployment.py new file mode 100755 index 0000000..73bb765 --- /dev/null +++ b/plugin_source/deployment_scripts/controller_post_deployment.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python + +import ConfigParser +from glanceclient import Client +from keystoneauth1 import loading +from keystoneauth1 import session +import os +from time import sleep +import utils +import yaml + + +utils.setup_logging('controller_post_deployment.log') +LOG = utils.LOG + + +def get_keystone_creds(): + return { + 'username': os.environ.get('OS_USERNAME'), + 'password': os.environ.get('OS_PASSWORD'), + 'auth_url': os.environ.get('OS_AUTH_URL'), + 'tenant_name': os.environ.get('OS_TENANT_NAME'), + } + + +def get_keystone_session(): + loader = loading.get_plugin_loader('password') + creds = get_keystone_creds() + auth = loader.load_from_options(**creds) + return session.Session(auth=auth) + + +def list_images(sess): + LOG.info('Listing images:') + glance = Client('2', session=sess) + images = glance.images.list() + for image in images: + LOG.info(('+ {name} container_format:{container_format} ' + 'disk_format:{disk_format} visibility:{visibility} ' + 'file:{file}').format(**image)) + + +def del_images(sess, image_name): + glance = Client('2', session=sess) + images = glance.images.list() + for image in images: + if image.name == image_name: + glance.images.delete(image.id) + LOG.info('Image %s has been deleted' % image_name) + + +def add_image(sess, image_name, vm_mode, image_file): + glance = Client('2', session=sess) + image = glance.images.create(name=image_name, container_format="ovf", + disk_format="vhd", visibility="public", + vm_mode=vm_mode) + with open(image_file, 'rb') as f: + glance.images.upload(image.id, f) + LOG.info('Image %s (mode: %s, file: %s) has been added' % + (image_name, vm_mode, image_file)) + + +def wait_ocf_resource_started(timeout, interval): + """Wait until all ocf resources are started""" + LOG.info("Waiting for all ocf resources to start") + remain_time = timeout + while remain_time > 0: + resources = utils.execute('pcs', 'resource', 'show') + if resources: + exists_not_started = any([("Started" not in line) + for line in resources.split('\n') + if "ocf::fuel" in line]) + # All started + if not exists_not_started: + return + sleep(interval) + remain_time = timeout - interval + + utils.reportError("Timeout for waiting resources to start") + + +def mod_novnc(): + astute = utils.get_astute() + if astute: + public_ip = utils.astute_get( + astute, ('network_metadata', 'vips', 'public', 'ipaddr')) + filename = '/etc/nova/nova-compute.conf' + cf = ConfigParser.ConfigParser() + try: + cf.read(filename) + cf.set('DEFAULT', 'novncproxy_base_url', + 'http//%s:6080/vnc_auto.html' % public_ip) + cf.set('DEFAULT', 'novncproxy_host', '0.0.0.0') + with open(filename, 'w') as configfile: + cf.write(configfile) + LOG.info('%s created' % filename) + utils.execute('service', 'nova-novncproxy', 'restart') + utils.execute('service', 'nova-consoleauth', 'restart') + except Exception: + utils.reportError('Cannot set configurations to %s' % filename) + + +def mod_ceilometer(): + rc, out, err = utils.detailed_execute( + 'pcs', 'resource', 'show', 'p_ceilometer-agent-central', + allowed_return_codes=[0, 1]) + + """Wait until all ocf resources are started, otherwise there is risk for race + condition: If run "pcs resource restart" while some resources are still in + restarting or initiating stage, it may result into failures for both. + """ + if rc == 0: + wait_ocf_resource_started(300, 10) + LOG.info("Patching ceilometer pipeline.yaml to exclude \ + network.servers.*") + # Exclude network.services.* to avoid error 404 + pipeline = '/etc/ceilometer/pipeline.yaml' + if not os.path.exists(pipeline): + utils.reportError('%s not found' % pipeline) + with open(pipeline) as f: + ceilometer = yaml.safe_load(f) + sources = utils.astute_get(ceilometer, ('sources',)) + if len(sources) != 1: + utils.reportError('ceilometer has none or more than one sources') + source = sources[0] + meters = utils.astute_get(source, ('meters',)) + new_meter = '!network.services.*' + if new_meter not in meters: + meters.append(new_meter) + with open(pipeline, "w") as f: + ceilometer = yaml.safe_dump(ceilometer, f) + + restart_info = utils.execute( + 'pcs', 'resource', 'restart', 'p_ceilometer-agent-central') + + LOG.info(restart_info) + + +if __name__ == '__main__': + sess = get_keystone_session() + list_images(sess) + del_images(sess, "TestVM") + add_image(sess, "TestVM", "xen", "cirros-0.3.4-x86_64-disk.vhd.tgz") + list_images(sess) + mod_ceilometer() + mod_novnc() diff --git a/plugin_source/deployment_scripts/controller_post_deployment.sh b/plugin_source/deployment_scripts/controller_post_deployment.sh deleted file mode 100755 index 5565aad..0000000 --- a/plugin_source/deployment_scripts/controller_post_deployment.sh +++ /dev/null @@ -1,99 +0,0 @@ -#!/bin/bash -eu - -LOG_ROOT="/var/log/@PLUGIN_NAME@/" -mkdir -p $LOG_ROOT -LOG_FILE=$LOG_ROOT"controller_post_deployment.log" - -function replace_test_image { - local image_name - image_name="$1" - - local vm_mode - vm_mode="$2" - - local image_file - image_file="$3" - - image_id=$(glance image-list | awk -F "|" '/'$image_name'/ {print $2}') - - if [[ -n "$image_id" ]]; then - echo "Delete image $image_name" >> $LOG_FILE - glance image-delete $image_id 2>&1 &>> $LOG_FILE - fi - - echo "Create image $image_name" >> $LOG_FILE - glance image-create \ - --name "$image_name" \ - --container-format ovf \ - --disk-format vhd \ - --property vm_mode="$vm_mode" \ - --visibility public \ - --file "$image_file" \ - 2>&1 &>> $LOG_FILE -} - -function mod_novnc { - local public_ip - public_ip=$(python - < /etc/nova/nova-compute.conf <> $LOG_FILE; then - echo "$(date): wait for resources to start." >> $LOG_FILE - sleep $INTERVAL - remain_time=$((remain_time - $INTERVAL)) - else - return 0 - fi - done - echo "Error: $(date): timeout for waiting resources to start." >> $LOG_FILE - echo "Error: $(date): timeout for waiting resources to start." >&2 - exit 1 -} - -function mod_ceilometer { - # modify ceilometer configuration per need. - if pcs resource show p_ceilometer-agent-central >/dev/null 2>&1; then - # wait until all ocf resources are started, otherwise there is risk for race - # condition: If run "pcs resource restart" while some resources are still in - # restarting or initiating stage, it may result into failures for both. - wait_ocf_resource_started - - # exclude network.services.* to avoid NotFound: 404 service not found error. - sed -i '/- "!storage.api.request"/a\ - "!network.services.*"' \ - /etc/ceilometer/pipeline.yaml>>$LOG_FILE 2>&1 - pcs resource restart p_ceilometer-agent-central >>$LOG_FILE 2>&1 - fi -} - -source /root/openrc admin - -echo "Before image replacement" >> $LOG_FILE -glance image-list 2>&1 >> $LOG_FILE - -replace_test_image "TestVM" "xen" cirros-0.3.4-x86_64-disk.vhd.tgz - -echo "After image replacement" >> $LOG_FILE -glance image-list 2>&1 >> $LOG_FILE - -mod_novnc - -mod_ceilometer diff --git a/plugin_source/deployment_scripts/utils.py b/plugin_source/deployment_scripts/utils.py index a5f6edb..b563457 100644 --- a/plugin_source/deployment_scripts/utils.py +++ b/plugin_source/deployment_scripts/utils.py @@ -13,6 +13,10 @@ LOG_ROOT = '/var/log/@PLUGIN_NAME@' HIMN_IP = '169.254.0.1' +LOG = logging.getLogger('@PLUGIN_NAME@') +LOG.setLevel(logging.DEBUG) + + class ExecutionError(Exception): pass @@ -22,7 +26,7 @@ class FatalException(Exception): def reportError(err): - logging.error(err) + LOG.error(err) raise FatalException(err) @@ -37,7 +41,7 @@ def detailed_execute(*cmd, **kwargs): env.update(_env) else: env = None - logging.info(env_prefix + ' '.join(cmd)) + LOG.info(env_prefix + ' '.join(cmd)) proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, # nosec stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) @@ -51,14 +55,14 @@ def detailed_execute(*cmd, **kwargs): if out: # Truncate "\n" if it is the last char out = out.strip() - logging.debug(out) + LOG.debug(out) if err: - logging.info(err) + LOG.info(err) if proc.returncode is not None and proc.returncode != 0: if proc.returncode in kwargs.get('allowed_return_codes', [0]): - logging.info('Swallowed acceptable return code of %d', - proc.returncode) + LOG.info('Swallowed acceptable return code of %d', + proc.returncode) else: raise ExecutionError(err) @@ -106,6 +110,17 @@ def scp(host, username, target_path, filename): '%s@%s:%s' % (username, host, target_path)) +def setup_logging(filename): + LOG_FILE = os.path.join(LOG_ROOT, filename) + + if not os.path.exists(LOG_ROOT): + os.mkdir(LOG_ROOT) + + logging.basicConfig( + filename=LOG_FILE, level=logging.WARNING, + format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s') + + def get_astute(astute_path=ASTUTE_PATH): """Return the root object read from astute.yaml""" if not os.path.exists(astute_path): @@ -133,9 +148,9 @@ def get_options(astute, astute_section=ASTUTE_SECTION): 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)) + LOG.info('username: {username}'.format(**options)) + LOG.info('password: {password}'.format(**options)) + LOG.info('install_xapi: {install_xapi}'.format(**options)) return options['username'], options['password'], \ options['install_xapi'] @@ -163,7 +178,7 @@ def find_eth_xenstore(): himn_mac = execute( 'xenstore-read', '/local/domain/%s/vm-data/himn_mac' % domid) - logging.info('himn_mac: %s' % himn_mac) + LOG.info('himn_mac: %s' % himn_mac) eths = [eth for eth in netifaces.interfaces() if eth_to_mac(eth) == himn_mac] @@ -205,7 +220,7 @@ def init_eth(): try: eth = find_eth_xenstore() except Exception: - logging.debug('Failed to find MAC through xenstore', exc_info=True) + LOG.debug('Failed to find MAC through xenstore', exc_info=True) if eth is None: eth = detect_eth_dhclient() @@ -213,7 +228,7 @@ def init_eth(): if eth is None: reportError('Failed to detect HIMN ethernet device') - logging.info('himn_eth: %s' % eth) + LOG.info('himn_eth: %s' % eth) execute('dhclient', eth) fname = '/etc/network/interfaces.d/ifcfg-' + eth @@ -222,7 +237,7 @@ def init_eth(): 'post-up route del default dev {eth}').format(eth=eth) with open(fname, 'w') as f: f.write(s) - logging.info('%s created' % fname) + LOG.info('%s created' % fname) execute('ifdown', eth) execute('ifup', eth) @@ -231,12 +246,12 @@ def init_eth(): himn_xs = '.'.join(himn_local.split('.')[:-1] + ['1']) if HIMN_IP != himn_xs: # Not on the HIMN - we failed here. - logging.info('himn_local: DHCP returned incorrect IP %s' % - ip[0]['addr']) + LOG.info('himn_local: DHCP returned incorrect IP %s' % + ip[0]['addr']) ip = None if not ip: reportError('HIMN failed to get IP address from Hypervisor') - logging.info('himn_local: %s' % ip[0]['addr']) + LOG.info('himn_local: %s' % ip[0]['addr']) return eth, ip[0]['addr'] diff --git a/plugin_source/deployment_tasks.yaml b/plugin_source/deployment_tasks.yaml index e24a72c..13ac6df 100644 --- a/plugin_source/deployment_tasks.yaml +++ b/plugin_source/deployment_tasks.yaml @@ -31,5 +31,5 @@ requires: ['post_deployment_start'] type: shell parameters: - cmd: ./controller_post_deployment.sh + cmd: source /root/openrc && ./controller_post_deployment.py timeout: 600