From 5a1c7a939cef64609c398779d83e4c0bdae83083 Mon Sep 17 00:00:00 2001 From: dongwenjuan Date: Mon, 7 Aug 2017 10:19:23 +0800 Subject: refactor apex installer JIRA: DOCTOR-100 Change-Id: I684071d35aac99ad1f5b65ae74e0a98ac726af35 Signed-off-by: dongwenjuan --- .gitignore | 3 + tests/installer/__init__.py | 5 +- tests/installer/apex.py | 99 ++++++++++++++++++++++++++++ tests/installer/base.py | 4 +- tests/installer/common/congress.py | 47 +++++++++++++ tests/installer/common/restore_ceilometer.py | 27 ++++++++ tests/installer/common/set_ceilometer.py | 44 +++++++++++++ tests/installer/local.py | 2 +- tests/utils.py | 45 +++++++++++++ tox.ini | 2 + 10 files changed, 273 insertions(+), 5 deletions(-) create mode 100644 tests/installer/apex.py create mode 100644 tests/installer/common/congress.py create mode 100644 tests/installer/common/restore_ceilometer.py create mode 100644 tests/installer/common/set_ceilometer.py diff --git a/.gitignore b/.gitignore index 84d085d8..c671eeed 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,8 @@ /releng/ /tests/*.img +#IntelJ Idea +.idea/ + #Build results .tox diff --git a/tests/installer/__init__.py b/tests/installer/__init__.py index 491543c8..bb0e452d 100644 --- a/tests/installer/__init__.py +++ b/tests/installer/__init__.py @@ -14,7 +14,7 @@ from oslo_utils import importutils OPTS = [ cfg.StrOpt('type', default=os.environ.get('INSTALLER_TYPE', 'local'), - choices=['local'], + choices=['local', 'apex'], help='the type of installer', required=True), cfg.StrOpt('ip', @@ -28,7 +28,8 @@ OPTS = [ _installer_name_class_mapping = { - 'local': 'installer.local.LocalInstaller' + 'local': 'installer.local.LocalInstaller', + 'apex': 'installer.apex.ApexInstaller' } diff --git a/tests/installer/apex.py b/tests/installer/apex.py new file mode 100644 index 00000000..24cd5a75 --- /dev/null +++ b/tests/installer/apex.py @@ -0,0 +1,99 @@ +############################################################################## +# Copyright (c) 2017 ZTE Corporation and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +import getpass +import grp +import os +import pwd +import stat +import sys + +from installer.common.congress import set_doctor_driver_conf +from installer.common.congress import restore_doctor_driver_conf +from installer.base import BaseInstaller +from utils import SSHClient + + +class ApexInstaller(BaseInstaller): + node_user_name = 'heat-admin' + cm_set_script = 'set_ceilometer.py' + cm_restore_script = 'restore_ceilometer.py' + + def __init__(self, conf, log): + super(ApexInstaller, self).__init__(conf, log) + self.client = SSHClient(self.conf.installer.ip, + self.conf.installer.username, + look_for_keys=True) + self.key_file = None + self.controllers = list() + self.controller_clients = list() + + def setup(self): + self.log.info('Setup Apex installer start......') + + self.key_file = self.get_ssh_key_from_installer() + self.get_controller_ips() + self.set_apply_patches() + + def cleanup(self): + self.restore_apply_patches() + + def get_ssh_key_from_installer(self): + self.log.info('Get SSH keys from Apex installer......') + + self.client.scp('/home/stack/.ssh/id_rsa', './instack_key', method='get') + user = getpass.getuser() + uid = pwd.getpwnam(user).pw_uid + gid = grp.getgrnam(user).gr_gid + os.chown('./instack_key', uid, gid) + os.chmod('./instack_key', stat.S_IREAD) + current_dir = sys.path[0] + return '{0}/{1}'.format(current_dir, 'instack_key') + + def get_controller_ips(self): + self.log.info('Get controller ips from Apex installer......') + + command = "source stackrc; " \ + "nova list | grep ' overcloud-controller-[0-9] ' " \ + "| sed -e 's/^.*ctlplane=//' |awk '{print $1}'" + ret, controllers = self.client.ssh(command) + if ret: + raise Exception('Exec command to get controller ips in Apex installer failed' + 'ret=%s, output=%s' % (ret, controllers)) + self.controllers = controllers + + def set_apply_patches(self): + self.log.info('Set apply patches start......') + + for node_ip in self.controllers: + client = SSHClient(node_ip, self.node_user_name, key_filename=self.key_file) + self.controller_clients.append(client) + self._ceilometer_apply_patches(client, self.cm_set_script) + cmd = 'sudo systemctl restart openstack-congress-server.service' + set_doctor_driver_conf(client, cmd) + + def restore_apply_patches(self): + self.log.info('restore apply patches start......') + + for client in self.controller_clients: + self._ceilometer_apply_patches(client, self.cm_restore_script) + cmd = 'sudo systemctl restart openstack-congress-server.service' + restore_doctor_driver_conf(client, cmd) + + def _ceilometer_apply_patches(self, ssh_client, script_name): + installer_dir = os.path.dirname(os.path.realpath(__file__)) + script_abs_path = '{0}/{1}/{2}'.format(installer_dir, 'common', script_name) + + ssh_client.scp(script_abs_path, script_name) + cmd = 'sudo python %s' % script_name + ret, output = ssh_client.ssh(cmd) + if ret: + raise Exception('Do the ceilometer command in controller node failed....' + 'ret=%s, cmd=%s, output=%s' % (ret, cmd, output)) + ssh_client.ssh('sudo systemctl restart openstack-ceilometer-notification.service') + diff --git a/tests/installer/base.py b/tests/installer/base.py index 83467f55..f3837f15 100644 --- a/tests/installer/base.py +++ b/tests/installer/base.py @@ -16,8 +16,8 @@ class BaseInstaller(object): self.log = log @abc.abstractproperty - def computer_user_name(self): - """user name for login to computer node""" + def node_user_name(self): + """user name for login to cloud node""" @abc.abstractmethod def get_ssh_key_from_installer(self): diff --git a/tests/installer/common/congress.py b/tests/installer/common/congress.py new file mode 100644 index 00000000..db882de2 --- /dev/null +++ b/tests/installer/common/congress.py @@ -0,0 +1,47 @@ +############################################################################## +# Copyright (c) 2017 ZTE Corporation and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +def set_doctor_driver_conf(ssh_client, restart_cmd): + cg_set_cmd = '''#!/bin/bash +co_conf=/etc/congress/congress.conf +co_conf_bak=/etc/congress/congress.conf.bak +co_entry="congress.datasources.doctor_driver.DoctorDriver" +if sudo grep -e "^drivers.*$co_entry" $co_conf; then + echo "NOTE: congress is configured as we needed" +else + echo "modify the congress config" + sudo cp $co_conf $co_conf_bak + sudo sed -i -e "/^drivers/s/$/,$co_entry/" $co_conf + %s +fi + ''' % (restart_cmd) + + ret, output = ssh_client.ssh(cg_set_cmd) + if ret: + raise Exception('Do the congress command in controller node failed....' + 'ret=%s, cmd=%s, output=%s' % (ret, cg_set_cmd, output)) + + +def restore_doctor_driver_conf(ssh_client, restart_cmd): + cg_restore_cmd = '''#!/bin/bash +co_conf=/etc/congress/congress.conf +co_conf_bak=/etc/congress/congress.conf.bak +if [ -e $co_conf_bak ]; then + echo "restore the congress config" + sudo cp $co_conf_bak $co_conf + sudo rm $co_conf_bak + %s +else + echo "Do not need to restore the congress config" +fi + ''' % (restart_cmd) + + ret, output = ssh_client.ssh(cg_restore_cmd) + if ret: + raise Exception('Do the congress command in controller node failed....' + 'ret=%s, cmd=%s, output=%s' % (ret, cg_restore_cmd, output)) diff --git a/tests/installer/common/restore_ceilometer.py b/tests/installer/common/restore_ceilometer.py new file mode 100644 index 00000000..d25b9ede --- /dev/null +++ b/tests/installer/common/restore_ceilometer.py @@ -0,0 +1,27 @@ +############################################################################## +# Copyright (c) 2017 ZTE Corporation and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +import os +import shutil + +ep_file = '/etc/ceilometer/event_pipeline.yaml' +ep_file_bak = '/etc/ceilometer/event_pipeline.yaml.bak' + + +def restore_ep_config(): + + if not os.path.isfile(ep_file_bak): + print('Bak_file:%s does not exist.' % ep_file_bak) + else: + print('restore') + shutil.copyfile(ep_file_bak, ep_file) + os.remove(ep_file_bak) + return + + +restore_ep_config() diff --git a/tests/installer/common/set_ceilometer.py b/tests/installer/common/set_ceilometer.py new file mode 100644 index 00000000..f5946cb2 --- /dev/null +++ b/tests/installer/common/set_ceilometer.py @@ -0,0 +1,44 @@ +############################################################################## +# Copyright (c) 2017 ZTE Corporation and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +import os +import shutil +import yaml + +ep_file = '/etc/ceilometer/event_pipeline.yaml' +ep_file_bak = '/etc/ceilometer/event_pipeline.yaml.bak' +event_notifier_topic = 'notifier://?topic=alarm.all' + + +def set_notifier_topic(): + config_modified = False + + if not os.path.isfile(ep_file): + raise Exception("File doesn't exist: %s." % ep_file) + + with open(ep_file, 'r') as file: + config = yaml.safe_load(file) + + sinks = config['sinks'] + for sink in sinks: + if sink['name'] == 'event_sink': + publishers = sink['publishers'] + if event_notifier_topic not in publishers: + print('Add event notifier in ceilometer') + publishers.append(event_notifier_topic) + config_modified = True + else: + print('NOTE: event notifier is configured in ceilometer as we needed') + + if config_modified: + shutil.copyfile(ep_file, ep_file_bak) + with open(ep_file, 'w+') as file: + file.write(yaml.safe_dump(config)) + + +set_notifier_topic() diff --git a/tests/installer/local.py b/tests/installer/local.py index e156ac1a..abe0ba25 100644 --- a/tests/installer/local.py +++ b/tests/installer/local.py @@ -15,7 +15,7 @@ from utils import write_json_file class LocalInstaller(BaseInstaller): - computer_user_name = 'root' + node_user_name = 'root' nova_policy_file = '/etc/nova/policy.json' nova_policy_file_backup = '%s%s' % (nova_policy_file, '.bak') diff --git a/tests/utils.py b/tests/utils.py index c5d6c1c3..f57cd266 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -8,6 +8,7 @@ ############################################################################## import json import os +import paramiko def load_json_file(full_path): @@ -31,3 +32,47 @@ def write_json_file(full_path, data): with open(full_path, 'w+') as file: file.write(json.dumps(data)) + +class SSHClient(object): + def __init__(self, ip, username, password=None, pkey=None, + key_filename=None, log=None, look_for_keys=False, + allow_agent=False): + self.client = paramiko.SSHClient() + self.client.load_system_host_keys() + self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + self.client.connect(ip, username=username, password=password, + pkey=pkey, key_filename=key_filename, + look_for_keys=look_for_keys, + allow_agent=allow_agent) + self.log = log + + def __del__(self): + self.client.close() + + def ssh(self, command): + if self.log: + self.log.debug("Executing: %s" % command) + stdin, stdout, stderr = self.client.exec_command(command) + ret = stdout.channel.recv_exit_status() + output = list() + for line in stdout.read().splitlines(): + output.append(line) + if ret: + if self.log: + self.log.debug("*** FAILED to run command %s (%s)" % (command, ret)) + raise Exception( + "Unable to run \ncommand: %s\nret: %s" + % (command, ret)) + if self.log: + self.log.debug("*** SUCCESSFULLY run command %s" % command) + return ret, output + + def scp(self, source, dest, method='put'): + if self.log: + self.log.info("Copy %s -> %s" % (source, dest)) + ftp = self.client.open_sftp() + if method == 'put': + ftp.put(source, dest) + elif method == 'get': + ftp.get(source, dest) + ftp.close() diff --git a/tox.ini b/tox.ini index e75a3741..def3c76c 100644 --- a/tox.ini +++ b/tox.ini @@ -20,6 +20,8 @@ passenv = PROFILER_TYPE PYTHON_ENABLE CI_DEBUG + INSTALLER_TYPE + INSTALLER_IP changedir = {toxinidir}/tests commands = python main.py -- cgit 1.2.3-korg