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