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