12c6d2398800743bf68eff1d851c03b7c3bee577
[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 import uuid
20 from copy import deepcopy
21 import pkg_resources
22 import yaml
23
24 import six
25 from snaps.config.flavor import FlavorConfig
26 from snaps.config.image import ImageConfig
27 from snaps.config.network import NetworkConfig, SubnetConfig
28 from snaps.config.router import RouterConfig
29 from snaps.config.security_group import (
30     Direction, Protocol, SecurityGroupConfig, SecurityGroupRuleConfig)
31 from snaps.config.user import UserConfig
32 from snaps.openstack.create_flavor import OpenStackFlavor
33 from snaps.openstack.create_image import OpenStackImage
34 from snaps.openstack.create_network import OpenStackNetwork
35 from snaps.openstack.create_router import OpenStackRouter
36 from snaps.openstack.create_security_group import OpenStackSecurityGroup
37 from snaps.openstack.create_user import OpenStackUser
38 from snaps.openstack.utils import keystone_utils
39 from snaps.openstack.utils import nova_utils
40 from snaps.openstack.utils import neutron_utils
41
42 from functest.core import vnf
43 from functest.opnfv_tests.openstack.snaps import snaps_utils
44 from functest.utils import config
45 from functest.utils import env
46
47 __author__ = "Amarendra Meher <amarendra@rebaca.com>"
48 __author__ = "Soumaya K Nayek <soumaya.nayek@rebaca.com>"
49
50 CLOUD_TEMPLATE = """clouds:
51   abot-epc:
52     type: openstack
53     auth-types: [userpass]
54     endpoint: {url}
55     regions:
56       {region}:
57         endpoint: {url}"""
58
59 CREDS_TEMPLATE2 = """credentials:
60   abot-epc:
61     default-credential: abot-epc
62     abot-epc:
63       auth-type: userpass
64       password: {pass}
65       project-domain-name: {project_domain_n}
66       tenant-name: {tenant_n}"""
67
68 CREDS_TEMPLATE3 = """credentials:
69   abot-epc:
70     default-credential: abot-epc
71     abot-epc:
72       auth-type: userpass
73       password: {pass}
74       project-domain-name: {project_domain_n}
75       tenant-name: {tenant_n}
76       user-domain-name: {user_domain_n}
77       username: {user_n}"""
78
79
80 class JujuEpc(vnf.VnfOnBoarding):
81     # pylint:disable=too-many-instance-attributes
82     """Abot EPC deployed with JUJU Orchestrator Case"""
83
84     __logger = logging.getLogger(__name__)
85
86     juju_timeout = '3600'
87
88     def __init__(self, **kwargs):
89         if "case_name" not in kwargs:
90             kwargs["case_name"] = "juju_epc"
91         super(JujuEpc, self).__init__(**kwargs)
92
93         # Retrieve the configuration
94         self.case_dir = pkg_resources.resource_filename(
95             'functest', 'opnfv_tests/vnf/epc')
96         try:
97             self.config = getattr(
98                 config.CONF, 'vnf_{}_config'.format(self.case_name))
99         except Exception:
100             raise Exception("VNF config file not found")
101         self.config_file = os.path.join(self.case_dir, self.config)
102         self.orchestrator = dict(requirements=get_config(
103             "orchestrator.requirements", self.config_file))
104
105         self.created_object = []
106         self.details['orchestrator'] = dict(
107             name=get_config("orchestrator.name", self.config_file),
108             version=get_config("orchestrator.version", self.config_file),
109             status='ERROR',
110             result=''
111         )
112
113         self.vnf = dict(
114             descriptor=get_config("vnf.descriptor", self.config_file),
115             requirements=get_config("vnf.requirements", self.config_file)
116         )
117         self.details['vnf'] = dict(
118             descriptor_version=self.vnf['descriptor']['version'],
119             name=get_config("vnf.name", self.config_file),
120             version=get_config("vnf.version", self.config_file),
121         )
122         self.__logger.debug("VNF configuration: %s", self.vnf)
123
124         self.details['test_vnf'] = dict(
125             name=get_config("vnf_test_suite.name", self.config_file),
126             version=get_config("vnf_test_suite.version", self.config_file),
127             tag_name=get_config("vnf_test_suite.tag_name", self.config_file)
128         )
129         self.public_auth_url = None
130
131         self.res_dir = os.path.join(
132             getattr(config.CONF, 'dir_results'), self.case_name)
133
134     def _bypass_juju_netdiscovery_bug(self, name):
135         user_creator = OpenStackUser(
136             self.snaps_creds,
137             UserConfig(
138                 name=name,
139                 password=str(uuid.uuid4()),
140                 project_name=self.tenant_name,
141                 domain_name=self.snaps_creds.user_domain_name,
142                 roles={'_member_': self.tenant_name}))
143         user_creator.create()
144         self.created_object.append(user_creator)
145         return user_creator
146
147     def _register_cloud(self):
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.snaps_creds.region_name}
153         with open(clouds_yaml, 'w') as yfile:
154             yfile.write(CLOUD_TEMPLATE.format(**cloud_data))
155         cmd = ['juju', 'add-cloud', 'abot-epc', '-f', clouds_yaml, '--replace']
156         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
157         self.__logger.info("%s\n%s", " ".join(cmd), output)
158
159     def _register_credentials_v2(self):
160         self.__logger.info("Creating Credentials for Abot-epc .....")
161         user_creator = self._bypass_juju_netdiscovery_bug(
162             'juju_network_discovery_bug')
163         snaps_creds = user_creator.get_os_creds(self.snaps_creds.project_name)
164         self.__logger.debug("snaps creds: %s", snaps_creds)
165         credentials_yaml = os.path.join(self.res_dir, "credentials.yaml")
166         creds_data = {
167             'pass': snaps_creds.password,
168             'tenant_n': snaps_creds.project_name,
169             'user_n': snaps_creds.username}
170         with open(credentials_yaml, 'w') as yfile:
171             yfile.write(CREDS_TEMPLATE2.format(**creds_data))
172         cmd = ['juju', 'add-credential', 'abot-epc', '-f', credentials_yaml,
173                '--replace']
174         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
175         self.__logger.info("%s\n%s", " ".join(cmd), output)
176
177     def _register_credentials_v3(self):
178         self.__logger.info("Creating Credentials for Abot-epc .....")
179         user_creator = self._bypass_juju_netdiscovery_bug(
180             'juju_network_discovery_bug')
181         snaps_creds = user_creator.get_os_creds(self.snaps_creds.project_name)
182         self.__logger.debug("snaps creds: %s", snaps_creds)
183         credentials_yaml = os.path.join(self.res_dir, "credentials.yaml")
184         creds_data = {
185             'pass': snaps_creds.password,
186             'tenant_n': snaps_creds.project_name,
187             'user_n': snaps_creds.username,
188             'project_domain_n': snaps_creds.project_domain_name,
189             'user_domain_n': snaps_creds.user_domain_name}
190         with open(credentials_yaml, 'w') as yfile:
191             yfile.write(CREDS_TEMPLATE3.format(**creds_data))
192         cmd = ['juju', 'add-credential', 'abot-epc', '-f', credentials_yaml,
193                '--replace']
194         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
195         self.__logger.info("%s\n%s", " ".join(cmd), output)
196
197     def _add_custom_rule(self, sec_grp_name):
198         """ To add custom rule for SCTP Traffic """
199
200         security_group = OpenStackSecurityGroup(
201             self.snaps_creds,
202             SecurityGroupConfig(
203                 name=sec_grp_name))
204
205         security_group.create()
206
207         # Add custom security rule to the obtained Security Group
208         self.__logger.info("Adding SCTP ingress rule to SG:%s",
209                            security_group.sec_grp_settings.name)
210
211         try:
212             security_group.add_rule(SecurityGroupRuleConfig(
213                 sec_grp_name=sec_grp_name, direction=Direction.ingress,
214                 protocol=Protocol.sctp))
215         except Exception:  # pylint: disable=broad-except
216             self.__logger.exception(
217                 "Some issue encountered with adding SCTP security rule ...")
218
219     def prepare(self):
220         """Prepare testcase (Additional pre-configuration steps)."""
221         self.__logger.info("Additional pre-configuration steps")
222         super(JujuEpc, self).prepare()
223         try:
224             os.makedirs(self.res_dir)
225         except OSError as ex:
226             if ex.errno != errno.EEXIST:
227                 self.__logger.exception("Cannot create %s", self.res_dir)
228                 raise vnf.VnfPreparationException
229
230         self.__logger.info("ENV:\n%s", env.string())
231
232         self.public_auth_url = keystone_utils.get_endpoint(
233             self.snaps_creds, 'identity')
234
235         # it enforces a versioned public identity endpoint as juju simply
236         # adds /auth/tokens wich fails vs an unversioned endpoint.
237         if not self.public_auth_url.endswith(('v3', 'v3/', 'v2.0', 'v2.0/')):
238             self.public_auth_url = six.moves.urllib.parse.urljoin(
239                 self.public_auth_url, 'v3')
240         self._register_cloud()
241         if self.snaps_creds.identity_api_version == 3:
242             self._register_credentials_v3()
243         else:
244             self._register_credentials_v2()
245
246     def deploy_orchestrator(self):  # pylint: disable=too-many-locals
247         """
248         Create network, subnet, router
249
250         Bootstrap juju
251         """
252         self.__logger.info("Deploying Juju Orchestrator")
253         private_net_name = getattr(
254             config.CONF, 'vnf_{}_private_net_name'.format(self.case_name))
255         private_subnet_name = '{}-{}'.format(
256             getattr(config.CONF,
257                     'vnf_{}_private_subnet_name'.format(self.case_name)),
258             self.uuid)
259         private_subnet_cidr = getattr(
260             config.CONF, 'vnf_{}_private_subnet_cidr'.format(self.case_name))
261         abot_router = '{}-{}'.format(
262             getattr(config.CONF,
263                     'vnf_{}_external_router'.format(self.case_name)),
264             self.uuid)
265         self.__logger.info("Creating full network with nameserver: %s",
266                            env.get('NAMESERVER'))
267         subnet_settings = SubnetConfig(
268             name=private_subnet_name,
269             cidr=private_subnet_cidr,
270             dns_nameservers=[env.get('NAMESERVER')])
271         network_settings = NetworkConfig(
272             name=private_net_name, subnet_settings=[subnet_settings])
273         network_creator = OpenStackNetwork(self.snaps_creds, network_settings)
274         net_id = network_creator.create().id
275         self.created_object.append(network_creator)
276
277         ext_net_name = snaps_utils.get_ext_net_name(self.snaps_creds)
278         self.__logger.info("Creating network Router ....")
279         router_creator = OpenStackRouter(
280             self.snaps_creds, RouterConfig(
281                 name=abot_router,
282                 external_gateway=ext_net_name,
283                 internal_subnets=[subnet_settings.name]))
284         router_creator.create()
285         self.created_object.append(router_creator)
286         self.__logger.info("Creating Flavor ....")
287         flavor_settings = FlavorConfig(
288             name=self.orchestrator['requirements']['flavor']['name'],
289             ram=self.orchestrator['requirements']['flavor']['ram_min'],
290             disk=10, vcpus=1)
291         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
292         flavor_creator.create()
293         self.created_object.append(flavor_creator)
294
295         self.__logger.info("Upload some OS images if it doesn't exist")
296         images = get_config("tenant_images", self.config_file)
297         self.__logger.info("Images needed for vEPC: %s", images)
298         for image_name, image_file in six.iteritems(images):
299             self.__logger.info("image: %s, file: %s", image_name, image_file)
300             if image_file and image_name:
301                 image_creator = OpenStackImage(self.snaps_creds, ImageConfig(
302                     name=image_name, image_user='cloud', img_format='qcow2',
303                     image_file=image_file))
304                 image_id = image_creator.create().id
305                 cmd = ['juju', 'metadata', 'generate-image', '-d', '/root',
306                        '-i', image_id, '-s', image_name,
307                        '-r', self.snaps_creds.region_name,
308                        '-u', self.public_auth_url]
309                 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
310                 self.__logger.info("%s\n%s", " ".join(cmd), output)
311                 self.created_object.append(image_creator)
312         self.__logger.info("Network ID  : %s", net_id)
313
314         self.__logger.info("Starting Juju Bootstrap process...")
315         try:
316             cmd = ['timeout', '-t', JujuEpc.juju_timeout,
317                    'juju', 'bootstrap', 'abot-epc', 'abot-controller',
318                    '--metadata-source', '/root',
319                    '--constraints', 'mem=2G',
320                    '--bootstrap-series', 'xenial',
321                    '--config', 'network={}'.format(net_id),
322                    '--config', 'ssl-hostname-verification=false',
323                    '--config', 'use-floating-ip=true',
324                    '--config', 'use-default-secgroup=true',
325                    '--debug']
326             output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
327             self.__logger.info("%s\n%s", " ".join(cmd), output)
328         except subprocess.CalledProcessError as cpe:
329             self.__logger.error(
330                 "Exception with Juju Bootstrap: %s\n%s",
331                 cpe.cmd, cpe.output)
332             return False
333         except Exception:  # pylint: disable=broad-except
334             self.__logger.exception("Some issue with Juju Bootstrap ...")
335             return False
336
337         return True
338
339     def check_app(self, name='abot-epc-basic', status='active'):
340         """Check application status."""
341         cmd = ['juju', 'status', '--format', 'short', name]
342         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
343         self.__logger.info("%s\n%s", " ".join(cmd), output)
344         ret = re.search(r'(?=workload:({})\))'.format(status), output)
345         if ret:
346             self.__logger.info("%s workload is %s", name, status)
347             return True
348         self.__logger.error("%s workload differs from %s", name, status)
349         return False
350
351     def deploy_vnf(self):
352         """Deploy ABOT-OAI-EPC."""
353         self.__logger.info("Upload VNFD")
354         descriptor = self.vnf['descriptor']
355         self.__logger.info("Get or create flavor for all Abot-EPC")
356         flavor_settings = FlavorConfig(
357             name=self.vnf['requirements']['flavor']['name'],
358             ram=self.vnf['requirements']['flavor']['ram_min'],
359             disk=10,
360             vcpus=1)
361         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
362         flavor_creator.create()
363         self.created_object.append(flavor_creator)
364
365         self.__logger.info("Deploying Abot-epc bundle file ...")
366         cmd = ['juju', 'deploy', '{}'.format(descriptor.get('file_name'))]
367         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
368         self.__logger.info("%s\n%s", " ".join(cmd), output)
369         self.__logger.info("Waiting for instances .....")
370         try:
371             cmd = ['timeout', '-t', JujuEpc.juju_timeout, 'juju-wait']
372             output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
373             self.__logger.info("%s\n%s", " ".join(cmd), output)
374             self.__logger.info("Deployed Abot-epc on Openstack")
375         except subprocess.CalledProcessError as cpe:
376             self.__logger.error(
377                 "Exception with Juju VNF Deployment: %s\n%s",
378                 cpe.cmd, cpe.output)
379             return False
380         except Exception:  # pylint: disable=broad-except
381             self.__logger.exception("Some issue with the VNF Deployment ..")
382             return False
383
384         self.__logger.info("Checking status of ABot and EPC units ...")
385         cmd = ['juju', 'status']
386         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
387         self.__logger.debug("%s\n%s", " ".join(cmd), output)
388         for app in ['abot-epc-basic', 'oai-epc', 'oai-hss']:
389             if not self.check_app(app):
390                 return False
391
392         nova_client = nova_utils.nova_client(self.snaps_creds)
393         instances = get_instances(nova_client)
394         self.__logger.info("List of Instance: %s", instances)
395         for items in instances:
396             metadata = get_instance_metadata(nova_client, items)
397             if 'juju-units-deployed' in metadata:
398                 sec_group = 'juju-{}-{}'.format(
399                     metadata['juju-controller-uuid'],
400                     metadata['juju-model-uuid'])
401                 self.__logger.info("Instance: %s", sec_group)
402                 break
403         self.__logger.info("Adding Security group rule....")
404         # This will add sctp rule to a common Security Group Created
405         # by juju and shared to all deployed units.
406         self._add_custom_rule(sec_group)
407
408         self.__logger.info("Transferring the feature files to Abot_node ...")
409         cmd = ['timeout', '-t', JujuEpc.juju_timeout,
410                'juju', 'scp', '--', '-r', '-v',
411                '{}/featureFiles'.format(self.case_dir), 'abot-epc-basic/0:~/']
412         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
413         self.__logger.info("%s\n%s", " ".join(cmd), output)
414
415         self.__logger.info("Copying the feature files within Abot_node ")
416         cmd = ['timeout', '-t', JujuEpc.juju_timeout,
417                'juju', 'ssh', 'abot-epc-basic/0',
418                'sudo', 'cp', '-vfR', '~/featureFiles/*',
419                '/etc/rebaca-test-suite/featureFiles']
420         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
421         self.__logger.info("%s\n%s", " ".join(cmd), output)
422         return True
423
424     def test_vnf(self):
425         """Run test on ABoT."""
426         start_time = time.time()
427         self.__logger.info("Running VNF Test cases....")
428         cmd = ['juju', 'run-action', 'abot-epc-basic/0', 'run',
429                'tagnames={}'.format(self.details['test_vnf']['tag_name'])]
430         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
431         self.__logger.info("%s\n%s", " ".join(cmd), output)
432
433         cmd = ['timeout', '-t', JujuEpc.juju_timeout, 'juju-wait']
434         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
435         self.__logger.info("%s\n%s", " ".join(cmd), output)
436
437         duration = time.time() - start_time
438         self.__logger.info("Getting results from Abot node....")
439         cmd = ['timeout', '-t', JujuEpc.juju_timeout,
440                'juju', 'scp', '--', '-v',
441                'abot-epc-basic/0:'
442                '/var/lib/abot-epc-basic/artifacts/TestResults.json',
443                '{}/.'.format(self.res_dir)]
444         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
445         self.__logger.info("%s\n%s", " ".join(cmd), output)
446         self.__logger.info("Parsing the Test results...")
447         res = (process_abot_test_result('{}/TestResults.json'.format(
448             self.res_dir)))
449         short_result = sig_test_format(res)
450         self.__logger.info(short_result)
451         self.details['test_vnf'].update(
452             status='PASS', result=short_result, full_result=res,
453             duration=duration)
454         self.__logger.info(
455             "Test VNF result: Passed: %d, Failed:%d, Skipped: %d",
456             short_result['passed'],
457             short_result['failures'], short_result['skipped'])
458         return True
459
460     def _get_floating_ips(self):
461         """Get the list of floating IPs associated with the current project"""
462
463         project_id = self.os_project.get_project().id
464
465         neutron_client = neutron_utils.neutron_client(self.snaps_creds)
466         floating_ips = neutron_utils.get_floating_ips(neutron_client)
467
468         project_floating_ip_list = list()
469         for floating_ip in floating_ips:
470             if project_id and project_id == floating_ip.project_id:
471                 project_floating_ip_list.append(floating_ip)
472
473         return project_floating_ip_list
474
475     def _release_floating_ips(self, fip_list):
476         """
477         Responsible for deleting a list of floating IPs
478         :param fip_list: A list of SNAPS FloatingIp objects
479         :return:
480         """
481         if not fip_list:
482             return
483
484         neutron_client = neutron_utils.neutron_client(self.snaps_creds)
485
486         for floating_ip in fip_list:
487             neutron_utils.delete_floating_ip(neutron_client, floating_ip)
488
489     def clean(self):
490         """Clean created objects/functions."""
491
492         # Store Floating IPs of instances created by Juju
493         fip_list = self._get_floating_ips()
494         self.__logger.info("Floating IPs assigned to project:%s",
495                            self.os_project.get_project().name)
496         for floating_ip in fip_list:
497             self.__logger.debug("%s:%s", floating_ip.ip,
498                                 floating_ip.description)
499
500         try:
501             cmd = ['juju', 'debug-log', '--replay', '--no-tail']
502             output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
503             self.__logger.debug("%s\n%s", " ".join(cmd), output)
504             if not self.orchestrator['requirements']['preserve_setup']:
505                 self.__logger.info("Destroying Orchestrator...")
506                 cmd = ['timeout', '-t', JujuEpc.juju_timeout,
507                        'juju', 'destroy-controller', '-y', 'abot-controller',
508                        '--destroy-all-models']
509                 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
510                 self.__logger.info("%s\n%s", " ".join(cmd), output)
511         except subprocess.CalledProcessError as cpe:
512             self.__logger.error(
513                 "Exception with Juju Cleanup: %s\n%s",
514                 cpe.cmd, cpe.output)
515         except Exception:  # pylint: disable=broad-except
516             self.__logger.exception("General issue during the undeployment ..")
517
518         if not self.orchestrator['requirements']['preserve_setup']:
519             try:
520                 self.__logger.info('Release floating IPs assigned by Juju...')
521                 self._release_floating_ips(fip_list)
522             except Exception:  # pylint: disable=broad-except
523                 self.__logger.exception(
524                     "Exception while releasing floating IPs ...")
525
526             self.__logger.info('Remove the Abot_epc OS objects ..')
527             super(JujuEpc, self).clean()
528
529         return True
530
531
532 # ----------------------------------------------------------
533 #
534 #               YAML UTILS
535 #
536 # -----------------------------------------------------------
537 def get_config(parameter, file_path):
538     """
539     Returns the value of a given parameter in file.yaml
540     parameter must be given in string format with dots
541     Example: general.openstack.image_name
542     """
543     with open(file_path) as config_file:
544         file_yaml = yaml.safe_load(config_file)
545     config_file.close()
546     value = file_yaml
547     for element in parameter.split("."):
548         value = value.get(element)
549         if value is None:
550             raise ValueError("The parameter %s is not defined in"
551                              " reporting.yaml" % parameter)
552     return value
553
554
555 def sig_test_format(sig_test):
556     """
557     Process the signaling result to have a short result
558     """
559     nb_passed = 0
560     nb_failures = 0
561     nb_skipped = 0
562     for data_test in sig_test:
563         if data_test['result'] == "passed":
564             nb_passed += 1
565         elif data_test['result'] == "failed":
566             nb_failures += 1
567         elif data_test['result'] == "skipped":
568             nb_skipped += 1
569     total_sig_test_result = {}
570     total_sig_test_result['passed'] = nb_passed
571     total_sig_test_result['failures'] = nb_failures
572     total_sig_test_result['skipped'] = nb_skipped
573     return total_sig_test_result
574
575
576 def process_abot_test_result(file_path):
577     """ Process ABoT Result """
578     with open(file_path) as test_result:
579         data = json.load(test_result)
580         res = []
581         for tests in data:
582             tests = update_data(tests)
583             try:
584                 flatten_steps = tests['elements'][0].pop('flatten_steps')
585                 for steps in flatten_steps:
586                     steps['result'] = steps['step_status']
587                     res.append(steps)
588             except Exception:  # pylint: disable=broad-except
589                 logging.error("Could not post data to ElasticSearch host")
590                 raise
591         return res
592
593
594 def update_data(obj):
595     """ Update Result data"""
596     try:
597         obj['feature_file'] = os.path.splitext(os.path.basename(obj['uri']))[0]
598
599         for element in obj['elements']:
600             element['final_result'] = "passed"
601             element['flatten_steps'] = []
602
603             for step in element['steps']:
604                 temp_dict = {}
605                 step['result'][step['result']['status']] = 1
606                 if step['result']['status'].lower() in ['fail', 'failed']:
607                     element['final_result'] = "failed"
608
609                 temp_dict['feature_file'] = obj['feature_file']
610                 temp_dict['step_name'] = step['name']
611                 temp_dict['step_status'] = step['result']['status']
612                 temp_dict['step_duration'] = step['result'].get('duration', 0)
613                 temp_dict['step_' + step['result']['status']] = 1
614                 element['flatten_steps'].append(deepcopy(temp_dict))
615
616             # Need to put the tag in OBJ and not ELEMENT
617             if 'tags' in obj:
618                 element['tags'] = deepcopy(obj['tags'])
619                 for tag in obj['tags']:
620                     element[tag['name']] = 1
621             else:
622                 for tag in element['tags']:
623                     element[tag['name']] = 1
624
625     except Exception:  # pylint: disable=broad-except
626         logging.error("Error in updating data, %s", sys.exc_info()[0])
627         raise
628
629     return obj
630
631
632 def get_instances(nova_client):
633     """ To get all vm info of a project """
634     try:
635         instances = nova_client.servers.list()
636         return instances
637     except Exception as exc:  # pylint: disable=broad-except
638         logging.error("Error [get_instances(nova_client)]: %s", exc)
639         return None
640
641
642 def get_instance_metadata(nova_client, instance):
643     """ Get instance Metadata - Instance ID """
644     try:
645         instance = nova_client.servers.get(instance.id)
646         return instance.metadata
647     except Exception as exc:  # pylint: disable=broad-except
648         logging.error("Error [get_instance_status(nova_client)]: %s", exc)
649         return None