Update Xtesting to 0.98
[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().__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, f'vnf_{self.case_name}_config')
94         except Exception as exc:
95             raise Exception("VNF config file not found") from exc
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 = f"{self.public_auth_url}/v3"
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', encoding='utf-8') 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', encoding='utf-8') 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             f'-i {self.image.id} -s xenial -r {region_name} '
209             f'-u {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().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             f'-i {image_alt.id} -s trusty -r {region_name} '
221             f'-u {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             f'timeout {JujuEpc.juju_timeout} '
240             f'/snap/bin/juju bootstrap abot-epc/{region_name} abot-controller '
241             '--agent-version 2.3.9 --metadata-source /home/ubuntu '
242             '--constraints mem=2G --bootstrap-series xenial '
243             f'--config network={self.network.id} '
244             '--config ssl-hostname-verification=false '
245             f'--config external-network={self.ext_net.id} '
246             '--config use-floating-ip=true '
247             '--config use-default-secgroup=true '
248             '--debug')
249         self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
250         self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
251         return not stdout.channel.recv_exit_status()
252
253     def check_app(self, name='abot-epc-basic', status='active'):
254         """Check application status."""
255         for i in range(10):
256             (_, stdout, stderr) = self.ssh.exec_command(
257                 f'/snap/bin/juju status --format short {name}')
258             output = stdout.read().decode("utf-8")
259             self.__logger.debug("stdout:\n%s", output)
260             self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
261             if stdout.channel.recv_exit_status():
262                 continue
263             ret = re.search(
264                 rf'(?=workload:({status})\))', output)
265             if ret:
266                 self.__logger.info("%s workload is %s", name, status)
267                 break
268             self.__logger.info(
269                 "loop %d: %s workload differs from %s", i + 1, name, status)
270             time.sleep(60)
271         else:
272             self.__logger.error("%s workload differs from %s", name, status)
273             return False
274         return True
275
276     def deploy_vnf(self):
277         """Deploy ABOT-OAI-EPC."""
278         self.__logger.info("Upload VNFD")
279         scpc = scp.SCPClient(self.ssh.get_transport())
280         scpc.put(
281             '/src/epc-requirements/abot_charm', remote_path='~/',
282             recursive=True)
283         self.__logger.info("Deploying Abot-epc bundle file ...")
284         (_, stdout, stderr) = self.ssh.exec_command(
285             'sudo mkdir -p /src/epc-requirements && '
286             'sudo mv abot_charm /src/epc-requirements/abot_charm && '
287             '/snap/bin/juju deploy '
288             '/src/epc-requirements/abot_charm/functest-abot-epc-bundle/'
289             'bundle.yaml')
290         self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
291         self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
292         if stdout.channel.recv_exit_status():
293             return not stdout.channel.recv_exit_status()
294         (_, stdout, stderr) = self.ssh.exec_command(
295             'PATH=/snap/bin/:$PATH '
296             f'timeout {JujuEpc.juju_timeout} juju-wait')
297         self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
298         self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
299         if stdout.channel.recv_exit_status():
300             return not stdout.channel.recv_exit_status()
301         self.__logger.info("Checking status of ABot and EPC units ...")
302         (_, stdout, stderr) = self.ssh.exec_command('/snap/bin/juju status')
303         output = stdout.read().decode("utf-8")
304         self.__logger.debug("stdout:\n%s", output)
305         self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
306         if stdout.channel.recv_exit_status():
307             return not stdout.channel.recv_exit_status()
308         for app in ['abot-epc-basic', 'oai-epc', 'oai-hss']:
309             if not self.check_app(app):
310                 return False
311         scpc = scp.SCPClient(self.ssh.get_transport())
312         scpc.put(
313             f'{self.case_dir}/featureFiles', remote_path='~/',
314             recursive=True)
315         (_, stdout, stderr) = self.ssh.exec_command(
316             f'timeout {JujuEpc.juju_timeout} /snap/bin/juju scp -- -r -v '
317             '~/featureFiles abot-epc-basic/0:/etc/rebaca-test-suite/')
318         output = stdout.read().decode("utf-8")
319         self.__logger.debug("stdout:\n%s", output)
320         self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
321         return not stdout.channel.recv_exit_status()
322
323     def test_vnf(self):
324         """Run test on ABoT."""
325         start_time = time.time()
326         (_, stdout, stderr) = self.ssh.exec_command(
327             "/snap/bin/juju run-action abot-epc-basic/0 "
328             f"run tagnames={self.details['test_vnf']['tag_name']}")
329         self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
330         self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
331         if stdout.channel.recv_exit_status():
332             return not stdout.channel.recv_exit_status()
333         (_, stdout, stderr) = self.ssh.exec_command(
334             'PATH=/snap/bin/:$PATH '
335             f'timeout {JujuEpc.juju_timeout} juju-wait')
336         self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
337         self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
338         if stdout.channel.recv_exit_status():
339             return not stdout.channel.recv_exit_status()
340         duration = time.time() - start_time
341         self.__logger.info("Getting results from Abot node....")
342         (_, stdout, stderr) = self.ssh.exec_command(
343             f'timeout {JujuEpc.juju_timeout} /snap/bin/juju scp '
344             '-- -v abot-epc-basic/0:'
345             '/var/lib/abot-epc-basic/artifacts/TestResults.json .')
346         self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
347         self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
348         if stdout.channel.recv_exit_status():
349             return not stdout.channel.recv_exit_status()
350         scpc = scp.SCPClient(self.ssh.get_transport())
351         scpc.get('TestResults.json', self.res_dir)
352         self.__logger.info("Parsing the Test results...")
353         res = process_abot_test_result(f'{self.res_dir}/TestResults.json')
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 execute(self):
366         """Prepare testcase (Additional pre-configuration steps)."""
367         assert self.public_auth_url
368         self.__logger.info("Additional pre-configuration steps")
369         try:
370             os.makedirs(self.res_dir)
371         except OSError as ex:
372             if ex.errno != errno.EEXIST:
373                 self.__logger.exception("Cannot create %s", self.res_dir)
374                 raise Exception from ex
375         self.__logger.info("ENV:\n%s", env.string())
376         try:
377             assert self._install_juju()
378             assert self._install_juju_wait()
379             assert self._register_cloud()
380             assert self._register_credentials()
381             assert self.deploy_orchestrator()
382             assert self.deploy_vnf()
383             assert self.test_vnf()
384         except Exception:  # pylint: disable=broad-except
385             self.__logger.exception("juju_epc failed")
386             return 1
387         return 0
388
389     def clean(self):
390         """Clean created objects/functions."""
391         (_, stdout, stderr) = self.ssh.exec_command(
392             '/snap/bin/juju debug-log --replay --no-tail')
393         self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
394         self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
395         (_, stdout, stderr) = self.ssh.exec_command(
396             '/snap/bin/juju destroy-controller -y abot-controller '
397             '--destroy-all-models')
398         self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
399         self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
400         for fip in self.cloud.list_floating_ips():
401             self.cloud.delete_floating_ip(fip.id)
402         if self.image_alt:
403             self.cloud.delete_image(self.image_alt)
404         if self.flavor_alt:
405             self.orig_cloud.delete_flavor(self.flavor_alt.id)
406         super().clean()
407
408
409 def sig_test_format(sig_test):
410     """
411     Process the signaling result to have a short result
412     """
413     nb_passed = 0
414     nb_failures = 0
415     nb_skipped = 0
416     for data_test in sig_test:
417         if data_test['result'] == "passed":
418             nb_passed += 1
419         elif data_test['result'] == "failed":
420             nb_failures += 1
421         elif data_test['result'] == "skipped":
422             nb_skipped += 1
423     total_sig_test_result = {}
424     total_sig_test_result['passed'] = nb_passed
425     total_sig_test_result['failures'] = nb_failures
426     total_sig_test_result['skipped'] = nb_skipped
427     return total_sig_test_result
428
429
430 def process_abot_test_result(file_path):
431     """ Process ABoT Result """
432     with open(file_path, encoding='utf-8') as test_result:
433         data = json.load(test_result)
434         res = []
435         for tests in data:
436             tests = update_data(tests)
437             try:
438                 flatten_steps = tests['elements'][0].pop('flatten_steps')
439                 for steps in flatten_steps:
440                     steps['result'] = steps['step_status']
441                     res.append(steps)
442             except Exception:  # pylint: disable=broad-except
443                 logging.error("Could not post data to ElasticSearch host")
444                 raise
445         return res
446
447
448 def update_data(obj):
449     """ Update Result data"""
450     try:
451         obj['feature_file'] = os.path.splitext(os.path.basename(obj['uri']))[0]
452
453         for element in obj['elements']:
454             element['final_result'] = "passed"
455             element['flatten_steps'] = []
456
457             for step in element['steps']:
458                 temp_dict = {}
459                 step['result'][step['result']['status']] = 1
460                 if step['result']['status'].lower() in ['fail', 'failed']:
461                     element['final_result'] = "failed"
462
463                 temp_dict['feature_file'] = obj['feature_file']
464                 temp_dict['step_name'] = step['name']
465                 temp_dict['step_status'] = step['result']['status']
466                 temp_dict['step_duration'] = step['result'].get('duration', 0)
467                 temp_dict['step_' + step['result']['status']] = 1
468                 element['flatten_steps'].append(deepcopy(temp_dict))
469
470             # Need to put the tag in OBJ and not ELEMENT
471             if 'tags' in obj:
472                 element['tags'] = deepcopy(obj['tags'])
473                 for tag in obj['tags']:
474                     element[tag['name']] = 1
475             else:
476                 for tag in element['tags']:
477                     element[tag['name']] = 1
478
479     except Exception:  # pylint: disable=broad-except
480         logging.error("Error in updating data, %s", sys.exc_info()[0])
481         raise
482
483     return obj