Remove duplicated code in vnfs
[functest.git] / functest / opnfv_tests / vnf / epc / juju_epc.py
1 #!/usr/bin/env python
2
3 # Copyright (c) 2016 Rebaca and others.
4 #
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Apache License, Version 2.0
7 # which accompanies this distribution, and is available at
8 # http://www.apache.org/licenses/LICENSE-2.0
9 """Juju testcase implementation."""
10
11 import errno
12 import logging
13 import os
14 import time
15 import json
16 import re
17 import subprocess
18 import sys
19
20 from copy import deepcopy
21 import pkg_resources
22 import six
23
24 from functest.core import singlevm
25 from functest.utils import config
26 from functest.utils import env
27 from functest.utils import functest_utils
28
29 __author__ = "Amarendra Meher <amarendra@rebaca.com>"
30 __author__ = "Soumaya K Nayek <soumaya.nayek@rebaca.com>"
31
32 CLOUD_TEMPLATE = """clouds:
33   abot-epc:
34     type: openstack
35     auth-types: [userpass]
36     endpoint: {url}
37     regions:
38       {region}:
39         endpoint: {url}"""
40
41 CREDS_TEMPLATE2 = """credentials:
42   abot-epc:
43     default-credential: abot-epc
44     abot-epc:
45       auth-type: userpass
46       password: {pass}
47       project-domain-name: {project_domain_n}
48       tenant-name: {tenant_n}"""
49
50 CREDS_TEMPLATE = """credentials:
51   abot-epc:
52     default-credential: abot-epc
53     abot-epc:
54       auth-type: userpass
55       password: {pass}
56       project-domain-name: {project_domain_n}
57       tenant-name: {tenant_n}
58       user-domain-name: {user_domain_n}
59       username: {user_n}"""
60
61
62 class JujuEpc(singlevm.VmReady2):
63     # pylint:disable=too-many-instance-attributes
64     """Abot EPC deployed with JUJU Orchestrator Case"""
65
66     __logger = logging.getLogger(__name__)
67
68     cidr = '192.168.121.0/24'
69
70     filename = ('/home/opnfv/functest/images/'
71                 'ubuntu-16.04-server-cloudimg-amd64-disk1.img')
72     filename_alt = ('/home/opnfv/functest/images/'
73                     'ubuntu-14.04-server-cloudimg-amd64-disk1.img')
74
75     flavor_ram = 2048
76     flavor_vcpus = 1
77     flavor_disk = 10
78
79     flavor_alt_ram = 4096
80     flavor_alt_vcpus = 1
81     flavor_alt_disk = 10
82
83     juju_timeout = '3600'
84
85     def __init__(self, **kwargs):
86         if "case_name" not in kwargs:
87             kwargs["case_name"] = "juju_epc"
88         super(JujuEpc, self).__init__(**kwargs)
89
90         # Retrieve the configuration
91         self.case_dir = pkg_resources.resource_filename(
92             'functest', 'opnfv_tests/vnf/epc')
93         try:
94             self.config = getattr(
95                 config.CONF, 'vnf_{}_config'.format(self.case_name))
96         except Exception:
97             raise Exception("VNF config file not found")
98         self.config_file = os.path.join(self.case_dir, self.config)
99         self.orchestrator = dict(
100             requirements=functest_utils.get_parameter_from_yaml(
101                 "orchestrator.requirements", self.config_file))
102
103         self.created_object = []
104         self.details['orchestrator'] = dict(
105             name=functest_utils.get_parameter_from_yaml(
106                 "orchestrator.name", self.config_file),
107             version=functest_utils.get_parameter_from_yaml(
108                 "orchestrator.version", self.config_file),
109             status='ERROR',
110             result=''
111         )
112
113         self.vnf = dict(
114             descriptor=functest_utils.get_parameter_from_yaml(
115                 "vnf.descriptor", self.config_file),
116             requirements=functest_utils.get_parameter_from_yaml(
117                 "vnf.requirements", self.config_file)
118         )
119         self.details['vnf'] = dict(
120             descriptor_version=self.vnf['descriptor']['version'],
121             name=functest_utils.get_parameter_from_yaml(
122                 "vnf.name", self.config_file),
123             version=functest_utils.get_parameter_from_yaml(
124                 "vnf.version", self.config_file),
125         )
126         self.__logger.debug("VNF configuration: %s", self.vnf)
127
128         self.details['test_vnf'] = dict(
129             name=functest_utils.get_parameter_from_yaml(
130                 "vnf_test_suite.name", self.config_file),
131             version=functest_utils.get_parameter_from_yaml(
132                 "vnf_test_suite.version", self.config_file),
133             tag_name=functest_utils.get_parameter_from_yaml(
134                 "vnf_test_suite.tag_name", self.config_file)
135         )
136
137         self.res_dir = os.path.join(
138             getattr(config.CONF, 'dir_results'), self.case_name)
139
140         try:
141             self.public_auth_url = self.get_public_auth_url(self.orig_cloud)
142             if not self.public_auth_url.endswith(('v3', 'v3/')):
143                 self.public_auth_url = six.moves.urllib.parse.urljoin(
144                     self.public_auth_url, 'v3')
145         except Exception:  # pylint: disable=broad-except
146             self.public_auth_url = None
147         self.sec = None
148         self.image_alt = None
149         self.flavor_alt = None
150
151     def check_requirements(self):
152         if env.get('NEW_USER_ROLE').lower() == "admin":
153             self.__logger.warn(
154                 "Defining NEW_USER_ROLE=admin will easily break the testcase "
155                 "because Juju doesn't manage tenancy (e.g. subnet  "
156                 "overlapping)")
157
158     def _register_cloud(self):
159         assert self.public_auth_url
160         self.__logger.info("Creating Cloud for Abot-epc .....")
161         clouds_yaml = os.path.join(self.res_dir, "clouds.yaml")
162         cloud_data = {
163             'url': self.public_auth_url,
164             'region': self.cloud.region_name if self.cloud.region_name else (
165                 'RegionOne')}
166         with open(clouds_yaml, 'w') as yfile:
167             yfile.write(CLOUD_TEMPLATE.format(**cloud_data))
168         cmd = ['juju', 'add-cloud', 'abot-epc', '-f', clouds_yaml, '--replace']
169         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
170         self.__logger.info("%s\n%s", " ".join(cmd), output)
171
172     def _register_credentials(self):
173         self.__logger.info("Creating Credentials for Abot-epc .....")
174         credentials_yaml = os.path.join(self.res_dir, "credentials.yaml")
175         creds_data = {
176             'pass': self.project.password,
177             'tenant_n': self.project.project.name,
178             'user_n': self.project.user.name,
179             'project_domain_n': self.cloud.auth.get(
180                 "project_domain_name", "Default"),
181             'user_domain_n': self.cloud.auth.get(
182                 "user_domain_name", "Default")}
183         with open(credentials_yaml, 'w') as yfile:
184             yfile.write(CREDS_TEMPLATE.format(**creds_data))
185         cmd = ['juju', 'add-credential', 'abot-epc', '-f', credentials_yaml,
186                '--replace']
187         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
188         self.__logger.info("%s\n%s", " ".join(cmd), output)
189
190     def prepare(self):
191         """Prepare testcase (Additional pre-configuration steps)."""
192         assert self.public_auth_url
193         self.__logger.info("Additional pre-configuration steps")
194         try:
195             os.makedirs(self.res_dir)
196         except OSError as ex:
197             if ex.errno != errno.EEXIST:
198                 self.__logger.exception("Cannot create %s", self.res_dir)
199                 raise Exception
200
201         self.__logger.info("ENV:\n%s", env.string())
202         self._register_cloud()
203         self._register_credentials()
204
205     def publish_image(self, name=None):
206         image = super(JujuEpc, self).publish_image(name)
207         cmd = ['juju', 'metadata', 'generate-image', '-d', '/root',
208                '-i', image.id, '-s', 'xenial',
209                '-r', self.cloud.region_name if self.cloud.region_name else (
210                    'RegionOne'),
211                '-u', self.public_auth_url]
212         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
213         self.__logger.info("%s\n%s", " ".join(cmd), output)
214         return image
215
216     def publish_image_alt(self, name=None):
217         image_alt = super(JujuEpc, self).publish_image_alt(name)
218         cmd = ['juju', 'metadata', 'generate-image', '-d', '/root',
219                '-i', image_alt.id, '-s', 'trusty',
220                '-r', self.cloud.region_name if self.cloud.region_name else (
221                    'RegionOne'),
222                '-u', self.public_auth_url]
223         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
224         self.__logger.info("%s\n%s", " ".join(cmd), output)
225         return image_alt
226
227     def deploy_orchestrator(self):  # pylint: disable=too-many-locals
228         """
229         Create network, subnet, router
230
231         Bootstrap juju
232         """
233         self.image_alt = self.publish_image_alt()
234         self.flavor_alt = self.create_flavor_alt()
235         self.__logger.info("Starting Juju Bootstrap process...")
236         try:
237             cmd = ['timeout', '-t', JujuEpc.juju_timeout,
238                    'juju', 'bootstrap',
239                    'abot-epc/{}'.format(
240                        self.cloud.region_name if self.cloud.region_name else (
241                            'RegionOne')),
242                    'abot-controller',
243                    '--agent-version', '2.3.9',
244                    '--metadata-source', '/root',
245                    '--constraints', 'mem=2G',
246                    '--bootstrap-series', 'xenial',
247                    '--config', 'network={}'.format(self.network.id),
248                    '--config', 'ssl-hostname-verification=false',
249                    '--config', 'use-floating-ip=true',
250                    '--config', 'use-default-secgroup=true',
251                    '--debug']
252             output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
253             self.__logger.info("%s\n%s", " ".join(cmd), output)
254         except subprocess.CalledProcessError as cpe:
255             self.__logger.error(
256                 "Exception with Juju Bootstrap: %s\n%s",
257                 cpe.cmd, cpe.output)
258             return False
259         except Exception:  # pylint: disable=broad-except
260             self.__logger.exception("Some issue with Juju Bootstrap ...")
261             return False
262
263         return True
264
265     def check_app(self, name='abot-epc-basic', status='active'):
266         """Check application status."""
267         cmd = ['juju', 'status', '--format', 'short', name]
268         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
269         self.__logger.info("%s\n%s", " ".join(cmd), output)
270         ret = re.search(r'(?=workload:({})\))'.format(status), output)
271         if ret:
272             self.__logger.info("%s workload is %s", name, status)
273             return True
274         self.__logger.error("%s workload differs from %s", name, status)
275         return False
276
277     def deploy_vnf(self):
278         """Deploy ABOT-OAI-EPC."""
279         self.__logger.info("Upload VNFD")
280         descriptor = self.vnf['descriptor']
281         self.__logger.info("Deploying Abot-epc bundle file ...")
282         cmd = ['juju', 'deploy', '{}'.format(descriptor.get('file_name'))]
283         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
284         self.__logger.info("%s\n%s", " ".join(cmd), output)
285         self.__logger.info("Waiting for instances .....")
286         try:
287             cmd = ['timeout', '-t', JujuEpc.juju_timeout, 'juju-wait']
288             output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
289             self.__logger.info("%s\n%s", " ".join(cmd), output)
290             self.__logger.info("Deployed Abot-epc on Openstack")
291         except subprocess.CalledProcessError as cpe:
292             self.__logger.error(
293                 "Exception with Juju VNF Deployment: %s\n%s",
294                 cpe.cmd, cpe.output)
295             return False
296         except Exception:  # pylint: disable=broad-except
297             self.__logger.exception("Some issue with the VNF Deployment ..")
298             return False
299
300         self.__logger.info("Checking status of ABot and EPC units ...")
301         cmd = ['juju', 'status']
302         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
303         self.__logger.debug("%s\n%s", " ".join(cmd), output)
304         for app in ['abot-epc-basic', 'oai-epc', 'oai-hss']:
305             if not self.check_app(app):
306                 return False
307
308         self.__logger.info("Transferring the feature files to Abot_node ...")
309         cmd = ['timeout', '-t', JujuEpc.juju_timeout,
310                'juju', 'scp', '--', '-r', '-v',
311                '{}/featureFiles'.format(self.case_dir), 'abot-epc-basic/0:~/']
312         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
313         self.__logger.info("%s\n%s", " ".join(cmd), output)
314
315         self.__logger.info("Copying the feature files within Abot_node ")
316         cmd = ['timeout', '-t', JujuEpc.juju_timeout,
317                'juju', 'ssh', 'abot-epc-basic/0',
318                'sudo', 'cp', '-vfR', '~/featureFiles/*',
319                '/etc/rebaca-test-suite/featureFiles']
320         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
321         self.__logger.info("%s\n%s", " ".join(cmd), output)
322         return True
323
324     def test_vnf(self):
325         """Run test on ABoT."""
326         start_time = time.time()
327         self.__logger.info("Running VNF Test cases....")
328         cmd = ['juju', 'run-action', 'abot-epc-basic/0', 'run',
329                'tagnames={}'.format(self.details['test_vnf']['tag_name'])]
330         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
331         self.__logger.info("%s\n%s", " ".join(cmd), output)
332
333         cmd = ['timeout', '-t', JujuEpc.juju_timeout, 'juju-wait']
334         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
335         self.__logger.info("%s\n%s", " ".join(cmd), output)
336
337         duration = time.time() - start_time
338         self.__logger.info("Getting results from Abot node....")
339         cmd = ['timeout', '-t', JujuEpc.juju_timeout,
340                'juju', 'scp', '--', '-v',
341                'abot-epc-basic/0:'
342                '/var/lib/abot-epc-basic/artifacts/TestResults.json',
343                '{}/.'.format(self.res_dir)]
344         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
345         self.__logger.info("%s\n%s", " ".join(cmd), output)
346         self.__logger.info("Parsing the Test results...")
347         res = (process_abot_test_result('{}/TestResults.json'.format(
348             self.res_dir)))
349         short_result = sig_test_format(res)
350         self.__logger.info(short_result)
351         self.details['test_vnf'].update(
352             status='PASS', result=short_result, full_result=res,
353             duration=duration)
354         self.__logger.info(
355             "Test VNF result: Passed: %d, Failed:%d, Skipped: %d",
356             short_result['passed'],
357             short_result['failures'], short_result['skipped'])
358         return True
359
360     def run(self, **kwargs):
361         self.start_time = time.time()
362         try:
363             assert super(JujuEpc, self).run(**kwargs) == self.EX_OK
364             self.prepare()
365             if (self.deploy_orchestrator() and
366                     self.deploy_vnf() and
367                     self.test_vnf()):
368                 self.stop_time = time.time()
369                 self.result = 100
370                 return self.EX_OK
371             self.result = 0
372             self.stop_time = time.time()
373             return self.EX_TESTCASE_FAILED
374         except Exception:  # pylint: disable=broad-except
375             self.stop_time = time.time()
376             self.__logger.exception("Exception on VNF testing")
377             return self.EX_TESTCASE_FAILED
378
379     def clean(self):
380         """Clean created objects/functions."""
381         try:
382             cmd = ['juju', 'debug-log', '--replay', '--no-tail']
383             output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
384             self.__logger.debug("%s\n%s", " ".join(cmd), output)
385             self.__logger.info("Destroying Orchestrator...")
386             cmd = ['timeout', '-t', JujuEpc.juju_timeout,
387                    'juju', 'destroy-controller', '-y', 'abot-controller',
388                    '--destroy-all-models']
389             output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
390             self.__logger.info("%s\n%s", " ".join(cmd), output)
391         except subprocess.CalledProcessError as cpe:
392             self.__logger.error(
393                 "Exception with Juju Cleanup: %s\n%s",
394                 cpe.cmd, cpe.output)
395         except Exception:  # pylint: disable=broad-except
396             self.__logger.exception("General issue during the undeployment ..")
397         for fip in self.cloud.list_floating_ips():
398             self.cloud.delete_floating_ip(fip.id)
399         if self.image_alt:
400             self.cloud.delete_image(self.image_alt)
401         if self.flavor_alt:
402             self.orig_cloud.delete_flavor(self.flavor_alt.id)
403         super(JujuEpc, self).clean()
404
405
406 def sig_test_format(sig_test):
407     """
408     Process the signaling result to have a short result
409     """
410     nb_passed = 0
411     nb_failures = 0
412     nb_skipped = 0
413     for data_test in sig_test:
414         if data_test['result'] == "passed":
415             nb_passed += 1
416         elif data_test['result'] == "failed":
417             nb_failures += 1
418         elif data_test['result'] == "skipped":
419             nb_skipped += 1
420     total_sig_test_result = {}
421     total_sig_test_result['passed'] = nb_passed
422     total_sig_test_result['failures'] = nb_failures
423     total_sig_test_result['skipped'] = nb_skipped
424     return total_sig_test_result
425
426
427 def process_abot_test_result(file_path):
428     """ Process ABoT Result """
429     with open(file_path) as test_result:
430         data = json.load(test_result)
431         res = []
432         for tests in data:
433             tests = update_data(tests)
434             try:
435                 flatten_steps = tests['elements'][0].pop('flatten_steps')
436                 for steps in flatten_steps:
437                     steps['result'] = steps['step_status']
438                     res.append(steps)
439             except Exception:  # pylint: disable=broad-except
440                 logging.error("Could not post data to ElasticSearch host")
441                 raise
442         return res
443
444
445 def update_data(obj):
446     """ Update Result data"""
447     try:
448         obj['feature_file'] = os.path.splitext(os.path.basename(obj['uri']))[0]
449
450         for element in obj['elements']:
451             element['final_result'] = "passed"
452             element['flatten_steps'] = []
453
454             for step in element['steps']:
455                 temp_dict = {}
456                 step['result'][step['result']['status']] = 1
457                 if step['result']['status'].lower() in ['fail', 'failed']:
458                     element['final_result'] = "failed"
459
460                 temp_dict['feature_file'] = obj['feature_file']
461                 temp_dict['step_name'] = step['name']
462                 temp_dict['step_status'] = step['result']['status']
463                 temp_dict['step_duration'] = step['result'].get('duration', 0)
464                 temp_dict['step_' + step['result']['status']] = 1
465                 element['flatten_steps'].append(deepcopy(temp_dict))
466
467             # Need to put the tag in OBJ and not ELEMENT
468             if 'tags' in obj:
469                 element['tags'] = deepcopy(obj['tags'])
470                 for tag in obj['tags']:
471                     element[tag['name']] = 1
472             else:
473                 for tag in element['tags']:
474                     element[tag['name']] = 1
475
476     except Exception:  # pylint: disable=broad-except
477         logging.error("Error in updating data, %s", sys.exc_info()[0])
478         raise
479
480     return obj