From: jose.lausuch Date: Mon, 6 Feb 2017 21:10:00 +0000 (+0100) Subject: [deployment handler] Refactor the old installer_handler X-Git-Tag: danube.1.0~307^2 X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=commitdiff_plain;h=ba25afb8b30e8f757ea1a6a2819d30dd1644e5d3;p=releng.git [deployment handler] Refactor the old installer_handler This is a util library to interact with the deployment regardless of the installer. Objects: - Deployment - Node - DeploymentHandler - ApexAdapter - FuelAdapter - Factory The installer adapters implement some of the abstract functions of DeploymentHandler class that can't be generalized. Printout of example.py: http://pastebin.com/raw/SF3A1fee More info: JIRA: RELENG-149 Change-Id: I5c9e94459d5be0bfad6ffac29908a8cfc7ba919c Signed-off-by: jose.lausuch --- diff --git a/modules/opnfv/deployment/__init__.py b/modules/opnfv/deployment/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modules/opnfv/deployment/apex/__init__.py b/modules/opnfv/deployment/apex/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modules/opnfv/deployment/apex/adapter.py b/modules/opnfv/deployment/apex/adapter.py new file mode 100644 index 000000000..1b81e781b --- /dev/null +++ b/modules/opnfv/deployment/apex/adapter.py @@ -0,0 +1,93 @@ +############################################################################## +# Copyright (c) 2017 Ericsson AB and others. +# Author: Jose Lausuch (jose.lausuch@ericsson.com) +# 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 re + +from opnfv.deployment import manager +from opnfv.utils import opnfv_logger as logger +from opnfv.utils import ssh_utils + +logger = logger.Logger(__name__).getLogger() + + +class ApexAdapter(manager.DeploymentHandler): + + def __init__(self, installer_ip, installer_user, pkey_file): + super(ApexAdapter, self).__init__(installer='apex', + installer_ip=installer_ip, + installer_user=installer_user, + installer_pwd=None, + pkey_file=pkey_file) + + def nodes(self): + nodes = [] + cmd = "source /home/stack/stackrc;nova list 2>/dev/null" + output = self.installer_node.run_cmd(cmd) + lines = output.rsplit('\n') + if len(lines) < 4: + logger.info("No nodes found in the deployment.") + return None + + for line in lines: + if 'controller' in line: + roles = "controller" + elif 'compute' in line: + roles = "compute" + else: + continue + if 'Daylight' in line: + roles += ", OpenDaylight" + fields = line.split('|') + id = re.sub('[!| ]', '', fields[1]) + name = re.sub('[!| ]', '', fields[2]) + status_node = re.sub('[!| ]', '', fields[3]) + ip = re.sub('[!| ctlplane=]', '', fields[6]) + + if status_node == 'ACTIVE': + status = manager.Node.STATUS_OK + ssh_client = ssh_utils.get_ssh_client(hostname=ip, + username='heat-admin', + pkey_file=self.pkey_file) + else: + status = manager.Node.STATUS_INACTIVE + ssh_client = None + + node = manager.Node(id, ip, name, status, roles, ssh_client) + nodes.append(node) + + return nodes + + def get_openstack_version(self): + cmd = 'source overcloudrc;sudo nova-manage version' + result = self.installer_node.run_cmd(cmd) + return result + + def get_sdn_version(self): + cmd_descr = ("sudo yum info opendaylight 2>/dev/null|" + "grep Description|sed 's/^.*\: //'") + cmd_ver = ("sudo yum info opendaylight 2>/dev/null|" + "grep Version|sed 's/^.*\: //'") + for node in self.nodes: + if 'controller' in node.get_attribute('roles'): + description = node.run_cmd(cmd_descr) + version = node.run_cmd(cmd_ver) + break + + if description is None: + return None + else: + return description + ':' + version + + def get_deployment_status(self): + cmd = 'source stackrc;openstack stack list|grep CREATE_COMPLETE' + result = self.installer_node.run_cmd(cmd) + if result is None or len(result) == 0: + return 'failed' + else: + return 'active' diff --git a/modules/opnfv/deployment/example.py b/modules/opnfv/deployment/example.py new file mode 100644 index 000000000..6a76eb9c3 --- /dev/null +++ b/modules/opnfv/deployment/example.py @@ -0,0 +1,21 @@ +# This is an example of usage of this Tool +# Author: Jose Lausuch (jose.lausuch@ericsson.com) + +from opnfv.deployment import factory + +handler = factory.Factory.get_handler('apex', + '192.168.122.135', + 'stack', + pkey_file='/root/.ssh/id_rsa') + + +installer_node = handler.get_installer_node() +print("Hello, I am node '%s'" % installer_node.run_cmd('hostname')) +installer_node.get_file('/home/stack/overcloudrc', './overcloudrc') + +nodes = handler.get_nodes() +for node in nodes: + print("Hello, I am node '%s' and my ip is %s." % + (node.run_cmd('hostname'), node.ip)) + +print handler.get_deployment_info() diff --git a/modules/opnfv/deployment/factory.py b/modules/opnfv/deployment/factory.py new file mode 100644 index 000000000..e48a751ad --- /dev/null +++ b/modules/opnfv/deployment/factory.py @@ -0,0 +1,44 @@ +############################################################################## +# Copyright (c) 2017 Ericsson AB and others. +# Author: Jose Lausuch (jose.lausuch@ericsson.com) +# 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 +############################################################################## + + +from opnfv.deployment.apex import adapter as apex_adapter +from opnfv.deployment.fuel import adapter as fuel_adapter +from opnfv.utils import opnfv_logger as logger + +logger = logger.Logger(__name__).getLogger() + + +class Factory(object): + + INSTALLERS = ["fuel", "apex", "compass", "joid", "daisy"] + + def __init__(self): + pass + + @staticmethod + def get_handler(installer, + installer_ip, + installer_user, + installer_pwd=None, + pkey_file=None): + + if installer not in Factory.INSTALLERS: + raise Exception("This is not an OPNFV installer.") + + if installer.lower() == "apex": + return apex_adapter.ApexAdapter(installer_ip=installer_ip, + installer_user=installer_user, + pkey_file=pkey_file) + elif installer.lower() == "fuel": + return fuel_adapter.FuelAdapter(installer_ip=installer_ip, + installer_user=installer_user, + installer_pwd=installer_pwd) + else: + raise Exception("Installer adapter is not implemented.") diff --git a/modules/opnfv/deployment/fuel/__init__.py b/modules/opnfv/deployment/fuel/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modules/opnfv/deployment/fuel/adapter.py b/modules/opnfv/deployment/fuel/adapter.py new file mode 100644 index 000000000..d53966e82 --- /dev/null +++ b/modules/opnfv/deployment/fuel/adapter.py @@ -0,0 +1,167 @@ +############################################################################## +# Copyright (c) 2017 Ericsson AB and others. +# Author: Jose Lausuch (jose.lausuch@ericsson.com) +# George Paraskevopoulos (geopar@intracom-telecom.com) +# 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 +############################################################################## + + +from opnfv.deployment import manager +from opnfv.utils import opnfv_logger as logger +from opnfv.utils import ssh_utils + +logger = logger.Logger("FuelAdapter").getLogger() + + +class FuelAdapter(manager.DeploymentHandler): + + def __init__(self, installer_ip, installer_user, installer_pwd): + super(FuelAdapter, self).__init__(installer='fuel', + installer_ip=installer_ip, + installer_user=installer_user, + installer_pwd=installer_pwd, + pkey_file=None) + + def _get_clusters(self): + environments = [] + output = self.runcmd_fuel_env() + lines = output.rsplit('\n') + if len(lines) < 2: + logger.info("No environments found in the deployment.") + return None + else: + fields = lines[0].rsplit(' | ') + + index_id = -1 + index_status = -1 + index_name = -1 + index_release_id = -1 + + for i in range(len(fields) - 1): + if "id" in fields[i]: + index_id = i + elif "status" in fields[i]: + index_status = i + elif "name" in fields[i]: + index_name = i + elif "release_id" in fields[i]: + index_release_id = i + + # order env info + for i in range(2, len(lines) - 1): + fields = lines[i].rsplit(' | ') + dict = {"id": fields[index_id].strip(), + "status": fields[index_status].strip(), + "name": fields[index_name].strip(), + "release_id": fields[index_release_id].strip()} + environments.append(dict) + + return environments + + def nodes(self, options=None): + nodes = [] + cmd = 'fuel node' + output = self.installer_node.run_cmd(cmd) + lines = output.rsplit('\n') + if len(lines) < 2: + logger.info("No nodes found in the deployment.") + return None + else: + # get fields indexes + fields = lines[0].rsplit(' | ') + + index_id = -1 + index_status = -1 + index_name = -1 + index_cluster = -1 + index_ip = -1 + index_mac = -1 + index_roles = -1 + index_online = -1 + + for i in range(0, len(fields) - 1): + if "id" in fields[i]: + index_id = i + elif "status" in fields[i]: + index_status = i + elif "name" in fields[i]: + index_name = i + elif "cluster" in fields[i]: + index_cluster = i + elif "ip" in fields[i]: + index_ip = i + elif "mac" in fields[i]: + index_mac = i + elif "roles " in fields[i]: + index_roles = i + elif "online" in fields[i]: + index_online = i + + # order nodes info + for i in range(2, len(lines) - 1): + fields = lines[i].rsplit(' | ') + + id = fields[index_id].strip(), + ip = fields[index_ip].strip() + status_node = fields[index_status].strip() + name = fields[index_name].strip() + roles = fields[index_roles].strip() + + dict = {"cluster": fields[index_cluster].strip(), + "mac": fields[index_mac].strip(), + "online": fields[index_online].strip()} + + if status_node == 'ready': + status = manager.Node.STATUS_OK + proxy = {'ip': self.installer_ip, + 'username': self.installer_user, + 'password': self.installer_pwd} + ssh_client = ssh_utils.get_ssh_client(hostname=ip, + username='root', + proxy=proxy) + else: + status = manager.Node.STATUS_INACTIVE + ssh_client = None + + node = manager.Node( + id, ip, name, status, roles, ssh_client, dict) + nodes.append(node) + + # TODO: Add support for Fuel cluster selection + ''' + if options and options['cluster']: + if fields[index_cluster].strip() == options['cluster']: + ''' + + return nodes + + def get_openstack_version(self): + cmd = 'source openrc;nova-manage version 2>/dev/null' + version = None + for node in self.nodes: + if 'controller' in node.get_attribute('roles'): + version = node.run_cmd(cmd) + break + return version + + def get_sdn_version(self): + cmd = "apt-cache show opendaylight|grep Version|sed 's/^.*\: //'" + version = None + for node in self.nodes: + if 'controller' in node.get_attribute('roles'): + odl_version = node.run_cmd(cmd) + if odl_version: + version = 'OpenDaylight ' + odl_version + break + return version + + def get_deployment_status(self): + cmd = 'fuel env|grep operational' + result = self.installer_node.run_cmd(cmd) + if result is None or len(result) == 0: + return 'failed' + else: + return 'active' diff --git a/modules/opnfv/deployment/manager.py b/modules/opnfv/deployment/manager.py new file mode 100644 index 000000000..f0e442903 --- /dev/null +++ b/modules/opnfv/deployment/manager.py @@ -0,0 +1,299 @@ +############################################################################## +# Copyright (c) 2017 Ericsson AB and others. +# Author: Jose Lausuch (jose.lausuch@ericsson.com) +# 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 +############################################################################## + +from abc import abstractmethod +import os + + +from opnfv.utils import opnfv_logger as logger +from opnfv.utils import ssh_utils + +logger = logger.Logger(__name__).getLogger() + + +class Deployment(object): + + def __init__(self, + installer, + installer_ip, + scenario, + pod, + status, + openstack_version, + sdn_controller, + nodes=[]): + + self.deployment_info = { + 'installer': installer, + 'installer_ip': installer_ip, + 'scenario': scenario, + 'pod': pod, + 'status': status, + 'openstack_version': openstack_version, + 'sdn_controller': sdn_controller, + 'nodes': nodes + } + + def _get_openstack_release(self): + ''' + Translates an openstack version into the release name + ''' + os_versions = { + '12': 'Liberty', + '13': 'Mitaka', + '14': 'Newton', + '15': 'Ocata', + '16': 'Pike', + '17': 'Queens' + } + try: + version = self.deployment_info['openstack_version'].split('.')[0] + name = os_versions[version] + return name + except Exception as e: + return 'Unknown release' + + def get_dict(self): + ''' + Returns a dictionary will all the attributes + ''' + return self.deployment_info + + def __str__(self): + ''' + Override of the str method + ''' + s = ''' + INSTALLER: {installer} + SCENARIO: {scenario} + INSTALLER IP: {installer_ip} + POD: {pod} + STATUS: {status} + OPENSTACK: {openstack_version} ({openstack_release}) + SDN: {sdn_controller} + NODES: + '''.format(installer=self.deployment_info['installer'], + scenario=self.deployment_info['scenario'], + installer_ip=self.deployment_info['installer_ip'], + pod=self.deployment_info['pod'], + status=self.deployment_info['status'], + openstack_version=self.deployment_info[ + 'openstack_version'], + openstack_release=self._get_openstack_release(), + sdn_controller=self.deployment_info['sdn_controller']) + + for node in self.deployment_info['nodes']: + s += '\t\t{node_object}\n'.format(node_object=node) + + return s + + +class Node(object): + + STATUS_OK = 'active' + STATUS_INACTIVE = 'inactive' + STATUS_OFFLINE = 'offline' + STATUS_FAILED = 'failed' + + def __init__(self, + id, + ip, + name, + status, + roles, + ssh_client, + info={}): + self.id = id + self.ip = ip + self.name = name + self.status = status + self.ssh_client = ssh_client + self.roles = roles + self.info = info + + def get_file(self, src, dest): + ''' + SCP file from a node + ''' + if self.status is not Node.STATUS_OK: + logger.info("The node %s is not active" % self.ip) + return 1 + logger.info("Fetching %s from %s" % (src, self.ip)) + get_file_result = ssh_utils.get_file(self.ssh_client, src, dest) + if get_file_result is None: + logger.error("SFTP failed to retrieve the file.") + else: + logger.info("Successfully copied %s:%s to %s" % + (self.ip, src, dest)) + return get_file_result + + def put_file(self, src, dest): + ''' + SCP file to a node + ''' + if self.status is not Node.STATUS_OK: + logger.info("The node %s is not active" % self.ip) + return 1 + logger.info("Copying %s to %s" % (src, self.ip)) + put_file_result = ssh_utils.put_file(self.ssh_client, src, dest) + if put_file_result is None: + logger.error("SFTP failed to retrieve the file.") + else: + logger.info("Successfully copied %s to %s:%s" % + (src, dest, self.ip)) + return put_file_result + + def run_cmd(self, cmd): + ''' + Run command remotely on a node + ''' + if self.status is not Node.STATUS_OK: + logger.info("The node %s is not active" % self.ip) + return 1 + _, stdout, stderr = (self.ssh_client.exec_command(cmd)) + error = stderr.readlines() + if len(error) > 0: + logger.error("error %s" % ''.join(error)) + return error + output = ''.join(stdout.readlines()).rstrip() + return output + + def get_dict(self): + ''' + Returns a dictionary with all the attributes + ''' + return { + 'id': self.id, + 'ip': self.ip, + 'name': self.name, + 'status': self.status, + 'roles': self.roles, + 'info': self.info + } + + def get_attribute(self, attribute): + ''' + Returns an attribute given the name + ''' + return self.get_dict()[attribute] + + def is_controller(self): + ''' + Returns if the node is a controller + ''' + if 'controller' in self.get_attribute('roles'): + return True + return False + + def is_compute(self): + ''' + Returns if the node is a compute + ''' + if 'compute' in self.get_attribute('roles'): + return True + return False + + def __str__(self): + return str(self.get_dict()) + + +class DeploymentHandler(object): + + EX_OK = os.EX_OK + EX_ERROR = os.EX_SOFTWARE + FUNCTION_NOT_IMPLEMENTED = "Function not implemented by adapter!" + + def __init__(self, + installer, + installer_ip, + installer_user, + installer_pwd=None, + pkey_file=None): + + self.installer = installer.lower() + self.installer_ip = installer_ip + self.installer_user = installer_user + self.installer_pwd = installer_pwd + self.pkey_file = pkey_file + + if pkey_file is not None and not os.path.isfile(pkey_file): + raise Exception( + 'The private key file %s does not exist!' % pkey_file) + + self.installer_connection = ssh_utils.get_ssh_client( + hostname=self.installer_ip, + username=self.installer_user, + password=self.installer_pwd, + pkey_file=self.pkey_file) + + if self.installer_connection: + self.installer_node = Node(id='', + ip=installer_ip, + name=installer, + status='active', + ssh_client=self.installer_connection, + roles='installer node') + else: + raise Exception( + 'Cannot establish connection to the installer node!') + + self.nodes = self.nodes() + + @abstractmethod + def get_openstack_version(self): + ''' + Returns a string of the openstack version (nova-compute) + ''' + raise Exception(DeploymentHandler.FUNCTION_NOT_IMPLEMENTED) + + @abstractmethod + def get_sdn_version(self): + ''' + Returns a string of the sdn controller and its version, if exists + ''' + raise Exception(DeploymentHandler.FUNCTION_NOT_IMPLEMENTED) + + @abstractmethod + def get_deployment_status(self): + ''' + Returns a string of the status of the deployment + ''' + raise Exception(DeploymentHandler.FUNCTION_NOT_IMPLEMENTED) + + @abstractmethod + def nodes(self, options=None): + ''' + Generates a list of all the nodes in the deployment + ''' + raise Exception(DeploymentHandler.FUNCTION_NOT_IMPLEMENTED) + + def get_nodes(self, options=None): + ''' + Returns the list of Node objects + ''' + return self.nodes + + def get_installer_node(self): + ''' + Returns the installer node object + ''' + return self.installer_node + + def get_deployment_info(self): + ''' + Returns an object of type Deployment + ''' + return Deployment(installer=self.installer, + installer_ip=self.installer_ip, + scenario=os.getenv('DEPLOY_SCENARIO', 'Unknown'), + status=self.get_deployment_status(), + pod=os.getenv('NODE_NAME', 'Unknown'), + openstack_version=self.get_openstack_version(), + sdn_controller=self.get_sdn_version(), + nodes=self.nodes) diff --git a/modules/opnfv/utils/OPNFVLogger.py b/modules/opnfv/utils/opnfv_logger.py similarity index 100% rename from modules/opnfv/utils/OPNFVLogger.py rename to modules/opnfv/utils/opnfv_logger.py diff --git a/modules/opnfv/utils/SSHUtils.py b/modules/opnfv/utils/ssh_utils.py similarity index 95% rename from modules/opnfv/utils/SSHUtils.py rename to modules/opnfv/utils/ssh_utils.py index e0a830caa..f90045540 100644 --- a/modules/opnfv/utils/SSHUtils.py +++ b/modules/opnfv/utils/ssh_utils.py @@ -9,11 +9,12 @@ ############################################################################## -import paramiko -import opnfv.utils.OPNFVLogger as OPNFVLogger import os +import paramiko + +from opnfv.utils import opnfv_logger as logger -logger = OPNFVLogger.Logger('SSHUtils').getLogger() +logger = logger.Logger("SSH utils").getLogger() def get_ssh_client(hostname, @@ -79,7 +80,6 @@ class ProxyHopClient(paramiko.SSHClient): ''' def __init__(self, *args, **kwargs): - self.logger = OPNFVLogger.Logger("ProxyHopClient").getLogger() self.proxy_ssh = None self.proxy_transport = None self.proxy_channel = None @@ -129,4 +129,4 @@ class ProxyHopClient(paramiko.SSHClient): sock=self.proxy_channel) os.remove(self.local_ssh_key) except Exception, e: - self.logger.error(e) + logger.error(e)