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