Set utf-8 in decode and encode calls
[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.decode("utf-8"))
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.decode("utf-8"))
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.decode("utf-8"))
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.decode("utf-8"))
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.decode("utf-8"))
259         except subprocess.CalledProcessError as cpe:
260             self.__logger.error(
261                 "Exception with Juju Bootstrap: %s\n%s",
262                 cpe.cmd, cpe.output.decode("utf-8"))
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.decode("utf-8"))
276             ret = re.search(
277                 r'(?=workload:({})\))'.format(status), output.decode("utf-8"))
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.decode("utf-8"))
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.decode("utf-8"))
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.decode("utf-8"))
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.decode("utf-8"))
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.decode("utf-8"))
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.decode("utf-8"))
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.decode("utf-8"))
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.decode("utf-8"))
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.decode("utf-8"))
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(
397                 "%s\n%s", " ".join(cmd), output.decode("utf-8"))
398             self.__logger.info("Destroying Orchestrator...")
399             cmd = ['timeout', '-t', JujuEpc.juju_timeout,
400                    'juju', 'destroy-controller', '-y', 'abot-controller',
401                    '--destroy-all-models']
402             output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
403             self.__logger.info("%s\n%s", " ".join(cmd), output.decode("utf-8"))
404         except subprocess.CalledProcessError as cpe:
405             self.__logger.error(
406                 "Exception with Juju Cleanup: %s\n%s",
407                 cpe.cmd, cpe.output.decode("utf-8"))
408         except Exception:  # pylint: disable=broad-except
409             self.__logger.exception("General issue during the undeployment ..")
410         for fip in self.cloud.list_floating_ips():
411             self.cloud.delete_floating_ip(fip.id)
412         if self.image_alt:
413             self.cloud.delete_image(self.image_alt)
414         if self.flavor_alt:
415             self.orig_cloud.delete_flavor(self.flavor_alt.id)
416         super(JujuEpc, self).clean()
417
418
419 def sig_test_format(sig_test):
420     """
421     Process the signaling result to have a short result
422     """
423     nb_passed = 0
424     nb_failures = 0
425     nb_skipped = 0
426     for data_test in sig_test:
427         if data_test['result'] == "passed":
428             nb_passed += 1
429         elif data_test['result'] == "failed":
430             nb_failures += 1
431         elif data_test['result'] == "skipped":
432             nb_skipped += 1
433     total_sig_test_result = {}
434     total_sig_test_result['passed'] = nb_passed
435     total_sig_test_result['failures'] = nb_failures
436     total_sig_test_result['skipped'] = nb_skipped
437     return total_sig_test_result
438
439
440 def process_abot_test_result(file_path):
441     """ Process ABoT Result """
442     with open(file_path) as test_result:
443         data = json.load(test_result)
444         res = []
445         for tests in data:
446             tests = update_data(tests)
447             try:
448                 flatten_steps = tests['elements'][0].pop('flatten_steps')
449                 for steps in flatten_steps:
450                     steps['result'] = steps['step_status']
451                     res.append(steps)
452             except Exception:  # pylint: disable=broad-except
453                 logging.error("Could not post data to ElasticSearch host")
454                 raise
455         return res
456
457
458 def update_data(obj):
459     """ Update Result data"""
460     try:
461         obj['feature_file'] = os.path.splitext(os.path.basename(obj['uri']))[0]
462
463         for element in obj['elements']:
464             element['final_result'] = "passed"
465             element['flatten_steps'] = []
466
467             for step in element['steps']:
468                 temp_dict = {}
469                 step['result'][step['result']['status']] = 1
470                 if step['result']['status'].lower() in ['fail', 'failed']:
471                     element['final_result'] = "failed"
472
473                 temp_dict['feature_file'] = obj['feature_file']
474                 temp_dict['step_name'] = step['name']
475                 temp_dict['step_status'] = step['result']['status']
476                 temp_dict['step_duration'] = step['result'].get('duration', 0)
477                 temp_dict['step_' + step['result']['status']] = 1
478                 element['flatten_steps'].append(deepcopy(temp_dict))
479
480             # Need to put the tag in OBJ and not ELEMENT
481             if 'tags' in obj:
482                 element['tags'] = deepcopy(obj['tags'])
483                 for tag in obj['tags']:
484                     element[tag['name']] = 1
485             else:
486                 for tag in element['tags']:
487                     element[tag['name']] = 1
488
489     except Exception:  # pylint: disable=broad-except
490         logging.error("Error in updating data, %s", sys.exc_info()[0])
491         raise
492
493     return obj