[deployment handler] Refactor the old installer_handler 49/28149/6
authorjose.lausuch <jose.lausuch@ericsson.com>
Mon, 6 Feb 2017 21:10:00 +0000 (22:10 +0100)
committerjose.lausuch <jose.lausuch@ericsson.com>
Thu, 9 Feb 2017 13:09:03 +0000 (14:09 +0100)
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 <jose.lausuch@ericsson.com>
modules/opnfv/deployment/__init__.py [new file with mode: 0644]
modules/opnfv/deployment/apex/__init__.py [new file with mode: 0644]
modules/opnfv/deployment/apex/adapter.py [new file with mode: 0644]
modules/opnfv/deployment/example.py [new file with mode: 0644]
modules/opnfv/deployment/factory.py [new file with mode: 0644]
modules/opnfv/deployment/fuel/__init__.py [new file with mode: 0644]
modules/opnfv/deployment/fuel/adapter.py [new file with mode: 0644]
modules/opnfv/deployment/manager.py [new file with mode: 0644]
modules/opnfv/utils/opnfv_logger.py [moved from modules/opnfv/utils/OPNFVLogger.py with 100% similarity]
modules/opnfv/utils/ssh_utils.py [moved from modules/opnfv/utils/SSHUtils.py with 95% similarity]

diff --git a/modules/opnfv/deployment/__init__.py b/modules/opnfv/deployment/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/modules/opnfv/deployment/apex/__init__.py b/modules/opnfv/deployment/apex/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/modules/opnfv/deployment/apex/adapter.py b/modules/opnfv/deployment/apex/adapter.py
new file mode 100644 (file)
index 0000000..1b81e78
--- /dev/null
@@ -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 (file)
index 0000000..6a76eb9
--- /dev/null
@@ -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 (file)
index 0000000..e48a751
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/modules/opnfv/deployment/fuel/adapter.py b/modules/opnfv/deployment/fuel/adapter.py
new file mode 100644 (file)
index 0000000..d53966e
--- /dev/null
@@ -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 (file)
index 0000000..f0e4429
--- /dev/null
@@ -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)
similarity index 95%
rename from modules/opnfv/utils/SSHUtils.py
rename to modules/opnfv/utils/ssh_utils.py
index e0a830c..f900455 100644 (file)
@@ -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)