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