fb3c587caedde9162e891e556f1261134214fcdf
[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 import yaml
24
25 from functest.core import singlevm
26 from functest.utils import config
27 from functest.utils import env
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(requirements=get_config(
100             "orchestrator.requirements", self.config_file))
101
102         self.created_object = []
103         self.details['orchestrator'] = dict(
104             name=get_config("orchestrator.name", self.config_file),
105             version=get_config("orchestrator.version", self.config_file),
106             status='ERROR',
107             result=''
108         )
109
110         self.vnf = dict(
111             descriptor=get_config("vnf.descriptor", self.config_file),
112             requirements=get_config("vnf.requirements", self.config_file)
113         )
114         self.details['vnf'] = dict(
115             descriptor_version=self.vnf['descriptor']['version'],
116             name=get_config("vnf.name", self.config_file),
117             version=get_config("vnf.version", self.config_file),
118         )
119         self.__logger.debug("VNF configuration: %s", self.vnf)
120
121         self.details['test_vnf'] = dict(
122             name=get_config("vnf_test_suite.name", self.config_file),
123             version=get_config("vnf_test_suite.version", self.config_file),
124             tag_name=get_config("vnf_test_suite.tag_name", self.config_file)
125         )
126
127         self.res_dir = os.path.join(
128             getattr(config.CONF, 'dir_results'), self.case_name)
129
130         try:
131             self.public_auth_url = self.get_public_auth_url(self.orig_cloud)
132             if not self.public_auth_url.endswith(('v3', 'v3/')):
133                 self.public_auth_url = six.moves.urllib.parse.urljoin(
134                     self.public_auth_url, 'v3')
135         except Exception:  # pylint: disable=broad-except
136             self.public_auth_url = None
137         self.sec = None
138         self.image_alt = None
139         self.flavor_alt = None
140
141     def check_requirements(self):
142         if env.get('NEW_USER_ROLE').lower() == "admin":
143             self.__logger.warn(
144                 "Defining NEW_USER_ROLE=admin will easily break the testcase "
145                 "because Juju doesn't manage tenancy (e.g. subnet  "
146                 "overlapping)")
147
148     def _register_cloud(self):
149         assert self.public_auth_url
150         self.__logger.info("Creating Cloud for Abot-epc .....")
151         clouds_yaml = os.path.join(self.res_dir, "clouds.yaml")
152         cloud_data = {
153             'url': self.public_auth_url,
154             'region': self.cloud.region_name if self.cloud.region_name else (
155                 'RegionOne')}
156         with open(clouds_yaml, 'w') as yfile:
157             yfile.write(CLOUD_TEMPLATE.format(**cloud_data))
158         cmd = ['juju', 'add-cloud', 'abot-epc', '-f', clouds_yaml, '--replace']
159         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
160         self.__logger.info("%s\n%s", " ".join(cmd), output)
161
162     def _register_credentials(self):
163         self.__logger.info("Creating Credentials for Abot-epc .....")
164         credentials_yaml = os.path.join(self.res_dir, "credentials.yaml")
165         creds_data = {
166             'pass': self.project.password,
167             'tenant_n': self.project.project.name,
168             'user_n': self.project.user.name,
169             'project_domain_n': self.cloud.auth.get(
170                 "project_domain_name", "Default"),
171             'user_domain_n': self.cloud.auth.get(
172                 "user_domain_name", "Default")}
173         with open(credentials_yaml, 'w') as yfile:
174             yfile.write(CREDS_TEMPLATE.format(**creds_data))
175         cmd = ['juju', 'add-credential', 'abot-epc', '-f', credentials_yaml,
176                '--replace']
177         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
178         self.__logger.info("%s\n%s", " ".join(cmd), output)
179
180     def prepare(self):
181         """Prepare testcase (Additional pre-configuration steps)."""
182         assert self.public_auth_url
183         self.__logger.info("Additional pre-configuration steps")
184         try:
185             os.makedirs(self.res_dir)
186         except OSError as ex:
187             if ex.errno != errno.EEXIST:
188                 self.__logger.exception("Cannot create %s", self.res_dir)
189                 raise Exception
190
191         self.__logger.info("ENV:\n%s", env.string())
192         self._register_cloud()
193         self._register_credentials()
194
195     def publish_image(self, name=None):
196         image = super(JujuEpc, self).publish_image(name)
197         cmd = ['juju', 'metadata', 'generate-image', '-d', '/root',
198                '-i', image.id, '-s', 'xenial',
199                '-r', self.cloud.region_name if self.cloud.region_name else (
200                    'RegionOne'),
201                '-u', self.public_auth_url]
202         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
203         self.__logger.info("%s\n%s", " ".join(cmd), output)
204         return image
205
206     def publish_image_alt(self, name=None):
207         image_alt = super(JujuEpc, self).publish_image_alt(name)
208         cmd = ['juju', 'metadata', 'generate-image', '-d', '/root',
209                '-i', image_alt.id, '-s', 'trusty',
210                '-r', self.cloud.region_name if self.cloud.region_name else (
211                    'RegionOne'),
212                '-u', self.public_auth_url]
213         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
214         self.__logger.info("%s\n%s", " ".join(cmd), output)
215         return image_alt
216
217     def deploy_orchestrator(self):  # pylint: disable=too-many-locals
218         """
219         Create network, subnet, router
220
221         Bootstrap juju
222         """
223         self.image_alt = self.publish_image_alt()
224         self.flavor_alt = self.create_flavor_alt()
225         self.__logger.info("Starting Juju Bootstrap process...")
226         try:
227             cmd = ['timeout', '-t', JujuEpc.juju_timeout,
228                    'juju', 'bootstrap',
229                    'abot-epc/{}'.format(
230                        self.cloud.region_name if self.cloud.region_name else (
231                            'RegionOne')),
232                    'abot-controller',
233                    '--agent-version', '2.2.9',
234                    '--metadata-source', '/root',
235                    '--constraints', 'mem=2G',
236                    '--bootstrap-series', 'xenial',
237                    '--config', 'network={}'.format(self.network.id),
238                    '--config', 'ssl-hostname-verification=false',
239                    '--config', 'use-floating-ip=true',
240                    '--config', 'use-default-secgroup=true',
241                    '--debug']
242             output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
243             self.__logger.info("%s\n%s", " ".join(cmd), output)
244         except subprocess.CalledProcessError as cpe:
245             self.__logger.error(
246                 "Exception with Juju Bootstrap: %s\n%s",
247                 cpe.cmd, cpe.output)
248             return False
249         except Exception:  # pylint: disable=broad-except
250             self.__logger.exception("Some issue with Juju Bootstrap ...")
251             return False
252
253         return True
254
255     def check_app(self, name='abot-epc-basic', status='active'):
256         """Check application status."""
257         cmd = ['juju', 'status', '--format', 'short', name]
258         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
259         self.__logger.info("%s\n%s", " ".join(cmd), output)
260         ret = re.search(r'(?=workload:({})\))'.format(status), output)
261         if ret:
262             self.__logger.info("%s workload is %s", name, status)
263             return True
264         self.__logger.error("%s workload differs from %s", name, status)
265         return False
266
267     def deploy_vnf(self):
268         """Deploy ABOT-OAI-EPC."""
269         self.__logger.info("Upload VNFD")
270         descriptor = self.vnf['descriptor']
271         self.__logger.info("Deploying Abot-epc bundle file ...")
272         cmd = ['juju', 'deploy', '{}'.format(descriptor.get('file_name'))]
273         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
274         self.__logger.info("%s\n%s", " ".join(cmd), output)
275         self.__logger.info("Waiting for instances .....")
276         try:
277             cmd = ['timeout', '-t', JujuEpc.juju_timeout, 'juju-wait']
278             output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
279             self.__logger.info("%s\n%s", " ".join(cmd), output)
280             self.__logger.info("Deployed Abot-epc on Openstack")
281         except subprocess.CalledProcessError as cpe:
282             self.__logger.error(
283                 "Exception with Juju VNF Deployment: %s\n%s",
284                 cpe.cmd, cpe.output)
285             return False
286         except Exception:  # pylint: disable=broad-except
287             self.__logger.exception("Some issue with the VNF Deployment ..")
288             return False
289
290         self.__logger.info("Checking status of ABot and EPC units ...")
291         cmd = ['juju', 'status']
292         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
293         self.__logger.debug("%s\n%s", " ".join(cmd), output)
294         for app in ['abot-epc-basic', 'oai-epc', 'oai-hss']:
295             if not self.check_app(app):
296                 return False
297
298         self.__logger.info("Transferring the feature files to Abot_node ...")
299         cmd = ['timeout', '-t', JujuEpc.juju_timeout,
300                'juju', 'scp', '--', '-r', '-v',
301                '{}/featureFiles'.format(self.case_dir), 'abot-epc-basic/0:~/']
302         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
303         self.__logger.info("%s\n%s", " ".join(cmd), output)
304
305         self.__logger.info("Copying the feature files within Abot_node ")
306         cmd = ['timeout', '-t', JujuEpc.juju_timeout,
307                'juju', 'ssh', 'abot-epc-basic/0',
308                'sudo', 'cp', '-vfR', '~/featureFiles/*',
309                '/etc/rebaca-test-suite/featureFiles']
310         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
311         self.__logger.info("%s\n%s", " ".join(cmd), output)
312         return True
313
314     def test_vnf(self):
315         """Run test on ABoT."""
316         start_time = time.time()
317         self.__logger.info("Running VNF Test cases....")
318         cmd = ['juju', 'run-action', 'abot-epc-basic/0', 'run',
319                'tagnames={}'.format(self.details['test_vnf']['tag_name'])]
320         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
321         self.__logger.info("%s\n%s", " ".join(cmd), output)
322
323         cmd = ['timeout', '-t', JujuEpc.juju_timeout, 'juju-wait']
324         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
325         self.__logger.info("%s\n%s", " ".join(cmd), output)
326
327         duration = time.time() - start_time
328         self.__logger.info("Getting results from Abot node....")
329         cmd = ['timeout', '-t', JujuEpc.juju_timeout,
330                'juju', 'scp', '--', '-v',
331                'abot-epc-basic/0:'
332                '/var/lib/abot-epc-basic/artifacts/TestResults.json',
333                '{}/.'.format(self.res_dir)]
334         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
335         self.__logger.info("%s\n%s", " ".join(cmd), output)
336         self.__logger.info("Parsing the Test results...")
337         res = (process_abot_test_result('{}/TestResults.json'.format(
338             self.res_dir)))
339         short_result = sig_test_format(res)
340         self.__logger.info(short_result)
341         self.details['test_vnf'].update(
342             status='PASS', result=short_result, full_result=res,
343             duration=duration)
344         self.__logger.info(
345             "Test VNF result: Passed: %d, Failed:%d, Skipped: %d",
346             short_result['passed'],
347             short_result['failures'], short_result['skipped'])
348         return True
349
350     def run(self, **kwargs):
351         self.start_time = time.time()
352         try:
353             assert super(JujuEpc, self).run(**kwargs) == self.EX_OK
354             self.prepare()
355             if (self.deploy_orchestrator() and
356                     self.deploy_vnf() and
357                     self.test_vnf()):
358                 self.stop_time = time.time()
359                 self.result = 100
360                 return self.EX_OK
361             self.result = 0
362             self.stop_time = time.time()
363             return self.EX_TESTCASE_FAILED
364         except Exception:  # pylint: disable=broad-except
365             self.stop_time = time.time()
366             self.__logger.exception("Exception on VNF testing")
367             return self.EX_TESTCASE_FAILED
368
369     def clean(self):
370         """Clean created objects/functions."""
371         try:
372             cmd = ['juju', 'debug-log', '--replay', '--no-tail']
373             output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
374             self.__logger.debug("%s\n%s", " ".join(cmd), output)
375             self.__logger.info("Destroying Orchestrator...")
376             cmd = ['timeout', '-t', JujuEpc.juju_timeout,
377                    'juju', 'destroy-controller', '-y', 'abot-controller',
378                    '--destroy-all-models']
379             output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
380             self.__logger.info("%s\n%s", " ".join(cmd), output)
381         except subprocess.CalledProcessError as cpe:
382             self.__logger.error(
383                 "Exception with Juju Cleanup: %s\n%s",
384                 cpe.cmd, cpe.output)
385         except Exception:  # pylint: disable=broad-except
386             self.__logger.exception("General issue during the undeployment ..")
387         for fip in self.cloud.list_floating_ips():
388             self.cloud.delete_floating_ip(fip.id)
389         if self.image_alt:
390             self.cloud.delete_image(self.image_alt)
391         if self.flavor_alt:
392             self.orig_cloud.delete_flavor(self.flavor_alt.id)
393         super(JujuEpc, self).clean()
394
395
396 # ----------------------------------------------------------
397 #
398 #               YAML UTILS
399 #
400 # -----------------------------------------------------------
401 def get_config(parameter, file_path):
402     """
403     Returns the value of a given parameter in file.yaml
404     parameter must be given in string format with dots
405     Example: general.openstack.image_name
406     """
407     with open(file_path) as config_file:
408         file_yaml = yaml.safe_load(config_file)
409     config_file.close()
410     value = file_yaml
411     for element in parameter.split("."):
412         value = value.get(element)
413         if value is None:
414             raise ValueError("The parameter %s is not defined in"
415                              " reporting.yaml" % parameter)
416     return value
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