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