summaryrefslogtreecommitdiffstats
path: root/modules/opnfv/deployment
diff options
context:
space:
mode:
authorjose.lausuch <jose.lausuch@ericsson.com>2017-02-06 22:10:00 +0100
committerjose.lausuch <jose.lausuch@ericsson.com>2017-02-09 14:09:03 +0100
commitba25afb8b30e8f757ea1a6a2819d30dd1644e5d3 (patch)
tree978efa55bd79a3609db99a998c76e11a56a1ee4e /modules/opnfv/deployment
parent8c17dd593620d962011cb4e810634182cba1d100 (diff)
[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 <jose.lausuch@ericsson.com>
Diffstat (limited to 'modules/opnfv/deployment')
-rw-r--r--modules/opnfv/deployment/__init__.py0
-rw-r--r--modules/opnfv/deployment/apex/__init__.py0
-rw-r--r--modules/opnfv/deployment/apex/adapter.py93
-rw-r--r--modules/opnfv/deployment/example.py21
-rw-r--r--modules/opnfv/deployment/factory.py44
-rw-r--r--modules/opnfv/deployment/fuel/__init__.py0
-rw-r--r--modules/opnfv/deployment/fuel/adapter.py167
-rw-r--r--modules/opnfv/deployment/manager.py299
8 files changed, 624 insertions, 0 deletions
diff --git a/modules/opnfv/deployment/__init__.py b/modules/opnfv/deployment/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/modules/opnfv/deployment/__init__.py
diff --git a/modules/opnfv/deployment/apex/__init__.py b/modules/opnfv/deployment/apex/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/modules/opnfv/deployment/apex/__init__.py
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
--- /dev/null
+++ b/modules/opnfv/deployment/fuel/__init__.py
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)