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