From d050b58d3fde6a049104d8287f8d8cfbe3013d5d Mon Sep 17 00:00:00 2001 From: DR695H Date: Thu, 18 Apr 2019 10:56:38 -0400 Subject: move robotframeworkonap to subdirectory i also moved the build to maven based, although that isnt needed to work right now. Change-Id: Ib18d70e3ea4858cc4b9d51fdc2046b59202640ab Issue-ID: TEST-141 Signed-off-by: DR695H Signed-off-by: Gary Wu --- README.TXT | 6 +- eteutils/DNSUtils.py | 17 - eteutils/EteGatherDataListener.py | 126 ------- eteutils/HEATUtils.py | 87 ----- eteutils/HTTPUtils.py | 18 - eteutils/JSONUtils.py | 41 --- eteutils/OSUtils.py | 14 - eteutils/OpenstackLibrary.py | 124 ------- eteutils/RequestsClientCert.py | 7 - eteutils/StringTemplater.py | 9 - eteutils/TemplatingEngine.py | 34 -- eteutils/UUID.py | 15 - eteutils/csvLibrary.py | 16 - loadtest/RunEte.py | 39 --- loadtest/TestConfig.py | 59 ---- loadtest/TestController.py | 80 ----- loadtest/TestMain.py | 93 ------ loadtest/__init__.py | 0 pom.xml | 80 +---- robotframework-onap/.gitignore | 262 +++++++++++++++ robotframework-onap/README.TXT | 6 + robotframework-onap/eteutils/DNSUtils.py | 17 + .../eteutils/EteGatherDataListener.py | 126 +++++++ robotframework-onap/eteutils/HEATUtils.py | 87 +++++ robotframework-onap/eteutils/HTTPUtils.py | 18 + robotframework-onap/eteutils/JSONUtils.py | 41 +++ robotframework-onap/eteutils/OSUtils.py | 14 + robotframework-onap/eteutils/OpenstackLibrary.py | 124 +++++++ robotframework-onap/eteutils/RequestsClientCert.py | 7 + robotframework-onap/eteutils/StringTemplater.py | 9 + robotframework-onap/eteutils/TemplatingEngine.py | 34 ++ robotframework-onap/eteutils/UUID.py | 15 + robotframework-onap/eteutils/__init__.py | 0 robotframework-onap/eteutils/csvLibrary.py | 16 + robotframework-onap/loadtest/RunEte.py | 39 +++ robotframework-onap/loadtest/TestConfig.py | 59 ++++ robotframework-onap/loadtest/TestController.py | 80 +++++ robotframework-onap/loadtest/TestMain.py | 93 ++++++ robotframework-onap/loadtest/__init__.py | 0 robotframework-onap/pom.xml | 85 +++++ robotframework-onap/setup-template.py | 48 +++ robotframework-onap/setup.cfg | 5 + robotframework-onap/setup.py | 48 +++ robotframework-onap/tox.ini | 12 + robotframework-onap/vcpeutils/SoUtils.py | 371 +++++++++++++++++++++ robotframework-onap/vcpeutils/__init__.py | 0 robotframework-onap/vcpeutils/csar_parser.py | 231 +++++++++++++ robotframework-onap/vcpeutils/preload.py | 231 +++++++++++++ robotframework-onap/vcpeutils/vcpecommon.py | 325 ++++++++++++++++++ setup.cfg | 5 - setup.py | 48 --- tox.ini | 12 - vcpeutils/SoUtils.py | 371 --------------------- vcpeutils/csar_parser.py | 231 ------------- vcpeutils/preload.py | 231 ------------- vcpeutils/vcpecommon.py | 325 ------------------ 56 files changed, 2411 insertions(+), 2080 deletions(-) delete mode 100644 eteutils/DNSUtils.py delete mode 100644 eteutils/EteGatherDataListener.py delete mode 100644 eteutils/HEATUtils.py delete mode 100644 eteutils/HTTPUtils.py delete mode 100644 eteutils/JSONUtils.py delete mode 100644 eteutils/OSUtils.py delete mode 100644 eteutils/OpenstackLibrary.py delete mode 100644 eteutils/RequestsClientCert.py delete mode 100644 eteutils/StringTemplater.py delete mode 100644 eteutils/TemplatingEngine.py delete mode 100644 eteutils/UUID.py delete mode 100644 eteutils/csvLibrary.py delete mode 100644 loadtest/RunEte.py delete mode 100644 loadtest/TestConfig.py delete mode 100644 loadtest/TestController.py delete mode 100644 loadtest/TestMain.py delete mode 100644 loadtest/__init__.py create mode 100644 robotframework-onap/.gitignore create mode 100644 robotframework-onap/README.TXT create mode 100644 robotframework-onap/eteutils/DNSUtils.py create mode 100644 robotframework-onap/eteutils/EteGatherDataListener.py create mode 100644 robotframework-onap/eteutils/HEATUtils.py create mode 100644 robotframework-onap/eteutils/HTTPUtils.py create mode 100644 robotframework-onap/eteutils/JSONUtils.py create mode 100644 robotframework-onap/eteutils/OSUtils.py create mode 100644 robotframework-onap/eteutils/OpenstackLibrary.py create mode 100644 robotframework-onap/eteutils/RequestsClientCert.py create mode 100644 robotframework-onap/eteutils/StringTemplater.py create mode 100644 robotframework-onap/eteutils/TemplatingEngine.py create mode 100644 robotframework-onap/eteutils/UUID.py create mode 100644 robotframework-onap/eteutils/__init__.py create mode 100644 robotframework-onap/eteutils/csvLibrary.py create mode 100644 robotframework-onap/loadtest/RunEte.py create mode 100644 robotframework-onap/loadtest/TestConfig.py create mode 100644 robotframework-onap/loadtest/TestController.py create mode 100644 robotframework-onap/loadtest/TestMain.py create mode 100644 robotframework-onap/loadtest/__init__.py create mode 100644 robotframework-onap/pom.xml create mode 100644 robotframework-onap/setup-template.py create mode 100644 robotframework-onap/setup.cfg create mode 100644 robotframework-onap/setup.py create mode 100644 robotframework-onap/tox.ini create mode 100755 robotframework-onap/vcpeutils/SoUtils.py create mode 100644 robotframework-onap/vcpeutils/__init__.py create mode 100755 robotframework-onap/vcpeutils/csar_parser.py create mode 100755 robotframework-onap/vcpeutils/preload.py create mode 100755 robotframework-onap/vcpeutils/vcpecommon.py delete mode 100644 setup.cfg delete mode 100644 setup.py delete mode 100644 tox.ini delete mode 100755 vcpeutils/SoUtils.py delete mode 100755 vcpeutils/csar_parser.py delete mode 100755 vcpeutils/preload.py delete mode 100755 vcpeutils/vcpecommon.py diff --git a/README.TXT b/README.TXT index e25739f..41a87ab 100644 --- a/README.TXT +++ b/README.TXT @@ -1,8 +1,6 @@ -OpenECOMP ETE Utils +Python Testing Utils ======================= - - Scripts written to be used during ete testing -to install locally, checkout this repo and then run 'pip install -e .' in the root \ No newline at end of file +to install locally, checkout this repo and then run 'pip install -e .' in the subdirectory of the project you want to install \ No newline at end of file diff --git a/eteutils/DNSUtils.py b/eteutils/DNSUtils.py deleted file mode 100644 index 65ae68b..0000000 --- a/eteutils/DNSUtils.py +++ /dev/null @@ -1,17 +0,0 @@ -import dns.message -import dns.name -import dns.query - -class DNSUtils: - """ Utilities useful for DNS requests """ - - def dns_request(self, domain, ns): - """ return the ip address of the given domain name from the given nameserver """ - request = dns.message.make_query(domain, dns.rdatatype.A); - request.flags |= dns.flags.AD; - request.find_rrset(request.additional, dns.name.root, 65535, dns.rdatatype.OPT, create=True, force_unique=True) - response = dns.query.udp(request, ns) - - for answer in response.answer: - for item in answer.items: - return item \ No newline at end of file diff --git a/eteutils/EteGatherDataListener.py b/eteutils/EteGatherDataListener.py deleted file mode 100644 index 79c02b4..0000000 --- a/eteutils/EteGatherDataListener.py +++ /dev/null @@ -1,126 +0,0 @@ -import os.path -import paramiko -import logging -from sys import stderr - -""" -EteGatherDataListener implements the ROBOT listener API version 2 and is -instantiated via the robot cammmand line option - - --listener EteGatherDataListener:: - -The purpose is to gather and preserve debugging data from each of the application -VMs when an ETE test fails. - -This listener counts the number of test -cases that have failed and, if > 0 at then end of the robot exection (close()), -will connect to each application vm and - -2. upload the gather_data.sh -2. execute gather_data.sh -3. Transfer the resulting zip file to the Robot reports folder - -This will enable the Jenkins job to retrieve the debug data along with the -Robot logs and reports and archive it with the failed job for later retreival. - -Note that the gather_data.sh depends upon the application providing -a /opt/gather_application_data.sh on their respective VMs for the zip file -to be created. -""" - - -class EteGatherDataListener(object): - ROBOT_LIBRARY_SCOPE = 'TEST SUITE' - ROBOT_LISTENER_API_VERSION = 2 - - APPLICATIONS = { - "aai" : "10.0.1.1", - "appc" : "10.0.2.1", - "sdc" : "10.0.3.1", - "dcae" : "10.0.4.1", - "mso" : "10.0.5.1", - "policy" : "10.0.6.1", - "sdnc" : "10.0.7.1", - "vid" : "10.0.8.1", - "portal" : "10.0.9.1", - "message_router" : "10.0.11.1", - "dcae_pstg00" : "10.0.4.101", - "dcae_coll00" : "10.0.4.102", - "dcae_cdap00" : "10.0.4.103", - "dcae_cdap01" : "10.0.4.104", - "dcae_cdap02" : "10.0.4.105" - } - - keyfile = "" - local_gather_data_sh = "" - - def __init__(self, job='10', keyfile='/share/config/key.pvt', shell="gather_data.sh"): - self.tests_passed = 0 - self.tests_failed = 0 - self.output_folder = '' - self.job = job - self.folder= '' - self.keyfile = keyfile - self.local_gather_data_sh = shell - print "EteGatherDataListener instantiated" - - def end_test(self, name, attrs): - if attrs['status'] == 'PASS': - self.tests_passed+=1 - else: - self.tests_failed+=1 - - def output_file(self, path): - if (self.folder != ''): - return - self.folder = os.path.dirname(path) - print(self.folder) - - def close(self): - print "EteGatherDataListener tests failed=" + str(self.tests_failed) - if (self.tests_failed > 0): - self.gather_debug_data() - - def gather_debug_data(self): - - for application in self.APPLICATIONS.keys(): - self.gather_application_data(application, self.APPLICATIONS.get(application)) - - def gather_application_data(self, application, ip): - extra = {"_threadid" : 1} - paramiko.util.log_to_file(self.folder + "/paramiko.log", level=0) - log = logging.getLogger("paramiko") - ssh = paramiko.SSHClient() - try: - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - ssh.connect(ip,username="root", key_filename=self.keyfile) - except paramiko.SSHException: - log.error("Connection Failed to " + ip, extra=extra) - return - try: - gather_data_sh = "/tmp/gather_data.sh" - ftp = ssh.open_sftp() - ftp.put(self.local_gather_data_sh, gather_data_sh) - ftp.close() - stdin, stdout, stderr = ssh.exec_command("/bin/bash "+ gather_data_sh + " " + application + " " + self.job) - error = stderr.read() - if (error != ''): - log.info("stderr:" + error, extra=extra) - ssh.close() - return; - # No error? ASsume we have a file to download. - out = stdout.read() - log.info("stdout:" + out, extra=extra) - filename = application + "_" + self.job + ".tar.gz" - localzip = self.folder + "/" + filename - remotezip = "/tmp/gather_data/" + filename - ftp = ssh.open_sftp() - ftp.get(remotezip, localzip) - ftp.close() - stdin, stdout, stderr = ssh.exec_command("rm -rf " + remotezip); - ssh.close() - except paramiko.SSHException: - ssh.close() - return - - diff --git a/eteutils/HEATUtils.py b/eteutils/HEATUtils.py deleted file mode 100644 index 15c5689..0000000 --- a/eteutils/HEATUtils.py +++ /dev/null @@ -1,87 +0,0 @@ -import json -import yaml -import StringIO -import copy -from hashlib import md5 -from paramiko import RSAKey -from paramiko.ssh_exception import PasswordRequiredException - -class HEATUtils: - """ Utilities useful for constructing OpenStack HEAT requests """ - - def get_yaml(self, template_file): - """Template Yaml To Json reads a YAML Heat template file returns a JSON string that can be used included in an Openstack Add Stack Request""" - if isinstance(template_file, basestring): - fin = open(template_file, 'r') - yamlobj = yaml.load(fin) - return yamlobj - return None - - def template_yaml_to_json(self, template_file): - """Template Yaml To Json reads a YAML Heat template file returns a JSON string that can be used included in an Openstack Add Stack Request""" - if isinstance(template_file, basestring): - fin = open(template_file, 'r') - yamlobj = yaml.load(fin) - fin.close() - if 'heat_template_version' in yamlobj: - datetime = yamlobj['heat_template_version'] - yamlobj['heat_template_version'] = str(datetime) - fout = StringIO.StringIO() - json.dump(yamlobj, fout) - contents = fout.getvalue() - fout.close() - return contents - - def env_yaml_to_json(self, template_file): - """Env Yaml To JSon reads a YAML Heat env file and returns a JSON string that can be used included in an Openstack Add Stack Request""" - if isinstance(template_file, basestring): - fin = open(template_file, 'r') - yamlobj = yaml.load(fin) - fin.close() - if 'parameters' in yamlobj: - fout = StringIO.StringIO() - json.dump(yamlobj['parameters'], fout) - contents = fout.getvalue() - fout.close() - return contents - return None - - def stack_info_parse(self, stack_info): - """ returns a flattened version of the Openstack Find Stack results """ - d = {} - if isinstance(stack_info, dict): - s = stack_info['stack'] - p = s['parameters'] - d = copy.deepcopy(p) - d['id'] = s['id'] - d['name'] = s['stack_name'] - d['stack_status'] = s['stack_status'] - return d - - - def match_fingerprint(self, pvt_file, pw, fingerprint): - try: - sshKey = RSAKey.from_private_key_file(pvt_file, pw) - keybytes = md5(sshKey.asbytes()).hexdigest() - printableFingerprint = ':'.join(a+b for a,b in zip(keybytes[::2], keybytes[1::2])) - return printableFingerprint == fingerprint.__str__() - except PasswordRequiredException: - return False - - def match_private_key_file_to_keypair(self, files, keypair): - for keyfile in files: - if (self.match_fingerprint(keyfile, None, keypair['keypair']['fingerprint'])): - return keyfile - return None - - def get_openstack_server_ip(self, server, network_name="public", ipversion=4): - ipaddr = None - try: - versions = server['addresses'][network_name] - for version in versions: - if version['version'] == ipversion: - ipaddr = version['addr'] - break; - except ValueError: - return ipaddr - return ipaddr \ No newline at end of file diff --git a/eteutils/HTTPUtils.py b/eteutils/HTTPUtils.py deleted file mode 100644 index 9324af7..0000000 --- a/eteutils/HTTPUtils.py +++ /dev/null @@ -1,18 +0,0 @@ -import urllib -import urllib3 -import urlparse - -class HTTPUtils: - """HTTPUtils is common resource for simple http helper keywords.""" - - def url_encode_string(self, barestring): - """URL Encode String takes in a string and converts into 'percent-encoded' string""" - return urllib.quote_plus(barestring) - - def disable_warnings(self): - """ Disable the cert warnings when creating sessions for A&AI API Calls """ - urllib3.disable_warnings() - - def url_parse(self, url): - """ Get pieces of the URL """ - return urlparse.urlparse(url) \ No newline at end of file diff --git a/eteutils/JSONUtils.py b/eteutils/JSONUtils.py deleted file mode 100644 index de5da6b..0000000 --- a/eteutils/JSONUtils.py +++ /dev/null @@ -1,41 +0,0 @@ -import json - -from deepdiff import DeepDiff - -class JSONUtils: - """JSONUtils is common resource for simple json helper keywords.""" - - def json_equals(self, left, right): - """JSON Equals takes in two strings or json objects, converts them into json if needed and then compares them, returning if they are equal or not.""" - if isinstance(left, basestring): - left_json = json.loads(left); - else: - left_json = left; - if isinstance(right, basestring): - right_json = json.loads(right); - else: - right_json = right; - - ddiff = DeepDiff(left_json, right_json, ignore_order=True); - if ddiff == {}: - return True; - else: - return False; - - def make_list_into_dict(self, listOfDicts, key): - """ Converts a list of dicts that contains a field that has a unique key into a dict of dicts """ - d = {} - if isinstance(listOfDicts, list): - for thisDict in listOfDicts: - v = thisDict[key] - d[v] = thisDict - return d - - def find_element_in_array(self, searchedArray, key, value): - """ Takes in an array and a key value, it will return the items in the array that has a key and value that matches what you pass in """ - elements = []; - for item in searchedArray: - if key in item: - if item[key] == value: - elements.append(item); - return elements; \ No newline at end of file diff --git a/eteutils/OSUtils.py b/eteutils/OSUtils.py deleted file mode 100644 index 78968f0..0000000 --- a/eteutils/OSUtils.py +++ /dev/null @@ -1,14 +0,0 @@ -from sys import platform - -class OSUtils: - """ Utilities useful for constructing OpenStack HEAT requests """ - - def get_normalized_os(self): - os = platform - if platform == "linux" or platform == "linux2": - os = 'linux64' - elif platform == "darwin": - os = 'mac64' - elif platform == "win32": - os = platform - return os diff --git a/eteutils/OpenstackLibrary.py b/eteutils/OpenstackLibrary.py deleted file mode 100644 index adb12db..0000000 --- a/eteutils/OpenstackLibrary.py +++ /dev/null @@ -1,124 +0,0 @@ -from robot.libraries.BuiltIn import BuiltIn -import robot.utils -import json - -class OpenstackLibrary: - """OpenstackLibrary manages the connection state and service catalog of an openstack instance.""" - - ROBOT_LIBRARY_SCOPE = 'Global' - - - def __init__(self): - self._cache = robot.utils.ConnectionCache('No connections created') - self.builtin = BuiltIn() - - def save_openstack_auth(self, alias, response,token, version='v2.0'): - """Save Openstack Auth takes in an openstack auth response and saves it to allow easy retrival of token and service catalog""" - self.builtin.log('Creating connection: %s' % alias, 'DEBUG') - jsonResponse = json.loads(response); - jsonResponse['auth_token'] = token - jsonResponse['keystone_api_version'] = version - self._cache.register(jsonResponse, alias=alias) - - def get_openstack_token(self, alias): - """Get Openstack auth token from the current alias""" - response = self._cache.switch(alias) - if isinstance(response, basestring): - jsonResponse = json.loads(response); - else: - jsonResponse = response; - if jsonResponse['keystone_api_version'] == 'v2.0': - return jsonResponse['access']['token']['id'] - else: - return jsonResponse['auth_token'] - - def get_openstack_catalog(self, alias): - """Get Openstack service catalog from the current alias""" - response = self._cache.switch(alias) - if isinstance(response, basestring): - jsonResponse = json.loads(response); - else: - jsonResponse = response; - if jsonResponse['keystone_api_version'] == 'v2.0': - return jsonResponse['access']['serviceCatalog'] - else: - return jsonResponse['token']['catalog'] - - - def get_current_openstack_tenant(self, alias): - """Get Openstack tenant from the current alias""" - response = self._cache.switch(alias) - if isinstance(response, basestring): - jsonResponse = json.loads(response); - else: - jsonResponse = response; - if jsonResponse['keystone_api_version'] == 'v2.0': - return jsonResponse['access']['token']['tenant'] - else: - return jsonResponse['token']['project'] - - def get_current_openstack_tenant_id(self, alias): - """Get Openstack tenant id from the current alias""" - tenant = self.get_current_openstack_tenant(alias); - return tenant['id'] - - def get_openstack_regions(self, alias): - """Get all Openstack regions from the current alias""" - response = self._cache.switch(alias) - if isinstance(response, basestring): - jsonResponse = json.loads(response); - else: - jsonResponse = response; - regions = []; - if jsonResponse['keystone_api_version'] == 'v2.0': - resp = jsonResponse['access']['serviceCatalog'] - else: - resp = jsonResponse['token']['catalog'] - for catalogEntry in resp: - listOfEndpoints = catalogEntry['endpoints']; - for endpoint in listOfEndpoints: - if 'region'in endpoint: - if endpoint['region'] not in regions: - regions.append(endpoint['region']) - return regions; - - def get_openstack_service_url(self, alias, servicetype, region = None, tenant_id = None): - """Get Openstack service catalog from the current alias""" - response = self._cache.switch(alias) - if isinstance(response, basestring): - jsonResponse = json.loads(response); - else: - jsonResponse = response; - endPoint = None; - if jsonResponse['keystone_api_version'] == 'v2.0': - resp = jsonResponse['access']['serviceCatalog'] - else: - resp = jsonResponse['token']['catalog'] - for catalogEntry in resp: - if self.__determine_match(catalogEntry['type'], servicetype): - listOfEndpoints = catalogEntry['endpoints']; - # filter out non matching regions if provided - listOfEndpoints[:] = [x for x in listOfEndpoints if self.__determine_match(x['region'], region)]; - # filter out non matching tenants if provided - # Only provide tenant id when authorizing without qualifying with tenant id - # WindRiver does not return the tenantId on the endpoint in this case. - if tenant_id is not None: - listOfEndpoints[:] = [y for y in listOfEndpoints if self.__determine_match(y['tenantId'], tenant_id)]; - if jsonResponse['keystone_api_version'] == 'v3': - listOfEndpoints[:] = [z for z in listOfEndpoints if self.__determine_match(z['interface'], 'public')]; - if len(listOfEndpoints) > 0: - if jsonResponse['keystone_api_version'] == 'v2.0': - endPoint = listOfEndpoints[0]['publicURL']; - else: - endPoint = listOfEndpoints[0]['url']; - if endPoint == None: - self.builtin.should_not_be_empty("", "Service Endpoint Url should not be empty") - return endPoint; - - def __determine_match(self, listItem, item): - if item is None: - return True; - elif listItem == item: - return True; - else: - return False; \ No newline at end of file diff --git a/eteutils/RequestsClientCert.py b/eteutils/RequestsClientCert.py deleted file mode 100644 index e1fd66f..0000000 --- a/eteutils/RequestsClientCert.py +++ /dev/null @@ -1,7 +0,0 @@ - -class RequestsClientCert: - """RequestsClientCert allows adding a client cert to the Requests Robot Library.""" - - def add_client_cert(self, session, cert): - """Add Client Cert takes in a requests session object and a string path to the cert""" - session.cert = cert \ No newline at end of file diff --git a/eteutils/StringTemplater.py b/eteutils/StringTemplater.py deleted file mode 100644 index 43d107e..0000000 --- a/eteutils/StringTemplater.py +++ /dev/null @@ -1,9 +0,0 @@ -import json -from string import Template - -class StringTemplater: - """StringTemplater is common resource for templating with strings.""" - - def template_string(self, template, values): - """Template String takes in a string and its values and converts it using the string.Template class""" - return Template(template).substitute(values) \ No newline at end of file diff --git a/eteutils/TemplatingEngine.py b/eteutils/TemplatingEngine.py deleted file mode 100644 index 0f579e7..0000000 --- a/eteutils/TemplatingEngine.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2019 AT&T Intellectual Property. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -from jinja2 import Environment, FileSystemLoader, select_autoescape - - -class TemplatingEngine: - """TemplateImporter is common resource for templating with strings.""" - - jinja_env = None - - def __init__(self, templates_folder): - self.jinja_env = Environment( - loader=FileSystemLoader(templates_folder), - autoescape=select_autoescape(['html', 'xml']) - ) - - def apply_template(self, template_location, values): - """returns a string that is the jinja template in template_location filled in via the dictionary in values """ - print - template = self.jinja_env.get_template(template_location) - return template.render(values) \ No newline at end of file diff --git a/eteutils/UUID.py b/eteutils/UUID.py deleted file mode 100644 index 35c26a7..0000000 --- a/eteutils/UUID.py +++ /dev/null @@ -1,15 +0,0 @@ -import uuid -import time -import datetime - -class UUID: - """UUID is a simple library that generates a uuid""" - - def generate_UUID(self): - """generate a uuid""" - return uuid.uuid4() - - def generate_MilliTimestamp_UUID(self): - """generate a millisecond timestamp uuid""" - then = datetime.datetime.now() - return int(time.mktime(then.timetuple())*1e3 + then.microsecond/1e3) \ No newline at end of file diff --git a/eteutils/csvLibrary.py b/eteutils/csvLibrary.py deleted file mode 100644 index b38b4a5..0000000 --- a/eteutils/csvLibrary.py +++ /dev/null @@ -1,16 +0,0 @@ -import csv -class csvLibrary(object): - - def read_csv_file(self, filename): - '''This creates a keyword named "Read CSV File" - - This keyword takes one argument, which is a path to a .csv file. It - returns a list of rows, with each row being a list of the data in - each column. - ''' - data = [] - with open(filename, 'rb') as csvfile: - reader = csv.reader(csvfile) - for row in reader: - data.append(row) - return data diff --git a/loadtest/RunEte.py b/loadtest/RunEte.py deleted file mode 100644 index 5012e7d..0000000 --- a/loadtest/RunEte.py +++ /dev/null @@ -1,39 +0,0 @@ -''' -Created on Apr 7, 2017 - -@author: jf9860 -''' -from threading import Thread -import subprocess -import os -from datetime import datetime -import logging - -class RunEte(Thread): - ''' - classdocs - ''' - robot_test = "" - robot_command = "runEteTag.sh" - soaksubfolder = "" - test_number =0 - - def __init__(self, test_name, soaksubfolder, test_number): - ''' - Constructor - ''' - super(RunEte, self).__init__() - self.robot_test = test_name - self.soaksubfolder = soaksubfolder - self.test_number = test_number - - def run(self): - logging.info("{} ({}) started - {}".format(self.getName(), self.robot_test, str(datetime.now()))) - try: - ''' Add the '/' here so that the shell doesn't require a subfolder... ''' - env = dict(os.environ, SOAKSUBFOLDER=self.soaksubfolder + "/") - output = subprocess.check_output(["bash", self.robot_command, self.robot_test, self.test_number], shell=False, env=env) - logging.info("{} ({}) {}".format(self.getName(), self.robot_test, output)) - except Exception, e: - logging.error("{} ({}) Unexpected error {}".format(self.getName(), self.robot_test, repr(e))) - logging.info("{} ({}) ended - {}".format(self.getName(), self.robot_test, str(datetime.now()))) diff --git a/loadtest/TestConfig.py b/loadtest/TestConfig.py deleted file mode 100644 index b9b8112..0000000 --- a/loadtest/TestConfig.py +++ /dev/null @@ -1,59 +0,0 @@ -''' -Created on Apr 7, 2017 - -@author: jf9860 -''' -import json - -class TestConfig(object): - ''' - The profile defines a cycle of tests. Each entry is defined as - [, [= len(self.config.profile)): - profileindex = 0 - profile = self.config.profile[profileindex] - sleeptime = profile[0] - currenttime = time.time() - if ((currenttime + sleeptime) < endtime): - time.sleep(sleeptime) - self.schedule(profile) - profileindex = profileindex + 1 - currenttime = time.time() - else: - currenttime = endtime - - for threadname in self.threads: - logging.info("TestController waiting on " + threadname) - t = self.threads[threadname] - t.join() - logging.info("Soak test completed") - - def schedule(self, profile): - self.remove_completed_threads() - tests = profile[1] - for test in tests: - self.schedule_one(test) - - def schedule_one(self, test): - self.test_number = self.test_number + 1 - self.threadid = self.threadid + 1 - threadname = "RunEte_" + str(self.threadid) - ''' test for max threads ''' - t = RunEte(test, self.soaksubfolder, str(self.test_number)) - t.setName(threadname) - t.start() - self.threads[threadname] = t - - - def remove_completed_threads(self): - toremove = [] - for threadname in self.threads: - t = self.threads[threadname] - if (t.isAlive() == False): - toremove.append(threadname) - for threadname in toremove: - logging.info("Removing " + threadname) - del(self.threads[threadname]) \ No newline at end of file diff --git a/loadtest/TestMain.py b/loadtest/TestMain.py deleted file mode 100644 index 81c635f..0000000 --- a/loadtest/TestMain.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 -''' -loadtest.TestMain -- shortdesc - -loadtest.TestMain is a description - -It defines classes_and_methods - -@author: user_name - -@copyright: 2017 organization_name. All rights reserved. - -@license: license - -@contact: user_email -@deffield updated: Updated -''' - -import sys -import os - -from optparse import OptionParser, Values - -from loadtest.TestController import TestController - -__all__ = [] -__version__ = 0.1 -__date__ = '2017-04-07' -__updated__ = '2017-04-07' - -DEBUG = 1 -TESTRUN = 0 -PROFILE = 0 -import time -import logging - -def main(argv=None): - '''Command line options.''' - program_name = os.path.basename(sys.argv[0]) - program_version = "v0.1" - program_build_date = "%s" % __updated__ - - program_version_string = '%%prog %s (%s)' % (program_version, program_build_date) - #program_usage = '''usage: spam two eggs''' # optional - will be autogenerated by optparse - program_longdesc = '''''' # optional - give further explanation about what the program does - program_license = "Copyright 2017 user_name (organization_name) \ - Licensed under the Apache License 2.0\nhttp://www.apache.org/licenses/LICENSE-2.0" - - if argv is None: - argv = sys.argv[1:] - try: - # setup option parser - parser = OptionParser(version=program_version_string, epilog=program_longdesc, description=program_license) - parser.add_option("-d", "--duration", dest="duration", help="duration of soak test in seconds [default: %default]", type=int) - parser.add_option("-l", "--logfile", dest="logfile", help="Full path soak log file name") - parser.add_option("-c", "--cyclelength", dest="cyclelength", help="Length of a single cycle through the config.\nMust be longer than a single iteration", type=int) - parser.add_option("-p", "--profile", dest="profile", help="Filename of json profile file") - parser.set_defaults(logfile="") - (opts, args) = parser.parse_args(argv) - - if (opts.logfile != ""): - logging.basicConfig(filename=opts.logfile, level=logging.DEBUG) - else: - logging.basicConfig(level=logging.DEBUG) - controller = TestController(opts) - controller.execute() - - except Exception, e: - indent = len(program_name) * " " - sys.stderr.write(program_name + ": " + repr(e) + "\n") - sys.stderr.write(indent + " for help use --help") - return 2 - - -if __name__ == "__main__": - if DEBUG: - print "debug" - if TESTRUN: - import doctest - doctest.testmod() - if PROFILE: - import cProfile - import pstats - profile_filename = 'loadtest.TestMain_profile.txt' - cProfile.run('main()', profile_filename) - statsfile = open("profile_stats.txt", "wb") - p = pstats.Stats(profile_filename, stream=statsfile) - stats = p.strip_dirs().sort_stats('cumulative') - stats.print_stats() - statsfile.close() - sys.exit(0) - sys.exit(main()) \ No newline at end of file diff --git a/loadtest/__init__.py b/loadtest/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pom.xml b/pom.xml index b196873..ecb6d7f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,78 +5,10 @@ org.onap.testsuite python-testing-utils testsuite-python-testing-utils - 0.3.0-SNAPSHOT - Scripts written to be used during robot framework testing - - org.onap.oparent - oparent-python - 1.2.3 - - - ${project.artifactId}-${python_version}-py2-none-any.whl - ${project.basedir} - https://nexus3.onap.org/repository/PyPi.snapshot/ - - - - - org.codehaus.mojo - build-helper-maven-plugin - - - attach-artifacts - - - - ${project.build.directory}/maven-python/dist/${wheel.name} - whl - - - false - - - - - - com.github.UltimateDogg - maven-python-distribute-plugin - - - package - - package - - - - process - - process-sources - - - - deploy - - deploy - - - - - ${python.pypi.repository} - ${python.sourceDirectory} - - - - exec-maven-plugin - org.codehaus.mojo - - - tox-test - - ${project.build.directory}/maven-python/eteutils - - - - - - + 0.4.0-SNAPSHOT + parent project for scripts written to be used during robot framework testing + + + robotframework-onap + \ No newline at end of file diff --git a/robotframework-onap/.gitignore b/robotframework-onap/.gitignore new file mode 100644 index 0000000..70a57dd --- /dev/null +++ b/robotframework-onap/.gitignore @@ -0,0 +1,262 @@ +.tox/* +python_openecomp_eteutils.egg-info/* + +# Created by https://www.gitignore.io/api/node,sonar,maven,eclipse,sonarqube,intellij+all + +### Eclipse ### + +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +### Eclipse Patch ### +# Eclipse Core +.project + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Annotation Processing +.apt_generated + +.sts4-cache/ + +### Intellij+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/ + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless + +### Sonar ### +#Sonar generated dir +/.sonar/ + +### SonarQube ### +# SonarQube ignore files. +# +# https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner +# Sonar Scanner working directories +.sonar/ +.scannerwork/ + +# http://www.sonarlint.org/commandline/ +# SonarLint working directories, configuration files (including credentials) +.sonarlint/ + + +# End of https://www.gitignore.io/api/node,sonar,maven,eclipse,sonarqube,intellij+all +.flattened-pom.xml \ No newline at end of file diff --git a/robotframework-onap/README.TXT b/robotframework-onap/README.TXT new file mode 100644 index 0000000..9989546 --- /dev/null +++ b/robotframework-onap/README.TXT @@ -0,0 +1,6 @@ +Robotframework-ONAP +======================= + +Robot framework plugin to work with onap + +to install locally, checkout this repo and then run 'pip install -e .' in the root \ No newline at end of file diff --git a/robotframework-onap/eteutils/DNSUtils.py b/robotframework-onap/eteutils/DNSUtils.py new file mode 100644 index 0000000..65ae68b --- /dev/null +++ b/robotframework-onap/eteutils/DNSUtils.py @@ -0,0 +1,17 @@ +import dns.message +import dns.name +import dns.query + +class DNSUtils: + """ Utilities useful for DNS requests """ + + def dns_request(self, domain, ns): + """ return the ip address of the given domain name from the given nameserver """ + request = dns.message.make_query(domain, dns.rdatatype.A); + request.flags |= dns.flags.AD; + request.find_rrset(request.additional, dns.name.root, 65535, dns.rdatatype.OPT, create=True, force_unique=True) + response = dns.query.udp(request, ns) + + for answer in response.answer: + for item in answer.items: + return item \ No newline at end of file diff --git a/robotframework-onap/eteutils/EteGatherDataListener.py b/robotframework-onap/eteutils/EteGatherDataListener.py new file mode 100644 index 0000000..79c02b4 --- /dev/null +++ b/robotframework-onap/eteutils/EteGatherDataListener.py @@ -0,0 +1,126 @@ +import os.path +import paramiko +import logging +from sys import stderr + +""" +EteGatherDataListener implements the ROBOT listener API version 2 and is +instantiated via the robot cammmand line option + + --listener EteGatherDataListener:: + +The purpose is to gather and preserve debugging data from each of the application +VMs when an ETE test fails. + +This listener counts the number of test +cases that have failed and, if > 0 at then end of the robot exection (close()), +will connect to each application vm and + +2. upload the gather_data.sh +2. execute gather_data.sh +3. Transfer the resulting zip file to the Robot reports folder + +This will enable the Jenkins job to retrieve the debug data along with the +Robot logs and reports and archive it with the failed job for later retreival. + +Note that the gather_data.sh depends upon the application providing +a /opt/gather_application_data.sh on their respective VMs for the zip file +to be created. +""" + + +class EteGatherDataListener(object): + ROBOT_LIBRARY_SCOPE = 'TEST SUITE' + ROBOT_LISTENER_API_VERSION = 2 + + APPLICATIONS = { + "aai" : "10.0.1.1", + "appc" : "10.0.2.1", + "sdc" : "10.0.3.1", + "dcae" : "10.0.4.1", + "mso" : "10.0.5.1", + "policy" : "10.0.6.1", + "sdnc" : "10.0.7.1", + "vid" : "10.0.8.1", + "portal" : "10.0.9.1", + "message_router" : "10.0.11.1", + "dcae_pstg00" : "10.0.4.101", + "dcae_coll00" : "10.0.4.102", + "dcae_cdap00" : "10.0.4.103", + "dcae_cdap01" : "10.0.4.104", + "dcae_cdap02" : "10.0.4.105" + } + + keyfile = "" + local_gather_data_sh = "" + + def __init__(self, job='10', keyfile='/share/config/key.pvt', shell="gather_data.sh"): + self.tests_passed = 0 + self.tests_failed = 0 + self.output_folder = '' + self.job = job + self.folder= '' + self.keyfile = keyfile + self.local_gather_data_sh = shell + print "EteGatherDataListener instantiated" + + def end_test(self, name, attrs): + if attrs['status'] == 'PASS': + self.tests_passed+=1 + else: + self.tests_failed+=1 + + def output_file(self, path): + if (self.folder != ''): + return + self.folder = os.path.dirname(path) + print(self.folder) + + def close(self): + print "EteGatherDataListener tests failed=" + str(self.tests_failed) + if (self.tests_failed > 0): + self.gather_debug_data() + + def gather_debug_data(self): + + for application in self.APPLICATIONS.keys(): + self.gather_application_data(application, self.APPLICATIONS.get(application)) + + def gather_application_data(self, application, ip): + extra = {"_threadid" : 1} + paramiko.util.log_to_file(self.folder + "/paramiko.log", level=0) + log = logging.getLogger("paramiko") + ssh = paramiko.SSHClient() + try: + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect(ip,username="root", key_filename=self.keyfile) + except paramiko.SSHException: + log.error("Connection Failed to " + ip, extra=extra) + return + try: + gather_data_sh = "/tmp/gather_data.sh" + ftp = ssh.open_sftp() + ftp.put(self.local_gather_data_sh, gather_data_sh) + ftp.close() + stdin, stdout, stderr = ssh.exec_command("/bin/bash "+ gather_data_sh + " " + application + " " + self.job) + error = stderr.read() + if (error != ''): + log.info("stderr:" + error, extra=extra) + ssh.close() + return; + # No error? ASsume we have a file to download. + out = stdout.read() + log.info("stdout:" + out, extra=extra) + filename = application + "_" + self.job + ".tar.gz" + localzip = self.folder + "/" + filename + remotezip = "/tmp/gather_data/" + filename + ftp = ssh.open_sftp() + ftp.get(remotezip, localzip) + ftp.close() + stdin, stdout, stderr = ssh.exec_command("rm -rf " + remotezip); + ssh.close() + except paramiko.SSHException: + ssh.close() + return + + diff --git a/robotframework-onap/eteutils/HEATUtils.py b/robotframework-onap/eteutils/HEATUtils.py new file mode 100644 index 0000000..15c5689 --- /dev/null +++ b/robotframework-onap/eteutils/HEATUtils.py @@ -0,0 +1,87 @@ +import json +import yaml +import StringIO +import copy +from hashlib import md5 +from paramiko import RSAKey +from paramiko.ssh_exception import PasswordRequiredException + +class HEATUtils: + """ Utilities useful for constructing OpenStack HEAT requests """ + + def get_yaml(self, template_file): + """Template Yaml To Json reads a YAML Heat template file returns a JSON string that can be used included in an Openstack Add Stack Request""" + if isinstance(template_file, basestring): + fin = open(template_file, 'r') + yamlobj = yaml.load(fin) + return yamlobj + return None + + def template_yaml_to_json(self, template_file): + """Template Yaml To Json reads a YAML Heat template file returns a JSON string that can be used included in an Openstack Add Stack Request""" + if isinstance(template_file, basestring): + fin = open(template_file, 'r') + yamlobj = yaml.load(fin) + fin.close() + if 'heat_template_version' in yamlobj: + datetime = yamlobj['heat_template_version'] + yamlobj['heat_template_version'] = str(datetime) + fout = StringIO.StringIO() + json.dump(yamlobj, fout) + contents = fout.getvalue() + fout.close() + return contents + + def env_yaml_to_json(self, template_file): + """Env Yaml To JSon reads a YAML Heat env file and returns a JSON string that can be used included in an Openstack Add Stack Request""" + if isinstance(template_file, basestring): + fin = open(template_file, 'r') + yamlobj = yaml.load(fin) + fin.close() + if 'parameters' in yamlobj: + fout = StringIO.StringIO() + json.dump(yamlobj['parameters'], fout) + contents = fout.getvalue() + fout.close() + return contents + return None + + def stack_info_parse(self, stack_info): + """ returns a flattened version of the Openstack Find Stack results """ + d = {} + if isinstance(stack_info, dict): + s = stack_info['stack'] + p = s['parameters'] + d = copy.deepcopy(p) + d['id'] = s['id'] + d['name'] = s['stack_name'] + d['stack_status'] = s['stack_status'] + return d + + + def match_fingerprint(self, pvt_file, pw, fingerprint): + try: + sshKey = RSAKey.from_private_key_file(pvt_file, pw) + keybytes = md5(sshKey.asbytes()).hexdigest() + printableFingerprint = ':'.join(a+b for a,b in zip(keybytes[::2], keybytes[1::2])) + return printableFingerprint == fingerprint.__str__() + except PasswordRequiredException: + return False + + def match_private_key_file_to_keypair(self, files, keypair): + for keyfile in files: + if (self.match_fingerprint(keyfile, None, keypair['keypair']['fingerprint'])): + return keyfile + return None + + def get_openstack_server_ip(self, server, network_name="public", ipversion=4): + ipaddr = None + try: + versions = server['addresses'][network_name] + for version in versions: + if version['version'] == ipversion: + ipaddr = version['addr'] + break; + except ValueError: + return ipaddr + return ipaddr \ No newline at end of file diff --git a/robotframework-onap/eteutils/HTTPUtils.py b/robotframework-onap/eteutils/HTTPUtils.py new file mode 100644 index 0000000..9324af7 --- /dev/null +++ b/robotframework-onap/eteutils/HTTPUtils.py @@ -0,0 +1,18 @@ +import urllib +import urllib3 +import urlparse + +class HTTPUtils: + """HTTPUtils is common resource for simple http helper keywords.""" + + def url_encode_string(self, barestring): + """URL Encode String takes in a string and converts into 'percent-encoded' string""" + return urllib.quote_plus(barestring) + + def disable_warnings(self): + """ Disable the cert warnings when creating sessions for A&AI API Calls """ + urllib3.disable_warnings() + + def url_parse(self, url): + """ Get pieces of the URL """ + return urlparse.urlparse(url) \ No newline at end of file diff --git a/robotframework-onap/eteutils/JSONUtils.py b/robotframework-onap/eteutils/JSONUtils.py new file mode 100644 index 0000000..de5da6b --- /dev/null +++ b/robotframework-onap/eteutils/JSONUtils.py @@ -0,0 +1,41 @@ +import json + +from deepdiff import DeepDiff + +class JSONUtils: + """JSONUtils is common resource for simple json helper keywords.""" + + def json_equals(self, left, right): + """JSON Equals takes in two strings or json objects, converts them into json if needed and then compares them, returning if they are equal or not.""" + if isinstance(left, basestring): + left_json = json.loads(left); + else: + left_json = left; + if isinstance(right, basestring): + right_json = json.loads(right); + else: + right_json = right; + + ddiff = DeepDiff(left_json, right_json, ignore_order=True); + if ddiff == {}: + return True; + else: + return False; + + def make_list_into_dict(self, listOfDicts, key): + """ Converts a list of dicts that contains a field that has a unique key into a dict of dicts """ + d = {} + if isinstance(listOfDicts, list): + for thisDict in listOfDicts: + v = thisDict[key] + d[v] = thisDict + return d + + def find_element_in_array(self, searchedArray, key, value): + """ Takes in an array and a key value, it will return the items in the array that has a key and value that matches what you pass in """ + elements = []; + for item in searchedArray: + if key in item: + if item[key] == value: + elements.append(item); + return elements; \ No newline at end of file diff --git a/robotframework-onap/eteutils/OSUtils.py b/robotframework-onap/eteutils/OSUtils.py new file mode 100644 index 0000000..78968f0 --- /dev/null +++ b/robotframework-onap/eteutils/OSUtils.py @@ -0,0 +1,14 @@ +from sys import platform + +class OSUtils: + """ Utilities useful for constructing OpenStack HEAT requests """ + + def get_normalized_os(self): + os = platform + if platform == "linux" or platform == "linux2": + os = 'linux64' + elif platform == "darwin": + os = 'mac64' + elif platform == "win32": + os = platform + return os diff --git a/robotframework-onap/eteutils/OpenstackLibrary.py b/robotframework-onap/eteutils/OpenstackLibrary.py new file mode 100644 index 0000000..adb12db --- /dev/null +++ b/robotframework-onap/eteutils/OpenstackLibrary.py @@ -0,0 +1,124 @@ +from robot.libraries.BuiltIn import BuiltIn +import robot.utils +import json + +class OpenstackLibrary: + """OpenstackLibrary manages the connection state and service catalog of an openstack instance.""" + + ROBOT_LIBRARY_SCOPE = 'Global' + + + def __init__(self): + self._cache = robot.utils.ConnectionCache('No connections created') + self.builtin = BuiltIn() + + def save_openstack_auth(self, alias, response,token, version='v2.0'): + """Save Openstack Auth takes in an openstack auth response and saves it to allow easy retrival of token and service catalog""" + self.builtin.log('Creating connection: %s' % alias, 'DEBUG') + jsonResponse = json.loads(response); + jsonResponse['auth_token'] = token + jsonResponse['keystone_api_version'] = version + self._cache.register(jsonResponse, alias=alias) + + def get_openstack_token(self, alias): + """Get Openstack auth token from the current alias""" + response = self._cache.switch(alias) + if isinstance(response, basestring): + jsonResponse = json.loads(response); + else: + jsonResponse = response; + if jsonResponse['keystone_api_version'] == 'v2.0': + return jsonResponse['access']['token']['id'] + else: + return jsonResponse['auth_token'] + + def get_openstack_catalog(self, alias): + """Get Openstack service catalog from the current alias""" + response = self._cache.switch(alias) + if isinstance(response, basestring): + jsonResponse = json.loads(response); + else: + jsonResponse = response; + if jsonResponse['keystone_api_version'] == 'v2.0': + return jsonResponse['access']['serviceCatalog'] + else: + return jsonResponse['token']['catalog'] + + + def get_current_openstack_tenant(self, alias): + """Get Openstack tenant from the current alias""" + response = self._cache.switch(alias) + if isinstance(response, basestring): + jsonResponse = json.loads(response); + else: + jsonResponse = response; + if jsonResponse['keystone_api_version'] == 'v2.0': + return jsonResponse['access']['token']['tenant'] + else: + return jsonResponse['token']['project'] + + def get_current_openstack_tenant_id(self, alias): + """Get Openstack tenant id from the current alias""" + tenant = self.get_current_openstack_tenant(alias); + return tenant['id'] + + def get_openstack_regions(self, alias): + """Get all Openstack regions from the current alias""" + response = self._cache.switch(alias) + if isinstance(response, basestring): + jsonResponse = json.loads(response); + else: + jsonResponse = response; + regions = []; + if jsonResponse['keystone_api_version'] == 'v2.0': + resp = jsonResponse['access']['serviceCatalog'] + else: + resp = jsonResponse['token']['catalog'] + for catalogEntry in resp: + listOfEndpoints = catalogEntry['endpoints']; + for endpoint in listOfEndpoints: + if 'region'in endpoint: + if endpoint['region'] not in regions: + regions.append(endpoint['region']) + return regions; + + def get_openstack_service_url(self, alias, servicetype, region = None, tenant_id = None): + """Get Openstack service catalog from the current alias""" + response = self._cache.switch(alias) + if isinstance(response, basestring): + jsonResponse = json.loads(response); + else: + jsonResponse = response; + endPoint = None; + if jsonResponse['keystone_api_version'] == 'v2.0': + resp = jsonResponse['access']['serviceCatalog'] + else: + resp = jsonResponse['token']['catalog'] + for catalogEntry in resp: + if self.__determine_match(catalogEntry['type'], servicetype): + listOfEndpoints = catalogEntry['endpoints']; + # filter out non matching regions if provided + listOfEndpoints[:] = [x for x in listOfEndpoints if self.__determine_match(x['region'], region)]; + # filter out non matching tenants if provided + # Only provide tenant id when authorizing without qualifying with tenant id + # WindRiver does not return the tenantId on the endpoint in this case. + if tenant_id is not None: + listOfEndpoints[:] = [y for y in listOfEndpoints if self.__determine_match(y['tenantId'], tenant_id)]; + if jsonResponse['keystone_api_version'] == 'v3': + listOfEndpoints[:] = [z for z in listOfEndpoints if self.__determine_match(z['interface'], 'public')]; + if len(listOfEndpoints) > 0: + if jsonResponse['keystone_api_version'] == 'v2.0': + endPoint = listOfEndpoints[0]['publicURL']; + else: + endPoint = listOfEndpoints[0]['url']; + if endPoint == None: + self.builtin.should_not_be_empty("", "Service Endpoint Url should not be empty") + return endPoint; + + def __determine_match(self, listItem, item): + if item is None: + return True; + elif listItem == item: + return True; + else: + return False; \ No newline at end of file diff --git a/robotframework-onap/eteutils/RequestsClientCert.py b/robotframework-onap/eteutils/RequestsClientCert.py new file mode 100644 index 0000000..e1fd66f --- /dev/null +++ b/robotframework-onap/eteutils/RequestsClientCert.py @@ -0,0 +1,7 @@ + +class RequestsClientCert: + """RequestsClientCert allows adding a client cert to the Requests Robot Library.""" + + def add_client_cert(self, session, cert): + """Add Client Cert takes in a requests session object and a string path to the cert""" + session.cert = cert \ No newline at end of file diff --git a/robotframework-onap/eteutils/StringTemplater.py b/robotframework-onap/eteutils/StringTemplater.py new file mode 100644 index 0000000..43d107e --- /dev/null +++ b/robotframework-onap/eteutils/StringTemplater.py @@ -0,0 +1,9 @@ +import json +from string import Template + +class StringTemplater: + """StringTemplater is common resource for templating with strings.""" + + def template_string(self, template, values): + """Template String takes in a string and its values and converts it using the string.Template class""" + return Template(template).substitute(values) \ No newline at end of file diff --git a/robotframework-onap/eteutils/TemplatingEngine.py b/robotframework-onap/eteutils/TemplatingEngine.py new file mode 100644 index 0000000..0f579e7 --- /dev/null +++ b/robotframework-onap/eteutils/TemplatingEngine.py @@ -0,0 +1,34 @@ +# Copyright 2019 AT&T Intellectual Property. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from jinja2 import Environment, FileSystemLoader, select_autoescape + + +class TemplatingEngine: + """TemplateImporter is common resource for templating with strings.""" + + jinja_env = None + + def __init__(self, templates_folder): + self.jinja_env = Environment( + loader=FileSystemLoader(templates_folder), + autoescape=select_autoescape(['html', 'xml']) + ) + + def apply_template(self, template_location, values): + """returns a string that is the jinja template in template_location filled in via the dictionary in values """ + print + template = self.jinja_env.get_template(template_location) + return template.render(values) \ No newline at end of file diff --git a/robotframework-onap/eteutils/UUID.py b/robotframework-onap/eteutils/UUID.py new file mode 100644 index 0000000..35c26a7 --- /dev/null +++ b/robotframework-onap/eteutils/UUID.py @@ -0,0 +1,15 @@ +import uuid +import time +import datetime + +class UUID: + """UUID is a simple library that generates a uuid""" + + def generate_UUID(self): + """generate a uuid""" + return uuid.uuid4() + + def generate_MilliTimestamp_UUID(self): + """generate a millisecond timestamp uuid""" + then = datetime.datetime.now() + return int(time.mktime(then.timetuple())*1e3 + then.microsecond/1e3) \ No newline at end of file diff --git a/robotframework-onap/eteutils/__init__.py b/robotframework-onap/eteutils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/robotframework-onap/eteutils/csvLibrary.py b/robotframework-onap/eteutils/csvLibrary.py new file mode 100644 index 0000000..b38b4a5 --- /dev/null +++ b/robotframework-onap/eteutils/csvLibrary.py @@ -0,0 +1,16 @@ +import csv +class csvLibrary(object): + + def read_csv_file(self, filename): + '''This creates a keyword named "Read CSV File" + + This keyword takes one argument, which is a path to a .csv file. It + returns a list of rows, with each row being a list of the data in + each column. + ''' + data = [] + with open(filename, 'rb') as csvfile: + reader = csv.reader(csvfile) + for row in reader: + data.append(row) + return data diff --git a/robotframework-onap/loadtest/RunEte.py b/robotframework-onap/loadtest/RunEte.py new file mode 100644 index 0000000..5012e7d --- /dev/null +++ b/robotframework-onap/loadtest/RunEte.py @@ -0,0 +1,39 @@ +''' +Created on Apr 7, 2017 + +@author: jf9860 +''' +from threading import Thread +import subprocess +import os +from datetime import datetime +import logging + +class RunEte(Thread): + ''' + classdocs + ''' + robot_test = "" + robot_command = "runEteTag.sh" + soaksubfolder = "" + test_number =0 + + def __init__(self, test_name, soaksubfolder, test_number): + ''' + Constructor + ''' + super(RunEte, self).__init__() + self.robot_test = test_name + self.soaksubfolder = soaksubfolder + self.test_number = test_number + + def run(self): + logging.info("{} ({}) started - {}".format(self.getName(), self.robot_test, str(datetime.now()))) + try: + ''' Add the '/' here so that the shell doesn't require a subfolder... ''' + env = dict(os.environ, SOAKSUBFOLDER=self.soaksubfolder + "/") + output = subprocess.check_output(["bash", self.robot_command, self.robot_test, self.test_number], shell=False, env=env) + logging.info("{} ({}) {}".format(self.getName(), self.robot_test, output)) + except Exception, e: + logging.error("{} ({}) Unexpected error {}".format(self.getName(), self.robot_test, repr(e))) + logging.info("{} ({}) ended - {}".format(self.getName(), self.robot_test, str(datetime.now()))) diff --git a/robotframework-onap/loadtest/TestConfig.py b/robotframework-onap/loadtest/TestConfig.py new file mode 100644 index 0000000..b9b8112 --- /dev/null +++ b/robotframework-onap/loadtest/TestConfig.py @@ -0,0 +1,59 @@ +''' +Created on Apr 7, 2017 + +@author: jf9860 +''' +import json + +class TestConfig(object): + ''' + The profile defines a cycle of tests. Each entry is defined as + [, [= len(self.config.profile)): + profileindex = 0 + profile = self.config.profile[profileindex] + sleeptime = profile[0] + currenttime = time.time() + if ((currenttime + sleeptime) < endtime): + time.sleep(sleeptime) + self.schedule(profile) + profileindex = profileindex + 1 + currenttime = time.time() + else: + currenttime = endtime + + for threadname in self.threads: + logging.info("TestController waiting on " + threadname) + t = self.threads[threadname] + t.join() + logging.info("Soak test completed") + + def schedule(self, profile): + self.remove_completed_threads() + tests = profile[1] + for test in tests: + self.schedule_one(test) + + def schedule_one(self, test): + self.test_number = self.test_number + 1 + self.threadid = self.threadid + 1 + threadname = "RunEte_" + str(self.threadid) + ''' test for max threads ''' + t = RunEte(test, self.soaksubfolder, str(self.test_number)) + t.setName(threadname) + t.start() + self.threads[threadname] = t + + + def remove_completed_threads(self): + toremove = [] + for threadname in self.threads: + t = self.threads[threadname] + if (t.isAlive() == False): + toremove.append(threadname) + for threadname in toremove: + logging.info("Removing " + threadname) + del(self.threads[threadname]) \ No newline at end of file diff --git a/robotframework-onap/loadtest/TestMain.py b/robotframework-onap/loadtest/TestMain.py new file mode 100644 index 0000000..81c635f --- /dev/null +++ b/robotframework-onap/loadtest/TestMain.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# encoding: utf-8 +''' +loadtest.TestMain -- shortdesc + +loadtest.TestMain is a description + +It defines classes_and_methods + +@author: user_name + +@copyright: 2017 organization_name. All rights reserved. + +@license: license + +@contact: user_email +@deffield updated: Updated +''' + +import sys +import os + +from optparse import OptionParser, Values + +from loadtest.TestController import TestController + +__all__ = [] +__version__ = 0.1 +__date__ = '2017-04-07' +__updated__ = '2017-04-07' + +DEBUG = 1 +TESTRUN = 0 +PROFILE = 0 +import time +import logging + +def main(argv=None): + '''Command line options.''' + program_name = os.path.basename(sys.argv[0]) + program_version = "v0.1" + program_build_date = "%s" % __updated__ + + program_version_string = '%%prog %s (%s)' % (program_version, program_build_date) + #program_usage = '''usage: spam two eggs''' # optional - will be autogenerated by optparse + program_longdesc = '''''' # optional - give further explanation about what the program does + program_license = "Copyright 2017 user_name (organization_name) \ + Licensed under the Apache License 2.0\nhttp://www.apache.org/licenses/LICENSE-2.0" + + if argv is None: + argv = sys.argv[1:] + try: + # setup option parser + parser = OptionParser(version=program_version_string, epilog=program_longdesc, description=program_license) + parser.add_option("-d", "--duration", dest="duration", help="duration of soak test in seconds [default: %default]", type=int) + parser.add_option("-l", "--logfile", dest="logfile", help="Full path soak log file name") + parser.add_option("-c", "--cyclelength", dest="cyclelength", help="Length of a single cycle through the config.\nMust be longer than a single iteration", type=int) + parser.add_option("-p", "--profile", dest="profile", help="Filename of json profile file") + parser.set_defaults(logfile="") + (opts, args) = parser.parse_args(argv) + + if (opts.logfile != ""): + logging.basicConfig(filename=opts.logfile, level=logging.DEBUG) + else: + logging.basicConfig(level=logging.DEBUG) + controller = TestController(opts) + controller.execute() + + except Exception, e: + indent = len(program_name) * " " + sys.stderr.write(program_name + ": " + repr(e) + "\n") + sys.stderr.write(indent + " for help use --help") + return 2 + + +if __name__ == "__main__": + if DEBUG: + print "debug" + if TESTRUN: + import doctest + doctest.testmod() + if PROFILE: + import cProfile + import pstats + profile_filename = 'loadtest.TestMain_profile.txt' + cProfile.run('main()', profile_filename) + statsfile = open("profile_stats.txt", "wb") + p = pstats.Stats(profile_filename, stream=statsfile) + stats = p.strip_dirs().sort_stats('cumulative') + stats.print_stats() + statsfile.close() + sys.exit(0) + sys.exit(main()) \ No newline at end of file diff --git a/robotframework-onap/loadtest/__init__.py b/robotframework-onap/loadtest/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/robotframework-onap/pom.xml b/robotframework-onap/pom.xml new file mode 100644 index 0000000..8c6c05b --- /dev/null +++ b/robotframework-onap/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + pom + org.onap.testsuite.python-testing-utils + robotframework-onap + robotframework-onap + 0.4.0-SNAPSHOT + Scripts written to be used during robot framework testing + + org.onap.oparent + oparent-python + 1.2.3 + + + + + robotframework_onap-${python_version}-py2-none-any.whl + ${project.basedir} + https://nexus3.onap.org/repository/PyPi.snapshot/ + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-artifacts + + + + ${project.build.directory}/maven-python/dist/${wheel.name} + whl + + + false + + + + + + com.github.UltimateDogg + maven-python-distribute-plugin + + + package + + package + + + + process + + process-sources + + + + deploy + + deploy + + + + + ${python.pypi.repository} + ${python.sourceDirectory} + + + + exec-maven-plugin + org.codehaus.mojo + + + tox-test + + ${project.build.directory} + + + + + + + diff --git a/robotframework-onap/setup-template.py b/robotframework-onap/setup-template.py new file mode 100644 index 0000000..e8d3dcf --- /dev/null +++ b/robotframework-onap/setup-template.py @@ -0,0 +1,48 @@ +# Copyright 2019 AT&T Intellectual Property. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from setuptools import setup + +setup( + name='${PROJECT_NAME}', # This is the name of your PyPI-package. + keywords=("utils", "robotframework", "testing", "onap"), + version='${VERSION}', # Update the version number for new releases + license="Apache 2.0", + description='Scripts written to be used during robot framework testing', # Info about script + long_description="python-package that provides convenience methods to make certain tasks in robot framework easier." + "since this uses robot framework internal libraries or may in the future, it is not meant as a" + "general purpose library", + url="https://github.com/onap/testsuite-python-testing-utils", + platforms=['all'], + install_requires=[ + 'dnspython', + 'paramiko', + 'pyyaml', + 'robotframework', + 'deepdiff>=2.5,<3.3', + 'Jinja2' + ], # what we need + packages=['eteutils', 'loadtest', 'vcpeutils'], # The name of your scripts package + package_dir={'eteutils': 'eteutils', 'loadtest': 'loadtest', 'vcpeutils':'vcpeutils'}, # The location of your scipts package + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Programming Language :: Python :: 2.7', + 'Environment :: Plugins', + 'Framework :: Robot Framework', + 'Framework :: Robot Framework :: Library', + 'License :: OSI Approved :: Apache Software License' + ] +) diff --git a/robotframework-onap/setup.cfg b/robotframework-onap/setup.cfg new file mode 100644 index 0000000..493416b --- /dev/null +++ b/robotframework-onap/setup.cfg @@ -0,0 +1,5 @@ +[bdist_wheel] +# This flag says that the code is written to work on both Python 2 and Python +# 3. If at all possible, it is good practice to do this. If you cannot, you +# will need to generate wheels for each Python version that you support. +universal=0 \ No newline at end of file diff --git a/robotframework-onap/setup.py b/robotframework-onap/setup.py new file mode 100644 index 0000000..c5460de --- /dev/null +++ b/robotframework-onap/setup.py @@ -0,0 +1,48 @@ +# Copyright 2019 AT&T Intellectual Property. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from setuptools import setup + +setup( + name='robotframework-onap', # This is the name of your PyPI-package. + keywords=("utils", "robotframework", "testing", "onap"), + version='0.4', # Update the version number for new releases + license="Apache 2.0", + description='Scripts written to be used during robot framework testing', # Info about script + long_description="python-package that provides convenience methods to make certain tasks in robot framework easier." + "since this uses robot framework internal libraries or may in the future, it is not meant as a" + "general purpose library", + url="https://github.com/onap/testsuite-python-testing-utils", + platforms=['all'], + install_requires=[ + 'dnspython', + 'paramiko', + 'pyyaml', + 'robotframework', + 'deepdiff>=2.5,<3.3', + 'Jinja2' + ], # what we need + packages=['eteutils', 'loadtest', 'vcpeutils'], # The name of your scripts package + package_dir={'eteutils': 'eteutils', 'loadtest': 'loadtest', 'vcpeutils':'vcpeutils'}, # The location of your scipts package + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Programming Language :: Python :: 2.7', + 'Environment :: Plugins', + 'Framework :: Robot Framework', + 'Framework :: Robot Framework :: Library', + 'License :: OSI Approved :: Apache Software License' + ] +) diff --git a/robotframework-onap/tox.ini b/robotframework-onap/tox.ini new file mode 100644 index 0000000..42183a7 --- /dev/null +++ b/robotframework-onap/tox.ini @@ -0,0 +1,12 @@ +# Tox (https://tox.readthedocs.io/) is a tool for running tests +# in multiple virtualenvs. This configuration file will run the +# test suite on all supported python versions. To use it, "pip install tox" +# and then run "tox" from this directory. + +[tox] +envlist = py27 + +[testenv] +commands = {envpython} setup.py test +deps = + diff --git a/robotframework-onap/vcpeutils/SoUtils.py b/robotframework-onap/vcpeutils/SoUtils.py new file mode 100755 index 0000000..1248613 --- /dev/null +++ b/robotframework-onap/vcpeutils/SoUtils.py @@ -0,0 +1,371 @@ +#! /usr/bin/python + +import sys +import logging +import requests +import json +from datetime import datetime +#import progressbar +import time +import csar_parser +import preload +from vcpecommon import * +from robot.api import logger + + +class SoUtils: + + def __init__(self): + """ + :param api_version: must be 'v4' or 'v5' + """ + self.tmp_solution_for_so_bug = False + #self.logger = logging.getLogger(__name__) + self.logger = logger + self.vcpecommon = VcpeCommon() + self.api_version = 'v4' + self.service_req_api_url = self.vcpecommon.so_req_api_url[self.api_version] + + + def submit_create_req(self, req_json, req_type, service_instance_id=None, vnf_instance_id=None): + """ + POST {serverRoot}/serviceInstances/v4 + POST {serverRoot}/serviceInstances/v4/{serviceInstanceId}/vnfs + POST {serverRoot}/serviceInstances/v4/{serviceInstanceId}/networks + POST {serverRoot}/serviceInstances/v4/{serviceInstanceId}/vnfs/{vnfInstanceId}/vfModules + :param req_json: + :param service_instance_id: this is required only for networks, vnfs, and vf modules + :param req_type: + :param vnf_instance_id: + :return: req_id, instance_id + """ + if req_type == 'service': + url = self.service_req_api_url + elif req_type == 'vnf': + url = '/'.join([self.service_req_api_url, service_instance_id, 'vnfs']) + elif req_type == 'network': + url = '/'.join([self.service_req_api_url, service_instance_id, 'networks']) + elif req_type == 'vfmodule': + url = '/'.join([self.service_req_api_url, service_instance_id, 'vnfs', vnf_instance_id, 'vfModules']) + else: + self.logger.error('Invalid request type: {0}. Can only be service/vnf/network/vfmodule'.format(req_type)) + return None, None + + self.logger.info(url) + r = requests.post(url, headers=self.vcpecommon.so_headers, auth=self.vcpecommon.so_userpass, json=req_json) + self.logger.debug(r) + response = r.json() + + self.logger.debug('---------------------------------------------------------------') + self.logger.debug('------- Creation request submitted to SO, got response --------') + self.logger.debug(json.dumps(response, indent=4, sort_keys=True)) + self.logger.debug('---------------------------------------------------------------') + req_id = response.get('requestReferences', {}).get('requestId', '') + instance_id = response.get('requestReferences', {}).get('instanceId', '') + + return req_id, instance_id + + def check_progress(self, req_id, eta=0, interval=5): + if not req_id: + self.logger.error('Error when checking SO request progress, invalid request ID: ' + req_id) + return False + duration = 0.0 + #bar = progressbar.ProgressBar(redirect_stdout=True) + url = self.vcpecommon.so_check_progress_api_url + '/' + req_id + + while True: + time.sleep(interval) + r = requests.get(url, headers=self.vcpecommon.so_headers, auth=self.vcpecommon.so_userpass) + response = r.json() + + duration += interval + if eta > 0: + percentage = min(95, 100 * duration / eta) + else: + percentage = int(response['request']['requestStatus']['percentProgress']) + + if response['request']['requestStatus']['requestState'] == 'IN_PROGRESS': + self.logger.debug('------------------Request Status-------------------------------') + self.logger.debug(json.dumps(response, indent=4, sort_keys=True)) + #bar.update(percentage) + else: + self.logger.debug('---------------------------------------------------------------') + self.logger.debug('----------------- Creation Request Results --------------------') + self.logger.debug(json.dumps(response, indent=4, sort_keys=True)) + self.logger.debug('---------------------------------------------------------------') + flag = response['request']['requestStatus']['requestState'] == 'COMPLETE' + if not flag: + self.logger.error('Request failed.') + self.logger.error(json.dumps(response, indent=4, sort_keys=True)) + #bar.update(100) + #bar.finish() + return flag + + def add_req_info(self, req_details, instance_name, product_family_id=None): + req_details['requestInfo'] = { + 'instanceName': instance_name, + 'source': 'VID', + 'suppressRollback': 'true', + 'requestorId': 'vCPE-Robot' + } + if product_family_id: + req_details['requestInfo']['productFamilyId'] = product_family_id + + def add_related_instance(self, req_details, instance_id, instance_model): + instance = {"instanceId": instance_id, "modelInfo": instance_model} + if 'relatedInstanceList' not in req_details: + req_details['relatedInstanceList'] = [{"relatedInstance": instance}] + else: + req_details['relatedInstanceList'].append({"relatedInstance": instance}) + + def generate_vnf_or_network_request(self, req_type, instance_name, vnf_or_network_model, service_instance_id, + service_model): + req_details = { + 'modelInfo': vnf_or_network_model, + #'cloudConfiguration': {"lcpCloudRegionId": self.vcpecommon.os_region_name, + # "tenantId": self.vcpecommon.os_tenant_id}, + 'cloudConfiguration': {"lcpCloudRegionId": self.region_name, + "tenantId": self.tenant_id}, + 'requestParameters': {"userParams": []}, + 'platform': {"platformName": "Platform-Demonstration"} + } + self.add_req_info(req_details, instance_name, self.vcpecommon.product_family_id) + self.add_related_instance(req_details, service_instance_id, service_model) + return {'requestDetails': req_details} + + def generate_vfmodule_request(self, instance_name, vfmodule_model, service_instance_id, + service_model, vnf_instance_id, vnf_model): + req_details = { + 'modelInfo': vfmodule_model, + #'cloudConfiguration': {"lcpCloudRegionId": self.vcpecommon.os_region_name, + # "tenantId": self.vcpecommon.os_tenant_id}, + 'cloudConfiguration': {"lcpCloudRegionId": self.region_name, + "tenantId": self.tenant_id}, + 'requestParameters': {"usePreload": 'true'} + } + self.add_req_info(req_details, instance_name, self.vcpecommon.product_family_id) + self.add_related_instance(req_details, service_instance_id, service_model) + self.add_related_instance(req_details, vnf_instance_id, vnf_model) + return {'requestDetails': req_details} + + def generate_service_request(self, instance_name, model): + req_details = { + 'modelInfo': model, + 'subscriberInfo': {'globalSubscriberId': self.vcpecommon.global_subscriber_id}, + 'requestParameters': { + "userParams": [], + "subscriptionServiceType": "vCPE", + "aLaCarte": 'true' + } + } + self.add_req_info(req_details, instance_name) + self.add_project_info(req_details) + self.add_owning_entity(req_details) + return {'requestDetails': req_details} + + def add_project_info(self, req_details): + req_details['project'] = {'projectName': self.vcpecommon.project_name} + + def add_owning_entity(self, req_details): + req_details['owningEntity'] = {'owningEntityId': self.vcpecommon.owning_entity_id, + 'owningEntityName': self.vcpecommon.owning_entity_name} + + def generate_custom_service_request(self, instance_name, model, brg_mac): + brg_mac_enc = brg_mac.replace(':', '-') + req_details = { + 'modelInfo': model, + 'subscriberInfo': {'subscriberName': 'Kaneohe', + 'globalSubscriberId': self.vcpecommon.global_subscriber_id}, + 'cloudConfiguration': {"lcpCloudRegionId": self.region_name, + "tenantId": self.tenant_id}, + 'requestParameters': { + "userParams": [ + { + 'name': 'BRG_WAN_MAC_Address', + 'value': brg_mac + }, + { + 'name': 'VfModuleNames', + 'value': [ + { + 'VfModuleModelInvariantUuid': self.vcpecommon.vgw_VfModuleModelInvariantUuid, + 'VfModuleName': 'VGW2BRG-{0}'.format(brg_mac_enc) + } + ] + }, + { + "name": "Customer_Location", + "value": self.vcpecommon.customer_location_used_by_oof + }, + { + "name": "Homing_Solution", + "value": self.vcpecommon.homing_solution + } + ], + "subscriptionServiceType": "vCPE", + 'aLaCarte': 'false' + } + } + self.add_req_info(req_details, instance_name, self.vcpecommon.custom_product_family_id) + self.add_project_info(req_details) + self.add_owning_entity(req_details) + return {'requestDetails': req_details} + + def create_custom_service(self, csar_file, brg_mac, name_suffix=None): + parser = csar_parser.CsarParser() + if not parser.parse_csar(csar_file): + return False + + # yyyymmdd_hhmm + if not name_suffix: + name_suffix = '_' + datetime.now().strftime('%Y%m%d%H%M') + + # create service + instance_name = '_'.join([self.vcpecommon.instance_name_prefix['service'], + parser.svc_model['modelName'][0:10], name_suffix]) + instance_name = instance_name.lower() + req = self.generate_custom_service_request(instance_name, parser.svc_model, brg_mac) + self.logger.info(json.dumps(req, indent=2, sort_keys=True)) + self.logger.info('Creating custom service {0}.'.format(instance_name)) + req_id, svc_instance_id = self.submit_create_req(req, 'service') + if not self.check_progress(req_id, 140): + return False + return True + + def wait_for_aai(self, node_type, uuid): + self.logger.info('Waiting for AAI traversal to complete...') + #bar = progressbar.ProgressBar() + for i in range(30): + time.sleep(1) + #bar.update(i*100.0/30) + if self.vcpecommon.is_node_in_aai(node_type, uuid): + #bar.update(100) + #bar.finish() + return + + self.logger.error("AAI traversal didn't finish in 30 seconds. Something is wrong. Type {0}, UUID {1}".format( + node_type, uuid)) + sys.exit() + + def create_entire_service(self, csar_file, vnf_template_file, preload_dict, name_suffix, region_name, tenant_id, heatbridge=False): + """ + :param csar_file: + :param vnf_template_file: + :param preload_dict: + :param name_suffix: + :return: service instance UUID + """ + self.region_name=region_name + self.tenant_id=tenant_id + self.logger.info('\n----------------------------------------------------------------------------------') + self.logger.info('Start to create entire service defined in csar: {0}'.format(csar_file)) + parser = csar_parser.CsarParser() + self.logger.info('Parsing csar ...') + if not parser.parse_csar(csar_file): + self.logger.error('Cannot parse csar: {0}'.format(csar_file)) + return None + + # Set Global timestamp for instancenames + global_timestamp = datetime.now().strftime("%Y%m%d%H%M%S") + # create service + instance_name = '_'.join([self.vcpecommon.instance_name_prefix['service'], + parser.svc_model['modelName'], global_timestamp, name_suffix]) + instance_name = instance_name.lower() + instance_name = instance_name.replace(' ','') + instance_name = instance_name.replace(':','') + self.logger.info('Creating service instance: {0}.'.format(instance_name)) + req = self.generate_service_request(instance_name, parser.svc_model) + self.logger.debug(json.dumps(req, indent=2, sort_keys=True)) + req_id, svc_instance_id = self.submit_create_req(req, 'service') + if not self.check_progress(req_id, eta=2, interval=5): + return None + + # wait for AAI to complete traversal + self.wait_for_aai('service', svc_instance_id) + + # create networks + for model in parser.net_models: + base_name = model['modelCustomizationName'].lower().replace('mux_vg', 'mux_gw') + network_name = '_'.join([self.vcpecommon.instance_name_prefix['network'], base_name, name_suffix]) + network_name = network_name.lower() + self.logger.info('Creating network: ' + network_name) + req = self.generate_vnf_or_network_request('network', network_name, model, svc_instance_id, + parser.svc_model) + self.logger.debug(json.dumps(req, indent=2, sort_keys=True)) + req_id, net_instance_id = self.submit_create_req(req, 'network', svc_instance_id) + if not self.check_progress(req_id, eta=20): + return None + + self.logger.info('Changing subnet name to ' + self.vcpecommon.network_name_to_subnet_name(network_name)) + self.vcpecommon.set_network_name(network_name) + subnet_name_changed = False + for i in range(20): + time.sleep(3) + if self.vcpecommon.set_subnet_name(network_name): + subnet_name_changed = True + break + + if not subnet_name_changed: + self.logger.error('Failed to change subnet name for ' + network_name) + return None + + + vnf_model = None + vnf_instance_id = None + # create VNF + if len(parser.vnf_models) == 1: + vnf_model = parser.vnf_models[0] + vnf_instance_name = '_'.join([self.vcpecommon.instance_name_prefix['vnf'], + vnf_model['modelCustomizationName'].split(' ')[0], name_suffix]) + vnf_instance_name = vnf_instance_name.lower() + vnf_instance_name = vnf_instance_name.replace(' ','') + vnf_instance_name = vnf_instance_name.replace(':','') + self.logger.info('Creating VNF: ' + vnf_instance_name) + req = self.generate_vnf_or_network_request('vnf', vnf_instance_name, vnf_model, svc_instance_id, + parser.svc_model) + self.logger.debug(json.dumps(req, indent=2, sort_keys=True)) + req_id, vnf_instance_id = self.submit_create_req(req, 'vnf', svc_instance_id) + if not self.check_progress(req_id, eta=2, interval=5): + self.logger.error('Failed to create VNF {0}.'.format(vnf_instance_name)) + return False + + # wait for AAI to complete traversal + if not vnf_instance_id: + self.logger.error('No VNF instance ID returned!') + sys.exit() + self.wait_for_aai('vnf', vnf_instance_id) + + # SDNC Preload + + preloader = preload.Preload(self.vcpecommon) + preloader.preload_vfmodule(vnf_template_file, svc_instance_id, parser.vnf_models[0], parser.vfmodule_models[0], + preload_dict, name_suffix) + + # create VF Module + if len(parser.vfmodule_models) == 1: + if not vnf_instance_id or not vnf_model: + self.logger.error('Invalid VNF instance ID or VNF model!') + sys.exit() + + model = parser.vfmodule_models[0] + vfmodule_instance_name = '_'.join([self.vcpecommon.instance_name_prefix['vfmodule'], + model['modelCustomizationName'].split('..')[0], name_suffix]) + vfmodule_instance_name = vfmodule_instance_name.lower() + vfmodule_instance_name = vfmodule_instance_name.replace(' ','') + vfmoduel_instance_name = vfmodule_instance_name.replace(':','') + self.logger.info('Creating VF Module: ' + vfmodule_instance_name) + req = self.generate_vfmodule_request(vfmodule_instance_name, model, svc_instance_id, parser.svc_model, + vnf_instance_id, vnf_model) + self.logger.debug(json.dumps(req, indent=2, sort_keys=True)) + req_id, vfmodule_instance_id = self.submit_create_req(req, 'vfmodule', svc_instance_id, vnf_instance_id) + if not self.check_progress(req_id, eta=70, interval=50): + self.logger.error('Failed to create VF Module {0}.'.format(vfmodule_instance_name)) + return None + + # run heatbridge + # removed until we fold in heatbridge + #if heatbridge: + #self.vcpecommon.heatbridge(vfmodule_instance_name, svc_instance_id) + #self.vcpecommon.save_vgmux_vnf_name(vnf_instance_name) + + return svc_instance_id diff --git a/robotframework-onap/vcpeutils/__init__.py b/robotframework-onap/vcpeutils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/robotframework-onap/vcpeutils/csar_parser.py b/robotframework-onap/vcpeutils/csar_parser.py new file mode 100755 index 0000000..f101364 --- /dev/null +++ b/robotframework-onap/vcpeutils/csar_parser.py @@ -0,0 +1,231 @@ +#! /usr/bin/python +import os +import zipfile +import shutil +import yaml +import json +import logging + + +class CsarParser: + def __init__(self): + self.logger = logging.getLogger(__name__) + self.svc_model = {} + self.net_models = [] # there could be multiple networks + self.vnf_models = [] # this version only support a single VNF in the service template + self.vfmodule_models = [] # this version only support a single VF module in the service template + + def get_service_yaml_from_csar(self, csar_file): + """ + :param csar_file: csar file path name, e.g. 'csar/vgmux.csar' + :return: + """ + tmpdir = './__tmp' + if os.path.isdir(tmpdir): + shutil.rmtree(tmpdir) + os.mkdir(tmpdir) + + with zipfile.ZipFile(csar_file, "r") as zip_ref: + zip_ref.extractall(tmpdir) + + yamldir = tmpdir + '/Definitions' + if os.path.isdir(yamldir): + for filename in os.listdir(yamldir): + # look for service template like this: service-Vcpesvcbrgemu111601-template.yml + if filename.startswith('service-') and filename.endswith('-template.yml'): + return os.path.join(yamldir, filename) + + self.logger.error('Invalid file: ' + csar_file) + return '' + + def get_service_model_info(self, svc_template): + """ extract service model info from yaml and convert to what to be used in SO request + Sample from yaml: + { + "UUID": "aed4fc5e-b871-4e26-8531-ceabd46df85e", + "category": "Network L1-3", + "description": "Infra service", + "ecompGeneratedNaming": true, + "invariantUUID": "c806682a-5b3a-44d8-9e88-0708be151296", + "name": "vcpesvcinfra111601", + "namingPolicy": "", + "serviceEcompNaming": true, + "serviceRole": "", + "serviceType": "", + "type": "Service" + }, + + Convert to + { + "modelType": "service", + "modelInvariantId": "ca4c7a70-06fd-45d8-8b9e-c9745d25bf2b", + "modelVersionId": "5d8911b4-e50c-4096-a81e-727a8157193c", + "modelName": "vcpesvcbrgemu111601", + "modelVersion": "1.0" + }, + + """ + if svc_template['metadata']['type'] != 'Service': + self.logger.error('csar error: metadata->type is not Service') + return + + metadata = svc_template['metadata'] + self.svc_model = { + 'modelType': 'service', + 'modelInvariantId': metadata['invariantUUID'], + 'modelVersionId': metadata['UUID'], + 'modelName': metadata['name'] + } + if 'version' in metadata: + self.svc_model['modelVersion'] = metadata['version'] + else: + self.svc_model['modelVersion'] = '1.0' + + def get_vnf_and_network_model_info(self, svc_template): + """ extract vnf and network model info from yaml and convert to what to be used in SO request + Sample from yaml: + "topology_template": { + "node_templates": { + "CPE_PUBLIC": { + "metadata": { + "UUID": "33b2c367-a165-4bb3-81c3-0150cd06ceff", + "category": "Generic", + "customizationUUID": "db1d4ac2-62cd-4e5d-b2dc-300dbd1a5da1", + "description": "Generic NeutronNet", + "invariantUUID": "3d4c0e47-4794-4e98-a794-baaced668930", + "name": "Generic NeutronNet", + "resourceVendor": "ATT (Tosca)", + "resourceVendorModelNumber": "", + "resourceVendorRelease": "1.0.0.wd03", + "subcategory": "Network Elements", + "type": "VL", + "version": "1.0" + }, + "type": "org.openecomp.resource.vl.GenericNeutronNet" + }, + Convert to + { + "modelType": "network", + "modelInvariantId": "3d4c0e47-4794-4e98-a794-baaced668930", + "modelVersionId": "33b2c367-a165-4bb3-81c3-0150cd06ceff", + "modelName": "Generic NeutronNet", + "modelVersion": "1.0", + "modelCustomizationId": "db1d4ac2-62cd-4e5d-b2dc-300dbd1a5da1", + "modelCustomizationName": "CPE_PUBLIC" + }, + """ + node_dic = svc_template['topology_template']['node_templates'] + for node_name, v in node_dic.items(): + model = { + 'modelInvariantId': v['metadata']['invariantUUID'], + 'modelVersionId': v['metadata']['UUID'], + 'modelName': v['metadata']['name'], + 'modelVersion': v['metadata']['version'], + 'modelCustomizationId': v['metadata']['customizationUUID'], + 'modelCustomizationName': node_name + } + + if v['type'].startswith('org.openecomp.resource.vl.GenericNeutronNet'): + # a neutron network is found + self.logger.info('Parser found a network: ' + node_name) + model['modelType'] = 'network' + self.net_models.append(model) + elif v['type'].startswith('org.openecomp.resource.vf.'): + # a VNF is found + self.logger.info('Parser found a VNF: ' + node_name) + model['modelType'] = 'vnf' + self.vnf_models.append(model) + else: + self.logger.warning('Parser found a node that is neither a network nor a VNF: ' + node_name) + + def get_vfmodule_model_info(self, svc_template): + """ extract network model info from yaml and convert to what to be used in SO request + Sample from yaml: + "topology_template": { + "groups": { + "vspinfra1116010..Vspinfra111601..base_vcpe_infra..module-0": { + "metadata": { + "vfModuleModelCustomizationUUID": "11ddac51-30e3-4a3f-92eb-2eb99c2cb288", + "vfModuleModelInvariantUUID": "02f70416-581e-4f00-bde1-d65e69af95c5", + "vfModuleModelName": "Vspinfra111601..base_vcpe_infra..module-0", + "vfModuleModelUUID": "88c78078-f1fd-4f73-bdd9-10420b0f6353", + "vfModuleModelVersion": "1" + }, + "properties": { + "availability_zone_count": null, + "initial_count": 1, + "max_vf_module_instances": 1, + "min_vf_module_instances": 1, + "vf_module_description": null, + "vf_module_label": "base_vcpe_infra", + "vf_module_type": "Base", + "vfc_list": null, + "volume_group": false + }, + "type": "org.openecomp.groups.VfModule" + } + }, + Convert to + { + "modelType": "vfModule", + "modelInvariantId": "02f70416-581e-4f00-bde1-d65e69af95c5", + "modelVersionId": "88c78078-f1fd-4f73-bdd9-10420b0f6353", + "modelName": "Vspinfra111601..base_vcpe_infra..module-0", + "modelVersion": "1", + "modelCustomizationId": "11ddac51-30e3-4a3f-92eb-2eb99c2cb288", + "modelCustomizationName": "Vspinfra111601..base_vcpe_infra..module-0" + }, + """ + node_dic = svc_template['topology_template']['groups'] + for node_name, v in node_dic.items(): + if v['type'].startswith('org.openecomp.groups.VfModule'): + model = { + 'modelType': 'vfModule', + 'modelInvariantId': v['metadata']['vfModuleModelInvariantUUID'], + 'modelVersionId': v['metadata']['vfModuleModelUUID'], + 'modelName': v['metadata']['vfModuleModelName'], + 'modelVersion': v['metadata']['vfModuleModelVersion'], + 'modelCustomizationId': v['metadata']['vfModuleModelCustomizationUUID'], + 'modelCustomizationName': v['metadata']['vfModuleModelName'] + } + self.vfmodule_models.append(model) + self.logger.info('Parser found a VF module: ' + model['modelCustomizationName']) + + def parse_service_yaml(self, filename): + # clean up + self.svc_model = {} + self.net_models = [] # there could be multiple networks + self.vnf_models = [] # this version only support a single VNF in the service template + self.vfmodule_models = [] # this version only support a single VF module in the service template + + svc_template = yaml.load(file(filename, 'r')) + self.get_service_model_info(svc_template) + self.get_vnf_and_network_model_info(svc_template) + self.get_vfmodule_model_info(svc_template) + + return True + + def parse_csar(self, csar_file): + yaml_file = self.get_service_yaml_from_csar(csar_file) + if yaml_file != '': + return self.parse_service_yaml(yaml_file) + + def print_models(self): + print('---------Service Model----------') + print(json.dumps(self.svc_model, indent=2, sort_keys=True)) + + print('---------Network Model(s)----------') + for model in self.net_models: + print(json.dumps(model, indent=2, sort_keys=True)) + + print('---------VNF Model(s)----------') + for model in self.vnf_models: + print(json.dumps(model, indent=2, sort_keys=True)) + + print('---------VF Module Model(s)----------') + for model in self.vfmodule_models: + print(json.dumps(model, indent=2, sort_keys=True)) + + def test(self): + self.parse_csar('csar/service-Vcpesvcinfra111601-csar.csar') + self.print_models() diff --git a/robotframework-onap/vcpeutils/preload.py b/robotframework-onap/vcpeutils/preload.py new file mode 100755 index 0000000..aab28f9 --- /dev/null +++ b/robotframework-onap/vcpeutils/preload.py @@ -0,0 +1,231 @@ +#! /usr/bin/python + +import requests +import json +import sys +from datetime import datetime +from vcpecommon import * +import csar_parser +#import logging +from robot.api import logger +import base64 + + +class Preload: + def __init__(self, vcpecommon): + #self.logger = logging.getLogger(__name__) + self.logger = logger + self.vcpecommon = vcpecommon + + def replace(self, sz, replace_dict): + for old_string, new_string in replace_dict.items(): + sz = sz.replace(old_string, new_string) + if self.vcpecommon.template_variable_symbol in sz: + self.logger.error('Error! Cannot find a value to replace ' + sz) + return sz + + def generate_json(self, template_file, replace_dict): + with open(template_file) as json_input: + json_data = json.load(json_input) + stk = [json_data] + while len(stk) > 0: + data = stk.pop() + for k, v in data.items(): + if type(v) is dict: + stk.append(v) + elif type(v) is list: + stk.extend(v) + elif type(v) is str or type(v) is unicode: + if self.vcpecommon.template_variable_symbol in v: + data[k] = self.replace(v, replace_dict) + else: + self.logger.warning('Unexpected line in template: %s. Look for value %s', template_file, v) + return json_data + + def reset_sniro(self): + self.logger.debug('Clearing SNIRO data') + r = requests.post(self.vcpecommon.sniro_url + '/reset', headers=self.vcpecommon.sniro_headers) + if 2 != r.status_code / 100: + self.logger.debug(r.content) + self.logger.error('Clearing SNIRO date failed.') + sys.exit() + + def preload_sniro(self, template_sniro_data, template_sniro_request, tunnelxconn_ar_name, vgw_name, vbrg_ar_name, + vgmux_svc_instance_uuid, vbrg_svc_instance_uuid): + self.reset_sniro() + self.logger.info('Preloading SNIRO for homing service') + replace_dict = {'${tunnelxconn_ar_name}': tunnelxconn_ar_name, + '${vgw_name}': vgw_name, + '${brg_ar_name}': vbrg_ar_name, + '${vgmux_svc_instance_uuid}': vgmux_svc_instance_uuid, + '${vbrg_svc_instance_uuid}': vbrg_svc_instance_uuid + } + sniro_data = self.generate_json(template_sniro_data, replace_dict) + self.logger.debug('SNIRO data:') + self.logger.debug(json.dumps(sniro_data, indent=4, sort_keys=True)) + + base64_sniro_data = base64.b64encode(json.dumps(sniro_data)) + self.logger.debug('SNIRO data: 64') + self.logger.debug(base64_sniro_data) + replace_dict = {'${base64_sniro_data}': base64_sniro_data, '${sniro_ip}': self.vcpecommon.hosts['robot']} + sniro_request = self.generate_json(template_sniro_request, replace_dict) + self.logger.debug('SNIRO request:') + self.logger.debug(json.dumps(sniro_request, indent=4, sort_keys=True)) + + r = requests.post(self.vcpecommon.sniro_url, headers=self.vcpecommon.sniro_headers, json=sniro_request) + if 2 != r.status_code / 100: + response = r.json() + self.logger.debug(json.dumps(response, indent=4, sort_keys=True)) + self.logger.error('SNIRO preloading failed.') + sys.exit() + + return True + + def preload_network(self, template_file, network_role, subnet_start_ip, subnet_gateway, common_dict, name_suffix): + """ + :param template_file: + :param network_role: cpe_signal, cpe_public, brg_bng, bng_mux, mux_gw + :param subnet_start_ip: + :param subnet_gateway: + :param name_suffix: e.g. '201711201311' + :return: + """ + network_name = '_'.join([self.vcpecommon.instance_name_prefix['network'], network_role.lower(), name_suffix]) + subnet_name = self.vcpecommon.network_name_to_subnet_name(network_name) + common_dict['${' + network_role+'_net}'] = network_name + common_dict['${' + network_role+'_subnet}'] = subnet_name + replace_dict = {'${network_role}': network_role, + '${service_type}': 'vCPE', + '${network_type}': 'Generic NeutronNet', + '${network_name}': network_name, + '${subnet_start_ip}': subnet_start_ip, + '${subnet_gateway}': subnet_gateway + } + self.logger.info('Preloading network ' + network_role) + return self.preload(template_file, replace_dict, self.vcpecommon.sdnc_preload_network_url) + + def preload(self, template_file, replace_dict, url): + self.logger.debug(json.dumps(replace_dict, indent=4, sort_keys=True)) + json_data = self.generate_json(template_file, replace_dict) + self.logger.debug(json.dumps(json_data, indent=4, sort_keys=True)) + r = requests.post(url, headers=self.vcpecommon.sdnc_headers, auth=self.vcpecommon.sdnc_userpass, json=json_data) + response = r.json() + if int(response.get('output', {}).get('response-code', 0)) != 200: + self.logger.debug(json.dumps(response, indent=4, sort_keys=True)) + self.logger.error('Preloading failed.') + return False + return True + + def preload_vgw(self, template_file, brg_mac, commont_dict, name_suffix): + replace_dict = {'${brg_mac}': brg_mac, + '${suffix}': name_suffix + } + replace_dict.update(commont_dict) + self.logger.info('Preloading vGW') + return self.preload(template_file, replace_dict, self.vcpecommon.sdnc_preload_vnf_url) + + def preload_vgw_gra(self, template_file, brg_mac, commont_dict, name_suffix, vgw_vfmod_name_index): + replace_dict = {'${brg_mac}': brg_mac, + '${suffix}': name_suffix, + '${vgw_vfmod_name_index}': vgw_vfmod_name_index + } + replace_dict.update(commont_dict) + self.logger.info('Preloading vGW-GRA') + return self.preload(template_file, replace_dict, self.vcpecommon.sdnc_preload_gra_url) + + def preload_vfmodule(self, template_file, service_instance_id, vnf_model, vfmodule_model, common_dict, name_suffix): + """ + :param template_file: + :param service_instance_id: + :param vnf_model: parsing results from csar_parser + :param vfmodule_model: parsing results from csar_parser + :param common_dict: + :param name_suffix: + :return: + """ + + # examples: + # vfmodule_model['modelCustomizationName']: "Vspinfra111601..base_vcpe_infra..module-0", + # vnf_model['modelCustomizationName']: "vspinfra111601 0", + + vfmodule_name = '_'.join([self.vcpecommon.instance_name_prefix['vfmodule'], + vfmodule_model['modelCustomizationName'].split('..')[0].lower(), name_suffix]) + + # vnf_type and generic_vnf_type are identical + replace_dict = {'${vnf_type}': vfmodule_model['modelCustomizationName'], + '${generic_vnf_type}': vfmodule_model['modelCustomizationName'], + '${service_type}': service_instance_id, + '${generic_vnf_name}': vnf_model['modelCustomizationName'], + '${vnf_name}': vfmodule_name, + '${mr_ip_addr}': self.vcpecommon.mr_ip_addr, + '${mr_ip_port}': self.vcpecommon.mr_ip_port, + '${sdnc_oam_ip}': self.vcpecommon.sdnc_oam_ip, + '${suffix}': name_suffix} + replace_dict.update(common_dict) + self.logger.info('Preloading VF Module ' + vfmodule_name) + return self.preload(template_file, replace_dict, self.vcpecommon.sdnc_preload_vnf_url) + + def preload_all_networks(self, template_file, name_suffix): + common_dict = {'${' + k + '}': v for k, v in self.vcpecommon.common_preload_config.items()} + for network, v in self.vcpecommon.preload_network_config.items(): + subnet_start_ip, subnet_gateway_ip = v + if not self.preload_network(template_file, network, subnet_start_ip, subnet_gateway_ip, + common_dict, name_suffix): + return None + return common_dict + + def test(self): + # this is for testing purpose + name_suffix = datetime.now().strftime('%Y%m%d%H%M') + vcpecommon = VcpeCommon() + preloader = Preload(vcpecommon) + + network_dict = {'${' + k + '}': v for k, v in self.vcpecommon.common_preload_config.items()} + template_file = 'preload_templates/template.network.json' + for k, v in self.vcpecommon.preload_network_config.items(): + if not preloader.preload_network(template_file, k, v[0], v[1], network_dict, name_suffix): + break + + print('---------------------------------------------------------------') + print('Network related replacement dictionary:') + print(json.dumps(network_dict, indent=4, sort_keys=True)) + print('---------------------------------------------------------------') + + keys = ['infra', 'bng', 'gmux', 'brg'] + for key in keys: + csar_file = self.vcpecommon.find_file(key, 'csar', 'csar') + template_file = self.vcpecommon.find_file(key, 'json', 'preload_templates') + if csar_file and template_file: + parser = csar_parser.CsarParser() + parser.parse_csar(csar_file) + service_instance_id = 'test112233' + preloader.preload_vfmodule(template_file, service_instance_id, parser.vnf_models[0], + parser.vfmodule_models[0], network_dict, name_suffix) + + def test_sniro(self): + template_sniro_data = self.vcpecommon.find_file('sniro_data', 'json', 'preload_templates') + template_sniro_request = self.vcpecommon.find_file('sniro_request', 'json', 'preload_templates') + + vcperescust_csar = self.vcpecommon.find_file('rescust', 'csar', 'csar') + parser = csar_parser.CsarParser() + parser.parse_csar(vcperescust_csar) + tunnelxconn_ar_name = None + brg_ar_name = None + vgw_name = None + for model in parser.vnf_models: + if 'tunnel' in model['modelCustomizationName']: + tunnelxconn_ar_name = model['modelCustomizationName'] + elif 'brg' in model['modelCustomizationName']: + brg_ar_name = model['modelCustomizationName'] + elif 'vgw' in model['modelCustomizationName']: + vgw_name = model['modelCustomizationName'] + + if not (tunnelxconn_ar_name and brg_ar_name and vgw_name): + self.logger.error('Cannot find all names from %s.', vcperescust_csar) + sys.exit() + + vgmux_svc_instance_uuid = '88888888888888' + vbrg_svc_instance_uuid = '999999999999999' + + self.preload_sniro(template_sniro_data, template_sniro_request, tunnelxconn_ar_name, vgw_name, brg_ar_name, + vgmux_svc_instance_uuid, vbrg_svc_instance_uuid) diff --git a/robotframework-onap/vcpeutils/vcpecommon.py b/robotframework-onap/vcpeutils/vcpecommon.py new file mode 100755 index 0000000..95b5bbe --- /dev/null +++ b/robotframework-onap/vcpeutils/vcpecommon.py @@ -0,0 +1,325 @@ +import json +import logging +import os +import pickle +import re +import sys + +import ipaddress +import requests +import commands +import time +from novaclient import client as openstackclient +from netaddr import IPAddress, IPNetwork + +class VcpeCommon: + ############################################################################################# + # Start: configurations that you must change for a new ONAP installation + external_net_addr = '10.12.0.0' + external_net_prefix_len = 16 + ############################################################################################# + # set the openstack cloud access credentials here + oom_mode = True + + cloud = { + '--os-auth-url': 'http://10.12.25.2:5000', + '--os-username': 'kxi', + '--os-user-domain-id': 'default', + '--os-project-domain-id': 'default', + '--os-tenant-id': '09d8566ea45e43aa974cf447ed591d77' if oom_mode else '1e097c6713e74fd7ac8e4295e605ee1e', + '--os-region-name': 'RegionOne', + '--os-password': 'n3JhGMGuDzD8', + '--os-project-domain-name': 'Integration-SB-03' if oom_mode else 'Integration-SB-07', + '--os-identity-api-version': '3' + } + + common_preload_config = { + 'oam_onap_net': 'oam_network_2No2' if oom_mode else 'oam_onap_lAky', + 'oam_onap_subnet': 'oam_network_2No2' if oom_mode else 'oam_onap_lAky', + 'public_net': 'external', + 'public_net_id': '971040b2-7059-49dc-b220-4fab50cb2ad4' + } + sdnc_controller_pod = 'dev-sdnc-sdnc-0' + + ############################################################################################# + + template_variable_symbol = '${' + cpe_vm_prefix = 'zdcpe' + ############################################################################################# + # preloading network config + # key=network role + # value = [subnet_start_ip, subnet_gateway_ip] + preload_network_config = { + 'cpe_public': ['10.2.0.2', '10.2.0.1'], + 'cpe_signal': ['10.4.0.2', '10.4.0.1'], + 'brg_bng': ['10.3.0.2', '10.3.0.1'], + 'bng_mux': ['10.1.0.10', '10.1.0.1'], + 'mux_gw': ['10.5.0.10', '10.5.0.1'] + } + + dcae_ves_collector_name = 'dcae-bootstrap' + global_subscriber_id = 'Demonstration' + project_name = 'Project-Demonstration' + owning_entity_id = '520cc603-a3c4-4ec2-9ef4-ca70facd79c0' + owning_entity_name = 'OE-Demonstration1' + + def __init__(self, extra_host_names=None): + rootlogger = logging.getLogger() + handler = logging.StreamHandler() + formatter = logging.Formatter('%(asctime)s %(levelname)s %(name)s.%(funcName)s(): %(message)s') + handler.setFormatter(formatter) + rootlogger.addHandler(handler) + rootlogger.setLevel(logging.INFO) + + self.logger = logging.getLogger(__name__) + self.logger.propagate = False + self.logger.addHandler(handler) + self.logger.setLevel(logging.DEBUG) + self.logger.info('Initializing configuration') + + # CHANGEME: vgw_VfModuleModelInvariantUuid is in rescust service csar, look in service-VcpesvcRescust1118-template.yml for groups vgw module metadata. TODO: read this value automcatically + self.vgw_VfModuleModelInvariantUuid = '26d6a718-17b2-4ba8-8691-c44343b2ecd2' + # CHANGEME: OOM: this is the address that the brg and bng will nat for sdnc access - 10.0.0.x address of k8 host for sdnc-0 container + #self.sdnc_oam_ip = self.get_pod_node_oam_ip('sdnc-sdnc-0') + self.sdnc_oam_ip = 'sdnc.onap' + # CHANGEME: OOM: this is a k8s host external IP, e.g. oom-k8s-01 IP + #self.oom_so_sdnc_aai_ip = self.get_pod_node_public_ip('sdnc-sdnc-0') + #self.oom_so_sdnc_aai_ip = self.get_pod_node_public_ip('sdnc-sdnc-0') + # CHANGEME: OOM: this is a k8s host external IP, e.g. oom-k8s-01 IP + #self.oom_dcae_ves_collector = self.oom_so_sdnc_aai_ip + # CHANGEME: OOM: this is a k8s host external IP, e.g. oom-k8s-01 IP + #self.mr_ip_addr = self.oom_so_sdnc_aai_ip + self.mr_ip_addr = 'mr.onap' + #self.mr_ip_port = '30227' + self.mr_ip_port = '3904' + #self.so_nbi_port = '30277' if self.oom_mode else '8080' + self.so_nbi_port = '8080' + #self.sdnc_preloading_port = '30202' if self.oom_mode else '8282' + self.sdnc_preloading_port = '8282' + #self.aai_query_port = '30233' if self.oom_mode else '8443' + self.aai_query_port = '8443' + #self.sniro_port = '30288' if self.oom_mode else '8080' + self.sniro_port = '8080' + + self.host_names = ['so', 'sdnc', 'robot', 'aai', self.dcae_ves_collector_name] + if extra_host_names: + self.host_names.extend(extra_host_names) + # get IP addresses + #self.hosts = self.get_vm_ip(self.host_names, self.external_net_addr, self.external_net_prefix_len) + self.hosts = { 'so': 'so.onap', 'sdnc': 'sdnc.onap', 'robot': 'robot.onap', 'aai': 'aai.onap' } + # this is the keyword used to name vgw stack, must not be used in other stacks + self.vgw_name_keyword = 'base_vcpe_vgw' + # this is the file that will keep the index of last assigned SO name + self.vgw_vfmod_name_index_file= '__var/vgw_vfmod_name_index' + self.svc_instance_uuid_file = '__var/svc_instance_uuid' + self.preload_dict_file = '__var/preload_dict' + self.vgmux_vnf_name_file = '__var/vgmux_vnf_name' + self.product_family_id = 'f9457e8c-4afd-45da-9389-46acd9bf5116' + self.custom_product_family_id = 'a9a77d5a-123e-4ca2-9eb9-0b015d2ee0fb' + self.instance_name_prefix = { + 'service': 'svc', + 'network': 'net', + 'vnf': 'vnf', + 'vfmodule': 'vf' + } + self.aai_userpass = 'AAI', 'AAI' + self.pub_key = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKXDgoo3+WOqcUG8/5uUbk81+yczgwC4Y8ywTmuQqbNxlY1oQ0YxdMUqUnhitSXs5S/yRuAVOYHwGg2mCs20oAINrP+mxBI544AMIb9itPjCtgqtE2EWo6MmnFGbHB4Sx3XioE7F4VPsh7japsIwzOjbrQe+Mua1TGQ5d4nfEOQaaglXLLPFfuc7WbhbJbK6Q7rHqZfRcOwAMXgDoBqlyqKeiKwnumddo2RyNT8ljYmvB6buz7KnMinzo7qB0uktVT05FH9Rg0CTWH5norlG5qXgP2aukL0gk1ph8iAt7uYLf1ktp+LJI2gaF6L0/qli9EmVCSLr1uJ38Q8CBflhkh' + self.os_tenant_id = self.cloud['--os-tenant-id'] + self.os_region_name = self.cloud['--os-region-name'] + self.common_preload_config['pub_key'] = self.pub_key + self.sniro_url = 'http://' + self.hosts['robot'] + ':' + self.sniro_port + '/__admin/mappings' + self.sniro_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'} + self.homing_solution = 'sniro' # value is either 'sniro' or 'oof' +# self.homing_solution = 'oof' + self.customer_location_used_by_oof = { + "customerLatitude": "32.897480", + "customerLongitude": "-97.040443", + "customerName": "some_company" + } + + ############################################################################################# + # SDNC urls + self.sdnc_userpass = 'admin', 'Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U' + self.sdnc_db_name = 'sdnctl' + self.sdnc_db_user = 'sdnctl' + self.sdnc_db_pass = 'gamma' + self.sdnc_db_port = '32774' + self.sdnc_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'} + self.sdnc_preload_network_url = 'http://' + self.hosts['sdnc'] + \ + ':' + self.sdnc_preloading_port + '/restconf/operations/VNF-API:preload-network-topology-operation' + self.sdnc_preload_vnf_url = 'http://' + self.hosts['sdnc'] + \ + ':' + self.sdnc_preloading_port + '/restconf/operations/VNF-API:preload-vnf-topology-operation' + self.sdnc_preload_gra_url = 'http://' + self.hosts['sdnc'] + \ + ':' + self.sdnc_preloading_port + '/restconf/operations/GENERIC-RESOURCE-API:preload-vf-module-topology-operation' + self.sdnc_ar_cleanup_url = 'http://' + self.hosts['sdnc'] + ':' + self.sdnc_preloading_port + \ + '/restconf/config/GENERIC-RESOURCE-API:' + + ############################################################################################# + # SO urls, note: do NOT add a '/' at the end of the url + self.so_req_api_url = {'v4': 'http://' + self.hosts['so'] + ':' + self.so_nbi_port + '/onap/so/infra/serviceInstantiation/v7/serviceInstances', + 'v5': 'http://' + self.hosts['so'] + ':' + self.so_nbi_port + '/onap/so/infra/serviceInstantiation/v7/serviceInstances'} + self.so_check_progress_api_url = 'http://' + self.hosts['so'] + ':' + self.so_nbi_port + '/onap/so/infra/orchestrationRequests/v6' + self.so_userpass = 'InfraPortalClient', 'password1$' + self.so_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'} + self.so_db_name = 'catalogdb' + self.so_db_user = 'root' + self.so_db_pass = 'password' + self.so_db_port = '30252' if self.oom_mode else '32769' + + self.vpp_inf_url = 'http://{0}:8183/restconf/config/ietf-interfaces:interfaces' + self.vpp_api_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'} + self.vpp_api_userpass = ('admin', 'admin') + self.vpp_ves_url= 'http://{0}:8183/restconf/config/vesagent:vesagent' + + + + def find_file(self, file_name_keyword, file_ext, search_dir): + """ + :param file_name_keyword: keyword used to look for the csar file, case insensitive matching, e.g, infra + :param file_ext: e.g., csar, json + :param search_dir path to search + :return: path name of the file + """ + file_name_keyword = file_name_keyword.lower() + file_ext = file_ext.lower() + if not file_ext.startswith('.'): + file_ext = '.' + file_ext + + filenamepath = None + for file_name in os.listdir(search_dir): + file_name_lower = file_name.lower() + if file_name_keyword in file_name_lower and file_name_lower.endswith(file_ext): + if filenamepath: + self.logger.error('Multiple files found for *{0}*.{1} in ' + 'directory {2}'.format(file_name_keyword, file_ext, search_dir)) + sys.exit() + filenamepath = os.path.abspath(os.path.join(search_dir, file_name)) + + if filenamepath: + return filenamepath + else: + self.logger.error("Cannot find *{0}*{1} in directory {2}".format(file_name_keyword, file_ext, search_dir)) + sys.exit() + + @staticmethod + def network_name_to_subnet_name(network_name): + """ + :param network_name: example: vcpe_net_cpe_signal_201711281221 + :return: vcpe_net_cpe_signal_subnet_201711281221 + """ + fields = network_name.split('_') + fields.insert(-1, 'subnet') + return '_'.join(fields) + + def set_network_name(self, network_name): + param = ' '.join([k + ' ' + v for k, v in self.cloud.items()]) + openstackcmd = 'openstack ' + param + cmd = ' '.join([openstackcmd, 'network set --name', network_name, 'ONAP-NW1']) + os.popen(cmd) + + def set_subnet_name(self, network_name): + """ + Example: network_name = vcpe_net_cpe_signal_201711281221 + set subnet name to vcpe_net_cpe_signal_subnet_201711281221 + :return: + """ + param = ' '.join([k + ' ' + v for k, v in self.cloud.items()]) + openstackcmd = 'openstack ' + param + + # expected results: | subnets | subnet_id | + subnet_info = os.popen(openstackcmd + ' network show ' + network_name + ' |grep subnets').read().split('|') + if len(subnet_info) > 2 and subnet_info[1].strip() == 'subnets': + subnet_id = subnet_info[2].strip() + subnet_name = self.network_name_to_subnet_name(network_name) + cmd = ' '.join([openstackcmd, 'subnet set --name', subnet_name, subnet_id]) + os.popen(cmd) + self.logger.info("Subnet name set to: " + subnet_name) + return True + else: + self.logger.error("Can't get subnet info from network name: " + network_name) + return False + + def is_node_in_aai(self, node_type, node_uuid): + key = None + search_node_type = None + if node_type == 'service': + search_node_type = 'service-instance' + key = 'service-instance-id' + elif node_type == 'vnf': + search_node_type = 'generic-vnf' + key = 'vnf-id' + else: + logging.error('Invalid node_type: ' + node_type) + sys.exit() + + url = 'https://{0}:{1}/aai/v11/search/nodes-query?search-node-type={2}&filter={3}:EQUALS:{4}'.format( + self.hosts['aai'], self.aai_query_port, search_node_type, key, node_uuid) + + headers = {'Content-Type': 'application/json', 'Accept': 'application/json', 'X-FromAppID': 'vCPE-Robot', 'X-TransactionId': 'get_aai_subscr'} + requests.packages.urllib3.disable_warnings() + r = requests.get(url, headers=headers, auth=self.aai_userpass, verify=False) + response = r.json() + self.logger.debug('aai query: ' + url) + self.logger.debug('aai response:\n' + json.dumps(response, indent=4, sort_keys=True)) + return 'result-data' in response + + @staticmethod + def extract_ip_from_str(net_addr, net_addr_len, sz): + """ + :param net_addr: e.g. 10.5.12.0 + :param net_addr_len: e.g. 24 + :param sz: a string + :return: the first IP address matching the network, e.g. 10.5.12.3 + """ + network = ipaddress.ip_network(unicode('{0}/{1}'.format(net_addr, net_addr_len)), strict=False) + ip_list = re.findall(r'[0-9]+(?:\.[0-9]+){3}', sz) + for ip in ip_list: + this_net = ipaddress.ip_network(unicode('{0}/{1}'.format(ip, net_addr_len)), strict=False) + if this_net == network: + return str(ip) + return None + + + + @staticmethod + def save_object(obj, filepathname): + with open(filepathname, 'wb') as fout: + pickle.dump(obj, fout) + + @staticmethod + def load_object(filepathname): + with open(filepathname, 'rb') as fin: + return pickle.load(fin) + + @staticmethod + def increase_ip_address_or_vni_in_template(vnf_template_file, vnf_parameter_name_list): + with open(vnf_template_file) as json_input: + json_data = json.load(json_input) + param_list = json_data['VNF-API:input']['VNF-API:vnf-topology-information']['VNF-API:vnf-parameters'] + for param in param_list: + if param['vnf-parameter-name'] in vnf_parameter_name_list: + ipaddr_or_vni = param['vnf-parameter-value'].split('.') + number = int(ipaddr_or_vni[-1]) + if 254 == number: + number = 10 + else: + number = number + 1 + ipaddr_or_vni[-1] = str(number) + param['vnf-parameter-value'] = '.'.join(ipaddr_or_vni) + + assert json_data is not None + with open(vnf_template_file, 'w') as json_output: + json.dump(json_data, json_output, indent=4, sort_keys=True) + + def save_preload_data(self, preload_data): + self.save_object(preload_data, self.preload_dict_file) + + def load_preload_data(self): + return self.load_object(self.preload_dict_file) + + def save_vgmux_vnf_name(self, vgmux_vnf_name): + self.save_object(vgmux_vnf_name, self.vgmux_vnf_name_file) + + def load_vgmux_vnf_name(self): + return self.load_object(self.vgmux_vnf_name_file) + diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 493416b..0000000 --- a/setup.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[bdist_wheel] -# This flag says that the code is written to work on both Python 2 and Python -# 3. If at all possible, it is good practice to do this. If you cannot, you -# will need to generate wheels for each Python version that you support. -universal=0 \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 20aec53..0000000 --- a/setup.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright 2019 AT&T Intellectual Property. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -from setuptools import setup - -setup( - name='python-openecomp-eteutils', # This is the name of your PyPI-package. - keywords=("utils", "robotframework", "testing", "onap"), - version='0.3', # Update the version number for new releases - license="Apache 2.0", - description='Scripts written to be used during robot framework testing', # Info about script - long_description="python-package that provides convenience methods to make certain tasks in robot framework easier." - "since this uses robot framework internal libraries or may in the future, it is not meant as a" - "general purpose library", - url="https://github.com/onap/testsuite-python-testing-utils", - platforms=['all'], - install_requires=[ - 'dnspython', - 'paramiko', - 'pyyaml', - 'robotframework', - 'deepdiff>=2.5,<3.3', - 'Jinja2' - ], # what we need - packages=['eteutils', 'loadtest', 'vcpeutils'], # The name of your scripts package - package_dir={'eteutils': 'eteutils', 'loadtest': 'loadtest', 'vcpeutils':'vcpeutils'}, # The location of your scipts package - classifiers=[ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'Programming Language :: Python :: 2.7', - 'Environment :: Plugins', - 'Framework :: Robot Framework', - 'Framework :: Robot Framework :: Library', - 'License :: OSI Approved :: Apache Software License' - ] -) diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 42183a7..0000000 --- a/tox.ini +++ /dev/null @@ -1,12 +0,0 @@ -# Tox (https://tox.readthedocs.io/) is a tool for running tests -# in multiple virtualenvs. This configuration file will run the -# test suite on all supported python versions. To use it, "pip install tox" -# and then run "tox" from this directory. - -[tox] -envlist = py27 - -[testenv] -commands = {envpython} setup.py test -deps = - diff --git a/vcpeutils/SoUtils.py b/vcpeutils/SoUtils.py deleted file mode 100755 index 1248613..0000000 --- a/vcpeutils/SoUtils.py +++ /dev/null @@ -1,371 +0,0 @@ -#! /usr/bin/python - -import sys -import logging -import requests -import json -from datetime import datetime -#import progressbar -import time -import csar_parser -import preload -from vcpecommon import * -from robot.api import logger - - -class SoUtils: - - def __init__(self): - """ - :param api_version: must be 'v4' or 'v5' - """ - self.tmp_solution_for_so_bug = False - #self.logger = logging.getLogger(__name__) - self.logger = logger - self.vcpecommon = VcpeCommon() - self.api_version = 'v4' - self.service_req_api_url = self.vcpecommon.so_req_api_url[self.api_version] - - - def submit_create_req(self, req_json, req_type, service_instance_id=None, vnf_instance_id=None): - """ - POST {serverRoot}/serviceInstances/v4 - POST {serverRoot}/serviceInstances/v4/{serviceInstanceId}/vnfs - POST {serverRoot}/serviceInstances/v4/{serviceInstanceId}/networks - POST {serverRoot}/serviceInstances/v4/{serviceInstanceId}/vnfs/{vnfInstanceId}/vfModules - :param req_json: - :param service_instance_id: this is required only for networks, vnfs, and vf modules - :param req_type: - :param vnf_instance_id: - :return: req_id, instance_id - """ - if req_type == 'service': - url = self.service_req_api_url - elif req_type == 'vnf': - url = '/'.join([self.service_req_api_url, service_instance_id, 'vnfs']) - elif req_type == 'network': - url = '/'.join([self.service_req_api_url, service_instance_id, 'networks']) - elif req_type == 'vfmodule': - url = '/'.join([self.service_req_api_url, service_instance_id, 'vnfs', vnf_instance_id, 'vfModules']) - else: - self.logger.error('Invalid request type: {0}. Can only be service/vnf/network/vfmodule'.format(req_type)) - return None, None - - self.logger.info(url) - r = requests.post(url, headers=self.vcpecommon.so_headers, auth=self.vcpecommon.so_userpass, json=req_json) - self.logger.debug(r) - response = r.json() - - self.logger.debug('---------------------------------------------------------------') - self.logger.debug('------- Creation request submitted to SO, got response --------') - self.logger.debug(json.dumps(response, indent=4, sort_keys=True)) - self.logger.debug('---------------------------------------------------------------') - req_id = response.get('requestReferences', {}).get('requestId', '') - instance_id = response.get('requestReferences', {}).get('instanceId', '') - - return req_id, instance_id - - def check_progress(self, req_id, eta=0, interval=5): - if not req_id: - self.logger.error('Error when checking SO request progress, invalid request ID: ' + req_id) - return False - duration = 0.0 - #bar = progressbar.ProgressBar(redirect_stdout=True) - url = self.vcpecommon.so_check_progress_api_url + '/' + req_id - - while True: - time.sleep(interval) - r = requests.get(url, headers=self.vcpecommon.so_headers, auth=self.vcpecommon.so_userpass) - response = r.json() - - duration += interval - if eta > 0: - percentage = min(95, 100 * duration / eta) - else: - percentage = int(response['request']['requestStatus']['percentProgress']) - - if response['request']['requestStatus']['requestState'] == 'IN_PROGRESS': - self.logger.debug('------------------Request Status-------------------------------') - self.logger.debug(json.dumps(response, indent=4, sort_keys=True)) - #bar.update(percentage) - else: - self.logger.debug('---------------------------------------------------------------') - self.logger.debug('----------------- Creation Request Results --------------------') - self.logger.debug(json.dumps(response, indent=4, sort_keys=True)) - self.logger.debug('---------------------------------------------------------------') - flag = response['request']['requestStatus']['requestState'] == 'COMPLETE' - if not flag: - self.logger.error('Request failed.') - self.logger.error(json.dumps(response, indent=4, sort_keys=True)) - #bar.update(100) - #bar.finish() - return flag - - def add_req_info(self, req_details, instance_name, product_family_id=None): - req_details['requestInfo'] = { - 'instanceName': instance_name, - 'source': 'VID', - 'suppressRollback': 'true', - 'requestorId': 'vCPE-Robot' - } - if product_family_id: - req_details['requestInfo']['productFamilyId'] = product_family_id - - def add_related_instance(self, req_details, instance_id, instance_model): - instance = {"instanceId": instance_id, "modelInfo": instance_model} - if 'relatedInstanceList' not in req_details: - req_details['relatedInstanceList'] = [{"relatedInstance": instance}] - else: - req_details['relatedInstanceList'].append({"relatedInstance": instance}) - - def generate_vnf_or_network_request(self, req_type, instance_name, vnf_or_network_model, service_instance_id, - service_model): - req_details = { - 'modelInfo': vnf_or_network_model, - #'cloudConfiguration': {"lcpCloudRegionId": self.vcpecommon.os_region_name, - # "tenantId": self.vcpecommon.os_tenant_id}, - 'cloudConfiguration': {"lcpCloudRegionId": self.region_name, - "tenantId": self.tenant_id}, - 'requestParameters': {"userParams": []}, - 'platform': {"platformName": "Platform-Demonstration"} - } - self.add_req_info(req_details, instance_name, self.vcpecommon.product_family_id) - self.add_related_instance(req_details, service_instance_id, service_model) - return {'requestDetails': req_details} - - def generate_vfmodule_request(self, instance_name, vfmodule_model, service_instance_id, - service_model, vnf_instance_id, vnf_model): - req_details = { - 'modelInfo': vfmodule_model, - #'cloudConfiguration': {"lcpCloudRegionId": self.vcpecommon.os_region_name, - # "tenantId": self.vcpecommon.os_tenant_id}, - 'cloudConfiguration': {"lcpCloudRegionId": self.region_name, - "tenantId": self.tenant_id}, - 'requestParameters': {"usePreload": 'true'} - } - self.add_req_info(req_details, instance_name, self.vcpecommon.product_family_id) - self.add_related_instance(req_details, service_instance_id, service_model) - self.add_related_instance(req_details, vnf_instance_id, vnf_model) - return {'requestDetails': req_details} - - def generate_service_request(self, instance_name, model): - req_details = { - 'modelInfo': model, - 'subscriberInfo': {'globalSubscriberId': self.vcpecommon.global_subscriber_id}, - 'requestParameters': { - "userParams": [], - "subscriptionServiceType": "vCPE", - "aLaCarte": 'true' - } - } - self.add_req_info(req_details, instance_name) - self.add_project_info(req_details) - self.add_owning_entity(req_details) - return {'requestDetails': req_details} - - def add_project_info(self, req_details): - req_details['project'] = {'projectName': self.vcpecommon.project_name} - - def add_owning_entity(self, req_details): - req_details['owningEntity'] = {'owningEntityId': self.vcpecommon.owning_entity_id, - 'owningEntityName': self.vcpecommon.owning_entity_name} - - def generate_custom_service_request(self, instance_name, model, brg_mac): - brg_mac_enc = brg_mac.replace(':', '-') - req_details = { - 'modelInfo': model, - 'subscriberInfo': {'subscriberName': 'Kaneohe', - 'globalSubscriberId': self.vcpecommon.global_subscriber_id}, - 'cloudConfiguration': {"lcpCloudRegionId": self.region_name, - "tenantId": self.tenant_id}, - 'requestParameters': { - "userParams": [ - { - 'name': 'BRG_WAN_MAC_Address', - 'value': brg_mac - }, - { - 'name': 'VfModuleNames', - 'value': [ - { - 'VfModuleModelInvariantUuid': self.vcpecommon.vgw_VfModuleModelInvariantUuid, - 'VfModuleName': 'VGW2BRG-{0}'.format(brg_mac_enc) - } - ] - }, - { - "name": "Customer_Location", - "value": self.vcpecommon.customer_location_used_by_oof - }, - { - "name": "Homing_Solution", - "value": self.vcpecommon.homing_solution - } - ], - "subscriptionServiceType": "vCPE", - 'aLaCarte': 'false' - } - } - self.add_req_info(req_details, instance_name, self.vcpecommon.custom_product_family_id) - self.add_project_info(req_details) - self.add_owning_entity(req_details) - return {'requestDetails': req_details} - - def create_custom_service(self, csar_file, brg_mac, name_suffix=None): - parser = csar_parser.CsarParser() - if not parser.parse_csar(csar_file): - return False - - # yyyymmdd_hhmm - if not name_suffix: - name_suffix = '_' + datetime.now().strftime('%Y%m%d%H%M') - - # create service - instance_name = '_'.join([self.vcpecommon.instance_name_prefix['service'], - parser.svc_model['modelName'][0:10], name_suffix]) - instance_name = instance_name.lower() - req = self.generate_custom_service_request(instance_name, parser.svc_model, brg_mac) - self.logger.info(json.dumps(req, indent=2, sort_keys=True)) - self.logger.info('Creating custom service {0}.'.format(instance_name)) - req_id, svc_instance_id = self.submit_create_req(req, 'service') - if not self.check_progress(req_id, 140): - return False - return True - - def wait_for_aai(self, node_type, uuid): - self.logger.info('Waiting for AAI traversal to complete...') - #bar = progressbar.ProgressBar() - for i in range(30): - time.sleep(1) - #bar.update(i*100.0/30) - if self.vcpecommon.is_node_in_aai(node_type, uuid): - #bar.update(100) - #bar.finish() - return - - self.logger.error("AAI traversal didn't finish in 30 seconds. Something is wrong. Type {0}, UUID {1}".format( - node_type, uuid)) - sys.exit() - - def create_entire_service(self, csar_file, vnf_template_file, preload_dict, name_suffix, region_name, tenant_id, heatbridge=False): - """ - :param csar_file: - :param vnf_template_file: - :param preload_dict: - :param name_suffix: - :return: service instance UUID - """ - self.region_name=region_name - self.tenant_id=tenant_id - self.logger.info('\n----------------------------------------------------------------------------------') - self.logger.info('Start to create entire service defined in csar: {0}'.format(csar_file)) - parser = csar_parser.CsarParser() - self.logger.info('Parsing csar ...') - if not parser.parse_csar(csar_file): - self.logger.error('Cannot parse csar: {0}'.format(csar_file)) - return None - - # Set Global timestamp for instancenames - global_timestamp = datetime.now().strftime("%Y%m%d%H%M%S") - # create service - instance_name = '_'.join([self.vcpecommon.instance_name_prefix['service'], - parser.svc_model['modelName'], global_timestamp, name_suffix]) - instance_name = instance_name.lower() - instance_name = instance_name.replace(' ','') - instance_name = instance_name.replace(':','') - self.logger.info('Creating service instance: {0}.'.format(instance_name)) - req = self.generate_service_request(instance_name, parser.svc_model) - self.logger.debug(json.dumps(req, indent=2, sort_keys=True)) - req_id, svc_instance_id = self.submit_create_req(req, 'service') - if not self.check_progress(req_id, eta=2, interval=5): - return None - - # wait for AAI to complete traversal - self.wait_for_aai('service', svc_instance_id) - - # create networks - for model in parser.net_models: - base_name = model['modelCustomizationName'].lower().replace('mux_vg', 'mux_gw') - network_name = '_'.join([self.vcpecommon.instance_name_prefix['network'], base_name, name_suffix]) - network_name = network_name.lower() - self.logger.info('Creating network: ' + network_name) - req = self.generate_vnf_or_network_request('network', network_name, model, svc_instance_id, - parser.svc_model) - self.logger.debug(json.dumps(req, indent=2, sort_keys=True)) - req_id, net_instance_id = self.submit_create_req(req, 'network', svc_instance_id) - if not self.check_progress(req_id, eta=20): - return None - - self.logger.info('Changing subnet name to ' + self.vcpecommon.network_name_to_subnet_name(network_name)) - self.vcpecommon.set_network_name(network_name) - subnet_name_changed = False - for i in range(20): - time.sleep(3) - if self.vcpecommon.set_subnet_name(network_name): - subnet_name_changed = True - break - - if not subnet_name_changed: - self.logger.error('Failed to change subnet name for ' + network_name) - return None - - - vnf_model = None - vnf_instance_id = None - # create VNF - if len(parser.vnf_models) == 1: - vnf_model = parser.vnf_models[0] - vnf_instance_name = '_'.join([self.vcpecommon.instance_name_prefix['vnf'], - vnf_model['modelCustomizationName'].split(' ')[0], name_suffix]) - vnf_instance_name = vnf_instance_name.lower() - vnf_instance_name = vnf_instance_name.replace(' ','') - vnf_instance_name = vnf_instance_name.replace(':','') - self.logger.info('Creating VNF: ' + vnf_instance_name) - req = self.generate_vnf_or_network_request('vnf', vnf_instance_name, vnf_model, svc_instance_id, - parser.svc_model) - self.logger.debug(json.dumps(req, indent=2, sort_keys=True)) - req_id, vnf_instance_id = self.submit_create_req(req, 'vnf', svc_instance_id) - if not self.check_progress(req_id, eta=2, interval=5): - self.logger.error('Failed to create VNF {0}.'.format(vnf_instance_name)) - return False - - # wait for AAI to complete traversal - if not vnf_instance_id: - self.logger.error('No VNF instance ID returned!') - sys.exit() - self.wait_for_aai('vnf', vnf_instance_id) - - # SDNC Preload - - preloader = preload.Preload(self.vcpecommon) - preloader.preload_vfmodule(vnf_template_file, svc_instance_id, parser.vnf_models[0], parser.vfmodule_models[0], - preload_dict, name_suffix) - - # create VF Module - if len(parser.vfmodule_models) == 1: - if not vnf_instance_id or not vnf_model: - self.logger.error('Invalid VNF instance ID or VNF model!') - sys.exit() - - model = parser.vfmodule_models[0] - vfmodule_instance_name = '_'.join([self.vcpecommon.instance_name_prefix['vfmodule'], - model['modelCustomizationName'].split('..')[0], name_suffix]) - vfmodule_instance_name = vfmodule_instance_name.lower() - vfmodule_instance_name = vfmodule_instance_name.replace(' ','') - vfmoduel_instance_name = vfmodule_instance_name.replace(':','') - self.logger.info('Creating VF Module: ' + vfmodule_instance_name) - req = self.generate_vfmodule_request(vfmodule_instance_name, model, svc_instance_id, parser.svc_model, - vnf_instance_id, vnf_model) - self.logger.debug(json.dumps(req, indent=2, sort_keys=True)) - req_id, vfmodule_instance_id = self.submit_create_req(req, 'vfmodule', svc_instance_id, vnf_instance_id) - if not self.check_progress(req_id, eta=70, interval=50): - self.logger.error('Failed to create VF Module {0}.'.format(vfmodule_instance_name)) - return None - - # run heatbridge - # removed until we fold in heatbridge - #if heatbridge: - #self.vcpecommon.heatbridge(vfmodule_instance_name, svc_instance_id) - #self.vcpecommon.save_vgmux_vnf_name(vnf_instance_name) - - return svc_instance_id diff --git a/vcpeutils/csar_parser.py b/vcpeutils/csar_parser.py deleted file mode 100755 index f101364..0000000 --- a/vcpeutils/csar_parser.py +++ /dev/null @@ -1,231 +0,0 @@ -#! /usr/bin/python -import os -import zipfile -import shutil -import yaml -import json -import logging - - -class CsarParser: - def __init__(self): - self.logger = logging.getLogger(__name__) - self.svc_model = {} - self.net_models = [] # there could be multiple networks - self.vnf_models = [] # this version only support a single VNF in the service template - self.vfmodule_models = [] # this version only support a single VF module in the service template - - def get_service_yaml_from_csar(self, csar_file): - """ - :param csar_file: csar file path name, e.g. 'csar/vgmux.csar' - :return: - """ - tmpdir = './__tmp' - if os.path.isdir(tmpdir): - shutil.rmtree(tmpdir) - os.mkdir(tmpdir) - - with zipfile.ZipFile(csar_file, "r") as zip_ref: - zip_ref.extractall(tmpdir) - - yamldir = tmpdir + '/Definitions' - if os.path.isdir(yamldir): - for filename in os.listdir(yamldir): - # look for service template like this: service-Vcpesvcbrgemu111601-template.yml - if filename.startswith('service-') and filename.endswith('-template.yml'): - return os.path.join(yamldir, filename) - - self.logger.error('Invalid file: ' + csar_file) - return '' - - def get_service_model_info(self, svc_template): - """ extract service model info from yaml and convert to what to be used in SO request - Sample from yaml: - { - "UUID": "aed4fc5e-b871-4e26-8531-ceabd46df85e", - "category": "Network L1-3", - "description": "Infra service", - "ecompGeneratedNaming": true, - "invariantUUID": "c806682a-5b3a-44d8-9e88-0708be151296", - "name": "vcpesvcinfra111601", - "namingPolicy": "", - "serviceEcompNaming": true, - "serviceRole": "", - "serviceType": "", - "type": "Service" - }, - - Convert to - { - "modelType": "service", - "modelInvariantId": "ca4c7a70-06fd-45d8-8b9e-c9745d25bf2b", - "modelVersionId": "5d8911b4-e50c-4096-a81e-727a8157193c", - "modelName": "vcpesvcbrgemu111601", - "modelVersion": "1.0" - }, - - """ - if svc_template['metadata']['type'] != 'Service': - self.logger.error('csar error: metadata->type is not Service') - return - - metadata = svc_template['metadata'] - self.svc_model = { - 'modelType': 'service', - 'modelInvariantId': metadata['invariantUUID'], - 'modelVersionId': metadata['UUID'], - 'modelName': metadata['name'] - } - if 'version' in metadata: - self.svc_model['modelVersion'] = metadata['version'] - else: - self.svc_model['modelVersion'] = '1.0' - - def get_vnf_and_network_model_info(self, svc_template): - """ extract vnf and network model info from yaml and convert to what to be used in SO request - Sample from yaml: - "topology_template": { - "node_templates": { - "CPE_PUBLIC": { - "metadata": { - "UUID": "33b2c367-a165-4bb3-81c3-0150cd06ceff", - "category": "Generic", - "customizationUUID": "db1d4ac2-62cd-4e5d-b2dc-300dbd1a5da1", - "description": "Generic NeutronNet", - "invariantUUID": "3d4c0e47-4794-4e98-a794-baaced668930", - "name": "Generic NeutronNet", - "resourceVendor": "ATT (Tosca)", - "resourceVendorModelNumber": "", - "resourceVendorRelease": "1.0.0.wd03", - "subcategory": "Network Elements", - "type": "VL", - "version": "1.0" - }, - "type": "org.openecomp.resource.vl.GenericNeutronNet" - }, - Convert to - { - "modelType": "network", - "modelInvariantId": "3d4c0e47-4794-4e98-a794-baaced668930", - "modelVersionId": "33b2c367-a165-4bb3-81c3-0150cd06ceff", - "modelName": "Generic NeutronNet", - "modelVersion": "1.0", - "modelCustomizationId": "db1d4ac2-62cd-4e5d-b2dc-300dbd1a5da1", - "modelCustomizationName": "CPE_PUBLIC" - }, - """ - node_dic = svc_template['topology_template']['node_templates'] - for node_name, v in node_dic.items(): - model = { - 'modelInvariantId': v['metadata']['invariantUUID'], - 'modelVersionId': v['metadata']['UUID'], - 'modelName': v['metadata']['name'], - 'modelVersion': v['metadata']['version'], - 'modelCustomizationId': v['metadata']['customizationUUID'], - 'modelCustomizationName': node_name - } - - if v['type'].startswith('org.openecomp.resource.vl.GenericNeutronNet'): - # a neutron network is found - self.logger.info('Parser found a network: ' + node_name) - model['modelType'] = 'network' - self.net_models.append(model) - elif v['type'].startswith('org.openecomp.resource.vf.'): - # a VNF is found - self.logger.info('Parser found a VNF: ' + node_name) - model['modelType'] = 'vnf' - self.vnf_models.append(model) - else: - self.logger.warning('Parser found a node that is neither a network nor a VNF: ' + node_name) - - def get_vfmodule_model_info(self, svc_template): - """ extract network model info from yaml and convert to what to be used in SO request - Sample from yaml: - "topology_template": { - "groups": { - "vspinfra1116010..Vspinfra111601..base_vcpe_infra..module-0": { - "metadata": { - "vfModuleModelCustomizationUUID": "11ddac51-30e3-4a3f-92eb-2eb99c2cb288", - "vfModuleModelInvariantUUID": "02f70416-581e-4f00-bde1-d65e69af95c5", - "vfModuleModelName": "Vspinfra111601..base_vcpe_infra..module-0", - "vfModuleModelUUID": "88c78078-f1fd-4f73-bdd9-10420b0f6353", - "vfModuleModelVersion": "1" - }, - "properties": { - "availability_zone_count": null, - "initial_count": 1, - "max_vf_module_instances": 1, - "min_vf_module_instances": 1, - "vf_module_description": null, - "vf_module_label": "base_vcpe_infra", - "vf_module_type": "Base", - "vfc_list": null, - "volume_group": false - }, - "type": "org.openecomp.groups.VfModule" - } - }, - Convert to - { - "modelType": "vfModule", - "modelInvariantId": "02f70416-581e-4f00-bde1-d65e69af95c5", - "modelVersionId": "88c78078-f1fd-4f73-bdd9-10420b0f6353", - "modelName": "Vspinfra111601..base_vcpe_infra..module-0", - "modelVersion": "1", - "modelCustomizationId": "11ddac51-30e3-4a3f-92eb-2eb99c2cb288", - "modelCustomizationName": "Vspinfra111601..base_vcpe_infra..module-0" - }, - """ - node_dic = svc_template['topology_template']['groups'] - for node_name, v in node_dic.items(): - if v['type'].startswith('org.openecomp.groups.VfModule'): - model = { - 'modelType': 'vfModule', - 'modelInvariantId': v['metadata']['vfModuleModelInvariantUUID'], - 'modelVersionId': v['metadata']['vfModuleModelUUID'], - 'modelName': v['metadata']['vfModuleModelName'], - 'modelVersion': v['metadata']['vfModuleModelVersion'], - 'modelCustomizationId': v['metadata']['vfModuleModelCustomizationUUID'], - 'modelCustomizationName': v['metadata']['vfModuleModelName'] - } - self.vfmodule_models.append(model) - self.logger.info('Parser found a VF module: ' + model['modelCustomizationName']) - - def parse_service_yaml(self, filename): - # clean up - self.svc_model = {} - self.net_models = [] # there could be multiple networks - self.vnf_models = [] # this version only support a single VNF in the service template - self.vfmodule_models = [] # this version only support a single VF module in the service template - - svc_template = yaml.load(file(filename, 'r')) - self.get_service_model_info(svc_template) - self.get_vnf_and_network_model_info(svc_template) - self.get_vfmodule_model_info(svc_template) - - return True - - def parse_csar(self, csar_file): - yaml_file = self.get_service_yaml_from_csar(csar_file) - if yaml_file != '': - return self.parse_service_yaml(yaml_file) - - def print_models(self): - print('---------Service Model----------') - print(json.dumps(self.svc_model, indent=2, sort_keys=True)) - - print('---------Network Model(s)----------') - for model in self.net_models: - print(json.dumps(model, indent=2, sort_keys=True)) - - print('---------VNF Model(s)----------') - for model in self.vnf_models: - print(json.dumps(model, indent=2, sort_keys=True)) - - print('---------VF Module Model(s)----------') - for model in self.vfmodule_models: - print(json.dumps(model, indent=2, sort_keys=True)) - - def test(self): - self.parse_csar('csar/service-Vcpesvcinfra111601-csar.csar') - self.print_models() diff --git a/vcpeutils/preload.py b/vcpeutils/preload.py deleted file mode 100755 index aab28f9..0000000 --- a/vcpeutils/preload.py +++ /dev/null @@ -1,231 +0,0 @@ -#! /usr/bin/python - -import requests -import json -import sys -from datetime import datetime -from vcpecommon import * -import csar_parser -#import logging -from robot.api import logger -import base64 - - -class Preload: - def __init__(self, vcpecommon): - #self.logger = logging.getLogger(__name__) - self.logger = logger - self.vcpecommon = vcpecommon - - def replace(self, sz, replace_dict): - for old_string, new_string in replace_dict.items(): - sz = sz.replace(old_string, new_string) - if self.vcpecommon.template_variable_symbol in sz: - self.logger.error('Error! Cannot find a value to replace ' + sz) - return sz - - def generate_json(self, template_file, replace_dict): - with open(template_file) as json_input: - json_data = json.load(json_input) - stk = [json_data] - while len(stk) > 0: - data = stk.pop() - for k, v in data.items(): - if type(v) is dict: - stk.append(v) - elif type(v) is list: - stk.extend(v) - elif type(v) is str or type(v) is unicode: - if self.vcpecommon.template_variable_symbol in v: - data[k] = self.replace(v, replace_dict) - else: - self.logger.warning('Unexpected line in template: %s. Look for value %s', template_file, v) - return json_data - - def reset_sniro(self): - self.logger.debug('Clearing SNIRO data') - r = requests.post(self.vcpecommon.sniro_url + '/reset', headers=self.vcpecommon.sniro_headers) - if 2 != r.status_code / 100: - self.logger.debug(r.content) - self.logger.error('Clearing SNIRO date failed.') - sys.exit() - - def preload_sniro(self, template_sniro_data, template_sniro_request, tunnelxconn_ar_name, vgw_name, vbrg_ar_name, - vgmux_svc_instance_uuid, vbrg_svc_instance_uuid): - self.reset_sniro() - self.logger.info('Preloading SNIRO for homing service') - replace_dict = {'${tunnelxconn_ar_name}': tunnelxconn_ar_name, - '${vgw_name}': vgw_name, - '${brg_ar_name}': vbrg_ar_name, - '${vgmux_svc_instance_uuid}': vgmux_svc_instance_uuid, - '${vbrg_svc_instance_uuid}': vbrg_svc_instance_uuid - } - sniro_data = self.generate_json(template_sniro_data, replace_dict) - self.logger.debug('SNIRO data:') - self.logger.debug(json.dumps(sniro_data, indent=4, sort_keys=True)) - - base64_sniro_data = base64.b64encode(json.dumps(sniro_data)) - self.logger.debug('SNIRO data: 64') - self.logger.debug(base64_sniro_data) - replace_dict = {'${base64_sniro_data}': base64_sniro_data, '${sniro_ip}': self.vcpecommon.hosts['robot']} - sniro_request = self.generate_json(template_sniro_request, replace_dict) - self.logger.debug('SNIRO request:') - self.logger.debug(json.dumps(sniro_request, indent=4, sort_keys=True)) - - r = requests.post(self.vcpecommon.sniro_url, headers=self.vcpecommon.sniro_headers, json=sniro_request) - if 2 != r.status_code / 100: - response = r.json() - self.logger.debug(json.dumps(response, indent=4, sort_keys=True)) - self.logger.error('SNIRO preloading failed.') - sys.exit() - - return True - - def preload_network(self, template_file, network_role, subnet_start_ip, subnet_gateway, common_dict, name_suffix): - """ - :param template_file: - :param network_role: cpe_signal, cpe_public, brg_bng, bng_mux, mux_gw - :param subnet_start_ip: - :param subnet_gateway: - :param name_suffix: e.g. '201711201311' - :return: - """ - network_name = '_'.join([self.vcpecommon.instance_name_prefix['network'], network_role.lower(), name_suffix]) - subnet_name = self.vcpecommon.network_name_to_subnet_name(network_name) - common_dict['${' + network_role+'_net}'] = network_name - common_dict['${' + network_role+'_subnet}'] = subnet_name - replace_dict = {'${network_role}': network_role, - '${service_type}': 'vCPE', - '${network_type}': 'Generic NeutronNet', - '${network_name}': network_name, - '${subnet_start_ip}': subnet_start_ip, - '${subnet_gateway}': subnet_gateway - } - self.logger.info('Preloading network ' + network_role) - return self.preload(template_file, replace_dict, self.vcpecommon.sdnc_preload_network_url) - - def preload(self, template_file, replace_dict, url): - self.logger.debug(json.dumps(replace_dict, indent=4, sort_keys=True)) - json_data = self.generate_json(template_file, replace_dict) - self.logger.debug(json.dumps(json_data, indent=4, sort_keys=True)) - r = requests.post(url, headers=self.vcpecommon.sdnc_headers, auth=self.vcpecommon.sdnc_userpass, json=json_data) - response = r.json() - if int(response.get('output', {}).get('response-code', 0)) != 200: - self.logger.debug(json.dumps(response, indent=4, sort_keys=True)) - self.logger.error('Preloading failed.') - return False - return True - - def preload_vgw(self, template_file, brg_mac, commont_dict, name_suffix): - replace_dict = {'${brg_mac}': brg_mac, - '${suffix}': name_suffix - } - replace_dict.update(commont_dict) - self.logger.info('Preloading vGW') - return self.preload(template_file, replace_dict, self.vcpecommon.sdnc_preload_vnf_url) - - def preload_vgw_gra(self, template_file, brg_mac, commont_dict, name_suffix, vgw_vfmod_name_index): - replace_dict = {'${brg_mac}': brg_mac, - '${suffix}': name_suffix, - '${vgw_vfmod_name_index}': vgw_vfmod_name_index - } - replace_dict.update(commont_dict) - self.logger.info('Preloading vGW-GRA') - return self.preload(template_file, replace_dict, self.vcpecommon.sdnc_preload_gra_url) - - def preload_vfmodule(self, template_file, service_instance_id, vnf_model, vfmodule_model, common_dict, name_suffix): - """ - :param template_file: - :param service_instance_id: - :param vnf_model: parsing results from csar_parser - :param vfmodule_model: parsing results from csar_parser - :param common_dict: - :param name_suffix: - :return: - """ - - # examples: - # vfmodule_model['modelCustomizationName']: "Vspinfra111601..base_vcpe_infra..module-0", - # vnf_model['modelCustomizationName']: "vspinfra111601 0", - - vfmodule_name = '_'.join([self.vcpecommon.instance_name_prefix['vfmodule'], - vfmodule_model['modelCustomizationName'].split('..')[0].lower(), name_suffix]) - - # vnf_type and generic_vnf_type are identical - replace_dict = {'${vnf_type}': vfmodule_model['modelCustomizationName'], - '${generic_vnf_type}': vfmodule_model['modelCustomizationName'], - '${service_type}': service_instance_id, - '${generic_vnf_name}': vnf_model['modelCustomizationName'], - '${vnf_name}': vfmodule_name, - '${mr_ip_addr}': self.vcpecommon.mr_ip_addr, - '${mr_ip_port}': self.vcpecommon.mr_ip_port, - '${sdnc_oam_ip}': self.vcpecommon.sdnc_oam_ip, - '${suffix}': name_suffix} - replace_dict.update(common_dict) - self.logger.info('Preloading VF Module ' + vfmodule_name) - return self.preload(template_file, replace_dict, self.vcpecommon.sdnc_preload_vnf_url) - - def preload_all_networks(self, template_file, name_suffix): - common_dict = {'${' + k + '}': v for k, v in self.vcpecommon.common_preload_config.items()} - for network, v in self.vcpecommon.preload_network_config.items(): - subnet_start_ip, subnet_gateway_ip = v - if not self.preload_network(template_file, network, subnet_start_ip, subnet_gateway_ip, - common_dict, name_suffix): - return None - return common_dict - - def test(self): - # this is for testing purpose - name_suffix = datetime.now().strftime('%Y%m%d%H%M') - vcpecommon = VcpeCommon() - preloader = Preload(vcpecommon) - - network_dict = {'${' + k + '}': v for k, v in self.vcpecommon.common_preload_config.items()} - template_file = 'preload_templates/template.network.json' - for k, v in self.vcpecommon.preload_network_config.items(): - if not preloader.preload_network(template_file, k, v[0], v[1], network_dict, name_suffix): - break - - print('---------------------------------------------------------------') - print('Network related replacement dictionary:') - print(json.dumps(network_dict, indent=4, sort_keys=True)) - print('---------------------------------------------------------------') - - keys = ['infra', 'bng', 'gmux', 'brg'] - for key in keys: - csar_file = self.vcpecommon.find_file(key, 'csar', 'csar') - template_file = self.vcpecommon.find_file(key, 'json', 'preload_templates') - if csar_file and template_file: - parser = csar_parser.CsarParser() - parser.parse_csar(csar_file) - service_instance_id = 'test112233' - preloader.preload_vfmodule(template_file, service_instance_id, parser.vnf_models[0], - parser.vfmodule_models[0], network_dict, name_suffix) - - def test_sniro(self): - template_sniro_data = self.vcpecommon.find_file('sniro_data', 'json', 'preload_templates') - template_sniro_request = self.vcpecommon.find_file('sniro_request', 'json', 'preload_templates') - - vcperescust_csar = self.vcpecommon.find_file('rescust', 'csar', 'csar') - parser = csar_parser.CsarParser() - parser.parse_csar(vcperescust_csar) - tunnelxconn_ar_name = None - brg_ar_name = None - vgw_name = None - for model in parser.vnf_models: - if 'tunnel' in model['modelCustomizationName']: - tunnelxconn_ar_name = model['modelCustomizationName'] - elif 'brg' in model['modelCustomizationName']: - brg_ar_name = model['modelCustomizationName'] - elif 'vgw' in model['modelCustomizationName']: - vgw_name = model['modelCustomizationName'] - - if not (tunnelxconn_ar_name and brg_ar_name and vgw_name): - self.logger.error('Cannot find all names from %s.', vcperescust_csar) - sys.exit() - - vgmux_svc_instance_uuid = '88888888888888' - vbrg_svc_instance_uuid = '999999999999999' - - self.preload_sniro(template_sniro_data, template_sniro_request, tunnelxconn_ar_name, vgw_name, brg_ar_name, - vgmux_svc_instance_uuid, vbrg_svc_instance_uuid) diff --git a/vcpeutils/vcpecommon.py b/vcpeutils/vcpecommon.py deleted file mode 100755 index 95b5bbe..0000000 --- a/vcpeutils/vcpecommon.py +++ /dev/null @@ -1,325 +0,0 @@ -import json -import logging -import os -import pickle -import re -import sys - -import ipaddress -import requests -import commands -import time -from novaclient import client as openstackclient -from netaddr import IPAddress, IPNetwork - -class VcpeCommon: - ############################################################################################# - # Start: configurations that you must change for a new ONAP installation - external_net_addr = '10.12.0.0' - external_net_prefix_len = 16 - ############################################################################################# - # set the openstack cloud access credentials here - oom_mode = True - - cloud = { - '--os-auth-url': 'http://10.12.25.2:5000', - '--os-username': 'kxi', - '--os-user-domain-id': 'default', - '--os-project-domain-id': 'default', - '--os-tenant-id': '09d8566ea45e43aa974cf447ed591d77' if oom_mode else '1e097c6713e74fd7ac8e4295e605ee1e', - '--os-region-name': 'RegionOne', - '--os-password': 'n3JhGMGuDzD8', - '--os-project-domain-name': 'Integration-SB-03' if oom_mode else 'Integration-SB-07', - '--os-identity-api-version': '3' - } - - common_preload_config = { - 'oam_onap_net': 'oam_network_2No2' if oom_mode else 'oam_onap_lAky', - 'oam_onap_subnet': 'oam_network_2No2' if oom_mode else 'oam_onap_lAky', - 'public_net': 'external', - 'public_net_id': '971040b2-7059-49dc-b220-4fab50cb2ad4' - } - sdnc_controller_pod = 'dev-sdnc-sdnc-0' - - ############################################################################################# - - template_variable_symbol = '${' - cpe_vm_prefix = 'zdcpe' - ############################################################################################# - # preloading network config - # key=network role - # value = [subnet_start_ip, subnet_gateway_ip] - preload_network_config = { - 'cpe_public': ['10.2.0.2', '10.2.0.1'], - 'cpe_signal': ['10.4.0.2', '10.4.0.1'], - 'brg_bng': ['10.3.0.2', '10.3.0.1'], - 'bng_mux': ['10.1.0.10', '10.1.0.1'], - 'mux_gw': ['10.5.0.10', '10.5.0.1'] - } - - dcae_ves_collector_name = 'dcae-bootstrap' - global_subscriber_id = 'Demonstration' - project_name = 'Project-Demonstration' - owning_entity_id = '520cc603-a3c4-4ec2-9ef4-ca70facd79c0' - owning_entity_name = 'OE-Demonstration1' - - def __init__(self, extra_host_names=None): - rootlogger = logging.getLogger() - handler = logging.StreamHandler() - formatter = logging.Formatter('%(asctime)s %(levelname)s %(name)s.%(funcName)s(): %(message)s') - handler.setFormatter(formatter) - rootlogger.addHandler(handler) - rootlogger.setLevel(logging.INFO) - - self.logger = logging.getLogger(__name__) - self.logger.propagate = False - self.logger.addHandler(handler) - self.logger.setLevel(logging.DEBUG) - self.logger.info('Initializing configuration') - - # CHANGEME: vgw_VfModuleModelInvariantUuid is in rescust service csar, look in service-VcpesvcRescust1118-template.yml for groups vgw module metadata. TODO: read this value automcatically - self.vgw_VfModuleModelInvariantUuid = '26d6a718-17b2-4ba8-8691-c44343b2ecd2' - # CHANGEME: OOM: this is the address that the brg and bng will nat for sdnc access - 10.0.0.x address of k8 host for sdnc-0 container - #self.sdnc_oam_ip = self.get_pod_node_oam_ip('sdnc-sdnc-0') - self.sdnc_oam_ip = 'sdnc.onap' - # CHANGEME: OOM: this is a k8s host external IP, e.g. oom-k8s-01 IP - #self.oom_so_sdnc_aai_ip = self.get_pod_node_public_ip('sdnc-sdnc-0') - #self.oom_so_sdnc_aai_ip = self.get_pod_node_public_ip('sdnc-sdnc-0') - # CHANGEME: OOM: this is a k8s host external IP, e.g. oom-k8s-01 IP - #self.oom_dcae_ves_collector = self.oom_so_sdnc_aai_ip - # CHANGEME: OOM: this is a k8s host external IP, e.g. oom-k8s-01 IP - #self.mr_ip_addr = self.oom_so_sdnc_aai_ip - self.mr_ip_addr = 'mr.onap' - #self.mr_ip_port = '30227' - self.mr_ip_port = '3904' - #self.so_nbi_port = '30277' if self.oom_mode else '8080' - self.so_nbi_port = '8080' - #self.sdnc_preloading_port = '30202' if self.oom_mode else '8282' - self.sdnc_preloading_port = '8282' - #self.aai_query_port = '30233' if self.oom_mode else '8443' - self.aai_query_port = '8443' - #self.sniro_port = '30288' if self.oom_mode else '8080' - self.sniro_port = '8080' - - self.host_names = ['so', 'sdnc', 'robot', 'aai', self.dcae_ves_collector_name] - if extra_host_names: - self.host_names.extend(extra_host_names) - # get IP addresses - #self.hosts = self.get_vm_ip(self.host_names, self.external_net_addr, self.external_net_prefix_len) - self.hosts = { 'so': 'so.onap', 'sdnc': 'sdnc.onap', 'robot': 'robot.onap', 'aai': 'aai.onap' } - # this is the keyword used to name vgw stack, must not be used in other stacks - self.vgw_name_keyword = 'base_vcpe_vgw' - # this is the file that will keep the index of last assigned SO name - self.vgw_vfmod_name_index_file= '__var/vgw_vfmod_name_index' - self.svc_instance_uuid_file = '__var/svc_instance_uuid' - self.preload_dict_file = '__var/preload_dict' - self.vgmux_vnf_name_file = '__var/vgmux_vnf_name' - self.product_family_id = 'f9457e8c-4afd-45da-9389-46acd9bf5116' - self.custom_product_family_id = 'a9a77d5a-123e-4ca2-9eb9-0b015d2ee0fb' - self.instance_name_prefix = { - 'service': 'svc', - 'network': 'net', - 'vnf': 'vnf', - 'vfmodule': 'vf' - } - self.aai_userpass = 'AAI', 'AAI' - self.pub_key = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKXDgoo3+WOqcUG8/5uUbk81+yczgwC4Y8ywTmuQqbNxlY1oQ0YxdMUqUnhitSXs5S/yRuAVOYHwGg2mCs20oAINrP+mxBI544AMIb9itPjCtgqtE2EWo6MmnFGbHB4Sx3XioE7F4VPsh7japsIwzOjbrQe+Mua1TGQ5d4nfEOQaaglXLLPFfuc7WbhbJbK6Q7rHqZfRcOwAMXgDoBqlyqKeiKwnumddo2RyNT8ljYmvB6buz7KnMinzo7qB0uktVT05FH9Rg0CTWH5norlG5qXgP2aukL0gk1ph8iAt7uYLf1ktp+LJI2gaF6L0/qli9EmVCSLr1uJ38Q8CBflhkh' - self.os_tenant_id = self.cloud['--os-tenant-id'] - self.os_region_name = self.cloud['--os-region-name'] - self.common_preload_config['pub_key'] = self.pub_key - self.sniro_url = 'http://' + self.hosts['robot'] + ':' + self.sniro_port + '/__admin/mappings' - self.sniro_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'} - self.homing_solution = 'sniro' # value is either 'sniro' or 'oof' -# self.homing_solution = 'oof' - self.customer_location_used_by_oof = { - "customerLatitude": "32.897480", - "customerLongitude": "-97.040443", - "customerName": "some_company" - } - - ############################################################################################# - # SDNC urls - self.sdnc_userpass = 'admin', 'Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U' - self.sdnc_db_name = 'sdnctl' - self.sdnc_db_user = 'sdnctl' - self.sdnc_db_pass = 'gamma' - self.sdnc_db_port = '32774' - self.sdnc_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'} - self.sdnc_preload_network_url = 'http://' + self.hosts['sdnc'] + \ - ':' + self.sdnc_preloading_port + '/restconf/operations/VNF-API:preload-network-topology-operation' - self.sdnc_preload_vnf_url = 'http://' + self.hosts['sdnc'] + \ - ':' + self.sdnc_preloading_port + '/restconf/operations/VNF-API:preload-vnf-topology-operation' - self.sdnc_preload_gra_url = 'http://' + self.hosts['sdnc'] + \ - ':' + self.sdnc_preloading_port + '/restconf/operations/GENERIC-RESOURCE-API:preload-vf-module-topology-operation' - self.sdnc_ar_cleanup_url = 'http://' + self.hosts['sdnc'] + ':' + self.sdnc_preloading_port + \ - '/restconf/config/GENERIC-RESOURCE-API:' - - ############################################################################################# - # SO urls, note: do NOT add a '/' at the end of the url - self.so_req_api_url = {'v4': 'http://' + self.hosts['so'] + ':' + self.so_nbi_port + '/onap/so/infra/serviceInstantiation/v7/serviceInstances', - 'v5': 'http://' + self.hosts['so'] + ':' + self.so_nbi_port + '/onap/so/infra/serviceInstantiation/v7/serviceInstances'} - self.so_check_progress_api_url = 'http://' + self.hosts['so'] + ':' + self.so_nbi_port + '/onap/so/infra/orchestrationRequests/v6' - self.so_userpass = 'InfraPortalClient', 'password1$' - self.so_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'} - self.so_db_name = 'catalogdb' - self.so_db_user = 'root' - self.so_db_pass = 'password' - self.so_db_port = '30252' if self.oom_mode else '32769' - - self.vpp_inf_url = 'http://{0}:8183/restconf/config/ietf-interfaces:interfaces' - self.vpp_api_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'} - self.vpp_api_userpass = ('admin', 'admin') - self.vpp_ves_url= 'http://{0}:8183/restconf/config/vesagent:vesagent' - - - - def find_file(self, file_name_keyword, file_ext, search_dir): - """ - :param file_name_keyword: keyword used to look for the csar file, case insensitive matching, e.g, infra - :param file_ext: e.g., csar, json - :param search_dir path to search - :return: path name of the file - """ - file_name_keyword = file_name_keyword.lower() - file_ext = file_ext.lower() - if not file_ext.startswith('.'): - file_ext = '.' + file_ext - - filenamepath = None - for file_name in os.listdir(search_dir): - file_name_lower = file_name.lower() - if file_name_keyword in file_name_lower and file_name_lower.endswith(file_ext): - if filenamepath: - self.logger.error('Multiple files found for *{0}*.{1} in ' - 'directory {2}'.format(file_name_keyword, file_ext, search_dir)) - sys.exit() - filenamepath = os.path.abspath(os.path.join(search_dir, file_name)) - - if filenamepath: - return filenamepath - else: - self.logger.error("Cannot find *{0}*{1} in directory {2}".format(file_name_keyword, file_ext, search_dir)) - sys.exit() - - @staticmethod - def network_name_to_subnet_name(network_name): - """ - :param network_name: example: vcpe_net_cpe_signal_201711281221 - :return: vcpe_net_cpe_signal_subnet_201711281221 - """ - fields = network_name.split('_') - fields.insert(-1, 'subnet') - return '_'.join(fields) - - def set_network_name(self, network_name): - param = ' '.join([k + ' ' + v for k, v in self.cloud.items()]) - openstackcmd = 'openstack ' + param - cmd = ' '.join([openstackcmd, 'network set --name', network_name, 'ONAP-NW1']) - os.popen(cmd) - - def set_subnet_name(self, network_name): - """ - Example: network_name = vcpe_net_cpe_signal_201711281221 - set subnet name to vcpe_net_cpe_signal_subnet_201711281221 - :return: - """ - param = ' '.join([k + ' ' + v for k, v in self.cloud.items()]) - openstackcmd = 'openstack ' + param - - # expected results: | subnets | subnet_id | - subnet_info = os.popen(openstackcmd + ' network show ' + network_name + ' |grep subnets').read().split('|') - if len(subnet_info) > 2 and subnet_info[1].strip() == 'subnets': - subnet_id = subnet_info[2].strip() - subnet_name = self.network_name_to_subnet_name(network_name) - cmd = ' '.join([openstackcmd, 'subnet set --name', subnet_name, subnet_id]) - os.popen(cmd) - self.logger.info("Subnet name set to: " + subnet_name) - return True - else: - self.logger.error("Can't get subnet info from network name: " + network_name) - return False - - def is_node_in_aai(self, node_type, node_uuid): - key = None - search_node_type = None - if node_type == 'service': - search_node_type = 'service-instance' - key = 'service-instance-id' - elif node_type == 'vnf': - search_node_type = 'generic-vnf' - key = 'vnf-id' - else: - logging.error('Invalid node_type: ' + node_type) - sys.exit() - - url = 'https://{0}:{1}/aai/v11/search/nodes-query?search-node-type={2}&filter={3}:EQUALS:{4}'.format( - self.hosts['aai'], self.aai_query_port, search_node_type, key, node_uuid) - - headers = {'Content-Type': 'application/json', 'Accept': 'application/json', 'X-FromAppID': 'vCPE-Robot', 'X-TransactionId': 'get_aai_subscr'} - requests.packages.urllib3.disable_warnings() - r = requests.get(url, headers=headers, auth=self.aai_userpass, verify=False) - response = r.json() - self.logger.debug('aai query: ' + url) - self.logger.debug('aai response:\n' + json.dumps(response, indent=4, sort_keys=True)) - return 'result-data' in response - - @staticmethod - def extract_ip_from_str(net_addr, net_addr_len, sz): - """ - :param net_addr: e.g. 10.5.12.0 - :param net_addr_len: e.g. 24 - :param sz: a string - :return: the first IP address matching the network, e.g. 10.5.12.3 - """ - network = ipaddress.ip_network(unicode('{0}/{1}'.format(net_addr, net_addr_len)), strict=False) - ip_list = re.findall(r'[0-9]+(?:\.[0-9]+){3}', sz) - for ip in ip_list: - this_net = ipaddress.ip_network(unicode('{0}/{1}'.format(ip, net_addr_len)), strict=False) - if this_net == network: - return str(ip) - return None - - - - @staticmethod - def save_object(obj, filepathname): - with open(filepathname, 'wb') as fout: - pickle.dump(obj, fout) - - @staticmethod - def load_object(filepathname): - with open(filepathname, 'rb') as fin: - return pickle.load(fin) - - @staticmethod - def increase_ip_address_or_vni_in_template(vnf_template_file, vnf_parameter_name_list): - with open(vnf_template_file) as json_input: - json_data = json.load(json_input) - param_list = json_data['VNF-API:input']['VNF-API:vnf-topology-information']['VNF-API:vnf-parameters'] - for param in param_list: - if param['vnf-parameter-name'] in vnf_parameter_name_list: - ipaddr_or_vni = param['vnf-parameter-value'].split('.') - number = int(ipaddr_or_vni[-1]) - if 254 == number: - number = 10 - else: - number = number + 1 - ipaddr_or_vni[-1] = str(number) - param['vnf-parameter-value'] = '.'.join(ipaddr_or_vni) - - assert json_data is not None - with open(vnf_template_file, 'w') as json_output: - json.dump(json_data, json_output, indent=4, sort_keys=True) - - def save_preload_data(self, preload_data): - self.save_object(preload_data, self.preload_dict_file) - - def load_preload_data(self): - return self.load_object(self.preload_dict_file) - - def save_vgmux_vnf_name(self, vgmux_vnf_name): - self.save_object(vgmux_vnf_name, self.vgmux_vnf_name_file) - - def load_vgmux_vnf_name(self): - return self.load_object(self.vgmux_vnf_name_file) - -- cgit 1.2.3-korg