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