diff options
Diffstat (limited to 'modules')
26 files changed, 1015 insertions, 3 deletions
diff --git a/modules/Readme.txt b/modules/README.rst index 32becc21e..caec46b1d 100644 --- a/modules/Readme.txt +++ b/modules/README.rst @@ -3,9 +3,10 @@ This directory may be used to add new tools that might be useful for any project in OPNFV. This tools must be python based and shall be imported as follows: - from releng.modules.utils import SSHUtils - from releng.modules.utils import RelengLogger - ... + from opnfv.utils import SSHUtils + from opnfv.utils import OPNFVLogger + from opnfv.utils import OPNFVException + from opnfv.utils import constants For further information about how to use this modules directory, contact: fatih.degirmenci@ericsson.com diff --git a/modules/__init.py__ b/modules/opnfv/__init__.py index e69de29bb..e69de29bb 100644 --- a/modules/__init.py__ +++ b/modules/opnfv/__init__.py diff --git a/modules/opnfv/installer_adapters/InstallerHandler.py b/modules/opnfv/installer_adapters/InstallerHandler.py new file mode 100644 index 000000000..e353ef3f4 --- /dev/null +++ b/modules/opnfv/installer_adapters/InstallerHandler.py @@ -0,0 +1,78 @@ +############################################################################## +# Copyright (c) 2015 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.installer_adapters.fuel.FuelAdapter import FuelAdapter +from opnfv.installer_adapters.apex.ApexAdapter import ApexAdapter +from opnfv.installer_adapters.compass.CompassAdapter import CompassAdapter +from opnfv.installer_adapters.joid.JoidAdapter import JoidAdapter + + +INSTALLERS = ["fuel", "apex", "compass", "joid"] + + +class InstallerHandler: + + def __init__(self, + installer, + installer_ip, + installer_user, + installer_pwd=None): + self.installer = installer.lower() + self.installer_ip = installer_ip + self.installer_user = installer_user + self.installer_pwd = installer_pwd + + if self.installer == INSTALLERS[0]: + self.InstallerAdapter = FuelAdapter(self.installer_ip, + self.installer_user, + self.installer_pwd) + elif self.installer == INSTALLERS[1]: + self.InstallerAdapter = ApexAdapter(self.installer_ip) + elif self.installer == INSTALLERS[2]: + self.InstallerAdapter = CompassAdapter(self.installer_ip) + elif self.installer == INSTALLERS[3]: + self.InstallerAdapter = JoidAdapter(self.installer_ip) + else: + print("Installer %s is not valid. " + "Please use one of the followings: %s" + % (self.installer, INSTALLERS)) + exit(1) + + def get_deployment_info(self): + return self.InstallerAdapter.get_deployment_info() + + def get_nodes(self, options=None): + return self.InstallerAdapter.get_nodes(options=options) + + def get_controller_ips(self, options=None): + return self.InstallerAdapter.get_controller_ips(options=options) + + def get_compute_ips(self, options=None): + return self.InstallerAdapter.get_compute_ips(options=options) + + def get_file_from_installer(self, + remote_path, + local_path, + options=None): + return self.InstallerAdapter.get_file_from_installer(remote_path, + local_path, + options=options) + + def get_file_from_controller(self, + remote_path, + local_path, + ip=None, + options=None): + return self.InstallerAdapter.get_file_from_controller(remote_path, + local_path, + ip=ip, + options=options) + + def get_all(self): + pass diff --git a/modules/installer_adapters/__init.py__ b/modules/opnfv/installer_adapters/__init__.py index e69de29bb..e69de29bb 100644 --- a/modules/installer_adapters/__init.py__ +++ b/modules/opnfv/installer_adapters/__init__.py diff --git a/modules/opnfv/installer_adapters/apex/ApexAdapter.py b/modules/opnfv/installer_adapters/apex/ApexAdapter.py new file mode 100644 index 000000000..17a27b10a --- /dev/null +++ b/modules/opnfv/installer_adapters/apex/ApexAdapter.py @@ -0,0 +1,32 @@ +############################################################################## +# Copyright (c) 2016 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 +############################################################################## + + +class ApexAdapter: + + def __init__(self, installer_ip): + self.installer_ip = installer_ip + + def get_deployment_info(self): + pass + + def get_nodes(self): + pass + + def get_controller_ips(self): + pass + + def get_compute_ips(self): + pass + + def get_file_from_installer(self, origin, target, options=None): + pass + + def get_file_from_controller(self, origin, target, ip=None, options=None): + pass diff --git a/modules/utils/__init.py__ b/modules/opnfv/installer_adapters/apex/__init__.py index e69de29bb..e69de29bb 100644 --- a/modules/utils/__init.py__ +++ b/modules/opnfv/installer_adapters/apex/__init__.py diff --git a/modules/opnfv/installer_adapters/compass/CompassAdapter.py b/modules/opnfv/installer_adapters/compass/CompassAdapter.py new file mode 100644 index 000000000..47cbc646d --- /dev/null +++ b/modules/opnfv/installer_adapters/compass/CompassAdapter.py @@ -0,0 +1,32 @@ +############################################################################## +# Copyright (c) 2016 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 +############################################################################## + + +class CompassAdapter: + + def __init__(self, installer_ip): + self.installer_ip = installer_ip + + def get_deployment_info(self): + pass + + def get_nodes(self): + pass + + def get_controller_ips(self): + pass + + def get_compute_ips(self): + pass + + def get_file_from_installer(self, origin, target, options=None): + pass + + def get_file_from_controller(self, origin, target, ip=None, options=None): + pass diff --git a/modules/opnfv/installer_adapters/compass/__init__.py b/modules/opnfv/installer_adapters/compass/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/modules/opnfv/installer_adapters/compass/__init__.py diff --git a/modules/opnfv/installer_adapters/fuel/FuelAdapter.py b/modules/opnfv/installer_adapters/fuel/FuelAdapter.py new file mode 100644 index 000000000..6f079404c --- /dev/null +++ b/modules/opnfv/installer_adapters/fuel/FuelAdapter.py @@ -0,0 +1,236 @@ +############################################################################## +# Copyright (c) 2016 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 +############################################################################## + +import opnfv.utils.SSHUtils as ssh_utils +import opnfv.utils.OPNFVLogger as logger + + +class FuelAdapter: + + def __init__(self, installer_ip, user="root", password="r00tme"): + self.installer_ip = installer_ip + self.installer_user = user + self.installer_password = password + self.installer_connection = ssh_utils.get_ssh_client( + installer_ip, + self.installer_user, + password=self.installer_password) + self.logger = logger.Logger("FuelHandler").getLogger() + + def runcmd_fuel_installer(self, cmd): + _, stdout, stderr = (self + .installer_connection + .exec_command(cmd)) + error = stderr.readlines() + if len(error) > 0: + self.logger.error("error %s" % ''.join(error)) + return error + output = ''.join(stdout.readlines()) + return output + + def runcmd_fuel_nodes(self): + return self.runcmd_fuel_installer('fuel nodes') + + def runcmd_fuel_env(self): + return self.runcmd_fuel_installer('fuel env') + + def get_clusters(self): + environments = [] + output = self.runcmd_fuel_env() + lines = output.rsplit('\n') + if len(lines) < 2: + self.logger.infp("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(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 "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 get_nodes(self, options=None): + nodes = [] + output = self.runcmd_fuel_nodes() + lines = output.rsplit('\n') + if len(lines) < 2: + self.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(' | ') + dict = {"id": fields[index_id].strip(), + "status": fields[index_status].strip(), + "name": fields[index_name].strip(), + "cluster": fields[index_cluster].strip(), + "ip": fields[index_ip].strip(), + "mac": fields[index_mac].strip(), + "roles": fields[index_roles].strip(), + "online": fields[index_online].strip()} + if options and options['cluster']: + if fields[index_cluster].strip() == options['cluster']: + nodes.append(dict) + else: + nodes.append(dict) + + return nodes + + def get_controller_ips(self, options): + nodes = self.get_nodes(options=options) + controllers = [] + for node in nodes: + if "controller" in node["roles"]: + controllers.append(node['ip']) + return controllers + + def get_compute_ips(self, options=None): + nodes = self.get_nodes(options=options) + computes = [] + for node in nodes: + if "compute" in node["roles"]: + computes.append(node['ip']) + return computes + + def get_deployment_info(self): + str = "Deployment details:\n" + str += "\tInstaller: Fuel\n" + str += "\tScenario: Unknown\n" + sdn = "None" + clusters = self.get_clusters() + str += "\tN.Clusters: %s\n" % len(clusters) + for cluster in clusters: + cluster_dic = {'cluster': cluster['id']} + str += "\tCluster info:\n" + str += "\t ID: %s\n" % cluster['id'] + str += "\t NAME: %s\n" % cluster['name'] + str += "\t STATUS: %s\n" % cluster['status'] + nodes = self.get_nodes(options=cluster_dic) + num_nodes = len(nodes) + for node in nodes: + if "opendaylight" in node['roles']: + sdn = "OpenDaylight" + elif "onos" in node['roles']: + sdn = "ONOS" + num_controllers = len( + self.get_controller_ips(options=cluster_dic)) + num_computes = len(self.get_compute_ips(options=cluster_dic)) + ha = False + if num_controllers > 1: + ha = True + + str += "\t HA: %s\n" % ha + str += "\t NUM.NODES: %s\n" % num_nodes + str += "\t CONTROLLERS: %s\n" % num_controllers + str += "\t COMPUTES: %s\n" % num_computes + str += "\t SDN CONTR.: %s\n\n" % sdn + str += self.runcmd_fuel_nodes() + return str + + def get_file_from_installer(self, remote_path, local_path, options=None): + self.logger.debug("Fetching %s from %s" % + (remote_path, self.installer_ip)) + get_file_result = ssh_utils.get_file(self.installer_connection, + remote_path, + local_path) + if get_file_result is None: + self.logger.error("SFTP failed to retrieve the file.") + return 1 + self.logger.info("%s successfully copied from Fuel to %s" % + (remote_path, local_path)) + + def get_file_from_controller(self, + remote_path, + local_path, + ip=None, + user='root', + options=None): + if ip is None: + controllers = self.get_controller_ips(options=options) + if len(controllers) == 0: + self.logger.info("No controllers found in the deployment.") + return 1 + else: + target_ip = controllers[0] + else: + target_ip = ip + + installer_jumphost = { + 'ip': self.installer_ip, + 'username': self.installer_user, + 'password': self.installer_password + } + controller_conn = ssh_utils.get_ssh_client( + target_ip, + user, + jumphost=installer_jumphost) + + self.logger.debug("Fetching %s from %s" % + (remote_path, target_ip)) + + get_file_result = ssh_utils.get_file(controller_conn, + remote_path, + local_path) + if get_file_result is None: + self.logger.error("SFTP failed to retrieve the file.") + return 1 + self.logger.info("%s successfully copied from %s to %s" % + (remote_path, target_ip, local_path)) diff --git a/modules/opnfv/installer_adapters/fuel/__init__.py b/modules/opnfv/installer_adapters/fuel/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/modules/opnfv/installer_adapters/fuel/__init__.py diff --git a/modules/opnfv/installer_adapters/fuel/example.py b/modules/opnfv/installer_adapters/fuel/example.py new file mode 100644 index 000000000..7fea4dfd7 --- /dev/null +++ b/modules/opnfv/installer_adapters/fuel/example.py @@ -0,0 +1,22 @@ +# This is an example of usage of this Tool +# Author: Jose Lausuch (jose.lausuch@ericsson.com) + +import opnfv.installer_adapters.InstallerHandler as ins_handler + +fuel_handler = ins_handler.InstallerHandler(installer='fuel', + installer_ip='10.20.0.2', + installer_user='root', + installer_pwd='r00tme') +print("Nodes in cluster 1:\n%s\n" % + fuel_handler.get_nodes(options={'cluster': '1'})) +print("Nodes in cluster 2:\n%s\n" % + fuel_handler.get_nodes(options={'cluster': '2'})) +print("Nodes:\n%s\n" % fuel_handler.get_nodes()) +print("Controller nodes:\n%s\n" % fuel_handler.get_controller_ips()) +print("Compute nodes:\n%s\n" % fuel_handler.get_compute_ips()) +print("\n%s\n" % fuel_handler.get_deployment_info()) +fuel_handler.get_file_from_installer('/root/deploy/dea.yaml', './dea.yaml') +fuel_handler.get_file_from_controller( + '/etc/neutron/neutron.conf', './neutron.conf') +fuel_handler.get_file_from_controller( + '/root/openrc', './openrc') diff --git a/modules/opnfv/installer_adapters/joid/JoidAdapter.py b/modules/opnfv/installer_adapters/joid/JoidAdapter.py new file mode 100644 index 000000000..be8c2ebac --- /dev/null +++ b/modules/opnfv/installer_adapters/joid/JoidAdapter.py @@ -0,0 +1,32 @@ +############################################################################## +# Copyright (c) 2016 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 +############################################################################## + + +class JoidAdapter: + + def __init__(self, installer_ip): + self.installer_ip = installer_ip + + def get_deployment_info(self): + pass + + def get_nodes(self): + pass + + def get_controller_ips(self): + pass + + def get_compute_ips(self): + pass + + def get_file_from_installer(self, origin, target, options=None): + pass + + def get_file_from_controller(self, origin, target, ip=None, options=None): + pass diff --git a/modules/opnfv/installer_adapters/joid/__init__.py b/modules/opnfv/installer_adapters/joid/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/modules/opnfv/installer_adapters/joid/__init__.py diff --git a/modules/opnfv/utils/Connection.py b/modules/opnfv/utils/Connection.py new file mode 100644 index 000000000..a3be51409 --- /dev/null +++ b/modules/opnfv/utils/Connection.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +# Copyright (c) 2016 Orange 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 time + + +class Connection(object): + + def __init__(self): + pass + + def verify_connectivity(self, target): + for x in range(0, 10): + ping_cmd = ("ping -c 1 -W 1 %s >/dev/null" % target) + response = os.system(ping_cmd) + if response == 0: + return os.EX_OK + time.sleep(1) + return os.EX_UNAVAILABLE + + def check_internet_access(self, url="www.google.com"): + return self.verify_connectivity(url) diff --git a/modules/opnfv/utils/Credentials.py b/modules/opnfv/utils/Credentials.py new file mode 100644 index 000000000..1882692b3 --- /dev/null +++ b/modules/opnfv/utils/Credentials.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python + +# Copyright (c) 2016 Orange 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 +# +# Usage example: +# from opnfv.utils.Credentials import Credentials as credentials +# credentials("fuel", "10.20.0.2", "root", "r00tme").fetch('./openrc') +# + +import os + +import opnfv.installer_adapters.InstallerHandler as ins_handler +import opnfv.utils.Connection as con +import opnfv.utils.OPNFVLogger as logger + + +class Credentials(object): + + def __init__(self, installer, ip, user, password=None): + self.installer = installer + self.ip = ip + self.logger = logger.Logger("Credentials", level="DEBUG").getLogger() + self.connection = con.Connection() + + if self.__check_installer_name(self.installer) != os.EX_OK: + self.logger.error("Installer %s not supported!" % self.installer) + return os.EX_CONFIG + else: + self.logger.debug("Installer %s supported." % self.installer) + + if self.connection.verify_connectivity(self.ip) != os.EX_OK: + self.logger.error("Installer %s not reachable!" % self.ip) + return os.EX_UNAVAILABLE + else: + self.logger.debug("IP %s is reachable!" % self.ip) + + self.logger.debug( + "Trying to stablish ssh connection to %s ..." % self.ip) + self.handler = ins_handler.InstallerHandler(installer, + ip, + user, + password) + + def __check_installer_name(self, installer): + if installer not in ("apex", "compass", "fuel", "joid"): + return os.EX_CONFIG + else: + return os.EX_OK + + def __check_path(self, path): + try: + with open(path, 'a'): + os.utime(path, None) + return os.EX_OK + except IOError as e: + self.logger.error(e) + return os.EX_IOERR + + def __fetch_creds_apex(self, target_path): + # TODO + pass + + def __fetch_creds_compass(self, target_path): + # TODO + pass + + def __fetch_creds_fuel(self, target_path): + creds_file = '/root/openrc' + try: + self.handler.get_file_from_controller(creds_file, target_path) + except Exception, e: + self.logger.error( + "Cannot get %s from controller. %e" % (creds_file, e)) + pass + + def __fetch_creds_joid(self, target_path): + # TODO + pass + + def fetch(self, target_path): + if self.__check_path(target_path) != os.EX_OK: + self.logger.error( + "Target path %s does not exist!" % target_path) + return os.EX_IOERR + else: + self.logger.debug("Target path correct.") + + self.logger.info("Fetching credentials from the deployment...") + if self.installer == "apex": + self.__fetch_creds_apex(target_path) + elif self.installer == "compass": + self.__fetch_creds_compass(target_path) + elif self.installer == "fuel": + self.__fetch_creds_fuel(target_path) + elif self.installer == "joid": + self.__fetch_creds_joid(target_path) diff --git a/modules/opnfv/utils/OPNFVExceptions.py b/modules/opnfv/utils/OPNFVExceptions.py new file mode 100644 index 000000000..03b3ea981 --- /dev/null +++ b/modules/opnfv/utils/OPNFVExceptions.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python + +# Copyright (c) 2016 Orange 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 +# +# This class defines Python OPNFV exceptions +# + + +class OPNFVException(Exception): + def __call__(self, *args): + return self.__class__(*(self.args + args)) + + +# ************************************ +# Generic +# ************************************ +class OPNFVSUTNotReachable(OPNFVException): + """Target System Under Test is not reachable""" + pass + + +class OPNFVCiExecutionError(OPNFVException): + """Error occurs during CI exection""" + pass + + +class TestDashboardError(OPNFVException): + """Impossible to report results to dashboard""" + pass + + +class TestReportingError(OPNFVException): + """Impossible to report results to reporting""" + pass + + +# ************************************ +# Exceptions related to test DB +# ************************************ +class TestDbNotReachable(OPNFVException): + """Test database is not reachable""" + pass + + +class UnknownScenario(OPNFVException): + """Test scenario is unknown""" + pass + + +class UnknownPod(OPNFVException): + """Test POD is unknown""" + pass + + +class UnknownProject(OPNFVException): + """Project is unknown""" + pass + + +class UnknownTestCase(OPNFVException): + """Test case is unknown""" + pass + + +class UnknownVersion(OPNFVException): + """Version is unknown""" + pass + + +class UnknownInstaller(OPNFVException): + """Installer is not supported""" + pass + + +# ******************* +# Test project errors +# ******************* +class FunctestExecutionError(OPNFVException): + """Internal Functest error""" + pass + + +class YardstickExecutionError(OPNFVException): + """Internal Yardstick error""" + pass + + +# ********************************** +# Errors related to Feature projects +# ********************************** +class TestCaseNotRunnable(OPNFVException): + """test case incompatible with SUT, scenario, installer""" + pass + + +class FeatureTestIntegrationError(OPNFVException): + """Impossible to integrate Feature test""" + pass + + +class FeatureTestExecutionError(OPNFVException): + """Error during Feature test execution""" + pass + + +# ********************************* +# Errors related to VNF on boarding +# ********************************* +class VNFTestNotRunnable(OPNFVException): + """VNF test is not compatible with SUT, scenario, installer""" + pass + + +class VNFIntegrationError(OPNFVException): + """Impossible to integrate the VNF test""" + pass + + +class VNFExecutionError(OPNFVException): + """Error during VNF test execution""" + pass diff --git a/modules/opnfv/utils/OPNFVLogger.py b/modules/opnfv/utils/OPNFVLogger.py new file mode 100644 index 000000000..6fa4ef2e2 --- /dev/null +++ b/modules/opnfv/utils/OPNFVLogger.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# +# 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 +# +# Logging levels: +# Level Numeric value +# CRITICAL 50 +# ERROR 40 +# WARNING 30 +# INFO 20 +# DEBUG 10 +# NOTSET 0 +# +# Usage: +# import RelengLogger as rl +# logger = fl.Logger("script_name").getLogger() +# logger.info("message to be shown with - INFO - ") +# logger.debug("message to be shown with - DEBUG -") + +import logging + + +class Logger: + + def __init__(self, logger_name, level="INFO"): + + self.logger = logging.getLogger(logger_name) + self.logger.propagate = 0 + self.logger.setLevel(logging.DEBUG) + + ch = logging.StreamHandler() + formatter = logging.Formatter('%(asctime)s - %(name)s - ' + '%(levelname)s - %(message)s') + ch.setFormatter(formatter) + if level.lower() == "debug": + ch.setLevel(logging.DEBUG) + else: + ch.setLevel(logging.INFO) + self.logger.addHandler(ch) + + hdlr = logging.FileHandler('/tmp/releng.log') + hdlr.setFormatter(formatter) + hdlr.setLevel(logging.DEBUG) + self.logger.addHandler(hdlr) + + def getLogger(self): + return self.logger diff --git a/modules/opnfv/utils/SSHUtils.py b/modules/opnfv/utils/SSHUtils.py new file mode 100644 index 000000000..6c794c274 --- /dev/null +++ b/modules/opnfv/utils/SSHUtils.py @@ -0,0 +1,121 @@ +############################################################################## +# Copyright (c) 2015 Ericsson AB and others. +# Authors: George Paraskevopoulos (geopar@intracom-telecom.com) +# 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 paramiko +import opnfv.utils.OPNFVLogger as OPNFVLogger +import os + +logger = OPNFVLogger.Logger('SSHUtils').getLogger() + + +def get_ssh_client(hostname, username, password=None, jumphost=None): + client = None + try: + if jumphost is None: + client = paramiko.SSHClient() + else: + client = JumpHostHopClient() + client.configure_jump_host(jumphost['ip'], + jumphost['username'], + jumphost['password']) + + if client is None: + raise Exception('Could not connect to client') + + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + client.connect(hostname, + username=username, + password=password) + return client + except Exception, e: + logger.error(e) + return None + + +def get_file(ssh_conn, src, dest): + try: + sftp = ssh_conn.open_sftp() + sftp.get(src, dest) + return True + except Exception, e: + logger.error("Error [get_file(ssh_conn, '%s', '%s']: %s" % + (src, dest, e)) + return None + + +def put_file(ssh_conn, src, dest): + try: + sftp = ssh_conn.open_sftp() + sftp.put(src, dest) + return True + except Exception, e: + logger.error("Error [put_file(ssh_conn, '%s', '%s']: %s" % + (src, dest, e)) + return None + + +class JumpHostHopClient(paramiko.SSHClient): + ''' + Connect to a remote server using a jumphost hop + ''' + + def __init__(self, *args, **kwargs): + self.logger = OPNFVLogger.Logger("JumpHostHopClient").getLogger() + self.jumphost_ssh = None + self.jumphost_transport = None + self.jumphost_channel = None + self.jumphost_ip = None + self.jumphost_ssh_key = None + self.local_ssh_key = os.path.join(os.getcwd(), 'id_rsa') + super(JumpHostHopClient, self).__init__(*args, **kwargs) + + def configure_jump_host(self, jh_ip, jh_user, jh_pass, + jh_ssh_key='/root/.ssh/id_rsa'): + self.jumphost_ip = jh_ip + self.jumphost_ssh_key = jh_ssh_key + self.jumphost_ssh = paramiko.SSHClient() + self.jumphost_ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + self.jumphost_ssh.connect(jh_ip, + username=jh_user, + password=jh_pass) + self.jumphost_transport = self.jumphost_ssh.get_transport() + + def connect(self, hostname, port=22, username='root', password=None, + pkey=None, key_filename=None, timeout=None, allow_agent=True, + look_for_keys=True, compress=False, sock=None, gss_auth=False, + gss_kex=False, gss_deleg_creds=True, gss_host=None, + banner_timeout=None): + try: + if self.jumphost_ssh is None: + raise Exception('You must configure the jump ' + 'host before calling connect') + + get_file_res = get_file(self.jumphost_ssh, + self.jumphost_ssh_key, + self.local_ssh_key) + if get_file_res is None: + raise Exception('Could\'t fetch SSH key from jump host') + jumphost_key = (paramiko.RSAKey + .from_private_key_file(self.local_ssh_key)) + + self.jumphost_channel = self.jumphost_transport.open_channel( + "direct-tcpip", + (hostname, 22), + (self.jumphost_ip, 22)) + + self.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + super(JumpHostHopClient, self).connect(hostname, + username=username, + pkey=jumphost_key, + sock=self.jumphost_channel) + os.remove(self.local_ssh_key) + except Exception, e: + self.logger.error(e) diff --git a/modules/opnfv/utils/__init__.py b/modules/opnfv/utils/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/modules/opnfv/utils/__init__.py diff --git a/modules/opnfv/utils/constants.py b/modules/opnfv/utils/constants.py new file mode 100644 index 000000000..29f0d02c5 --- /dev/null +++ b/modules/opnfv/utils/constants.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +# Copyright (c) 2016 Orange 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 + +INSTALLERS = ['apex', 'fuel', 'compass', 'joid'] +VERSIONS = ['arno', 'brahmaputra', 'colorado', 'danube'] + +EXIT_OK = 0 +EXIT_RUN_ERROR = -1 +EXIT_PUSH_TO_TEST_DB_ERROR = -2 diff --git a/modules/run_unit_tests.sh b/modules/run_unit_tests.sh new file mode 100755 index 000000000..df511ce0c --- /dev/null +++ b/modules/run_unit_tests.sh @@ -0,0 +1,36 @@ +#!/bin/bash +set -o errexit +set -o pipefail + +# Either Workspace is set (CI) +if [ -z $WORKSPACE ] +then + WORKSPACE="." +fi + + +# *************** +# Run unit tests +# *************** +echo "Running unit tests..." + +# start vitual env +virtualenv $WORKSPACE/modules_venv +source $WORKSPACE/modules_venv/bin/activate + +# install python packages +easy_install -U setuptools +easy_install -U pip +pip install $WORKSPACE + + +# unit tests +nosetests --with-xunit \ + --cover-package=opnfv \ + --with-coverage \ + --cover-xml \ + --cover-html \ + tests/unit +rc=$? + +deactivate diff --git a/modules/setup.py b/modules/setup.py new file mode 100644 index 000000000..8ac5ceac3 --- /dev/null +++ b/modules/setup.py @@ -0,0 +1,25 @@ +############################################################################## +# 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 setuptools import setup, find_packages + + +setup( + name="opnfv", + version="danube", + packages=find_packages(), + include_package_data=True, + package_data={ + }, + url="https://www.opnfv.org", + install_requires=["paramiko>=2.0.1", + "mock==1.3.0", + "nose==1.3.7", + "coverage==4.1", + "requests==2.9.1"] +) diff --git a/modules/tests/__init__.py b/modules/tests/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/modules/tests/__init__.py diff --git a/modules/tests/unit/__init__.py b/modules/tests/unit/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/modules/tests/unit/__init__.py diff --git a/modules/tests/unit/utils/__init__.py b/modules/tests/unit/utils/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/modules/tests/unit/utils/__init__.py diff --git a/modules/tests/unit/utils/test_OPNFVExceptions.py b/modules/tests/unit/utils/test_OPNFVExceptions.py new file mode 100644 index 000000000..fca927b58 --- /dev/null +++ b/modules/tests/unit/utils/test_OPNFVExceptions.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python + +# Copyright (c) 2016 Orange 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.0import requests +import unittest +import requests + +from opnfv.utils import OPNFVExceptions + + +def base_function(): + raise OPNFVExceptions.TestDbNotReachable('Test database is not reachable') + + +def base_function_wrong(): + raise OPNFVExceptions.NotSelfDefinedException + + +def db_connectivity(): + url = 'http://testresults.opnfv2.org/test/api/v1/projects/functest/cases' + r = requests.get(url) + if r.status_code is not 200: + raise OPNFVExceptions.TestDbNotReachable('Database not found') + + +def project_unknown(): + url = 'http://testresults.opnfv.org/test/api/v1/projects/functest2/cases' + r = requests.get(url) + if len(r.json()['testcases']) is 0: + raise OPNFVExceptions.UnknownProject + + +class TestBasicRaise(unittest.TestCase): + def test(self): + with self.assertRaises(Exception) as context: + base_function() + self.assertTrue('Test database is not reachable' in context.exception) + + +class TestWrongRaise(unittest.TestCase): + def test(self): + try: + base_function_wrong() + except OPNFVExceptions.OPNFVException: + assert(False) + except AttributeError: + assert(True) + + +class TestCaseDBNotReachable(unittest.TestCase): + def test(self): + with self.assertRaises(Exception) as context: + db_connectivity() + self.assertTrue('Database not found' in context.exception) + + +class TestUnkownProject(unittest.TestCase): + def test(self): + try: + project_unknown() + except OPNFVExceptions.TestDashboardError: + # should not be there + assert(False) + except OPNFVExceptions.UnknownProject: + assert(True) + except Exception: + assert(False) + +if __name__ == '__main__': + unittest.main() |