3 # Copyright (c) 2016 Rebaca and others.
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."""
20 from copy import deepcopy
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
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
46 __author__ = "Amarendra Meher <amarendra@rebaca.com>"
47 __author__ = "Soumaya K Nayek <soumaya.nayek@rebaca.com>"
49 CLOUD_TEMPLATE = """clouds:
52 auth-types: [userpass]
58 CREDS_TEMPLATE2 = """credentials:
60 default-credential: abot-epc
64 project-domain-name: {project_domain_n}
65 tenant-name: {tenant_n}"""
67 CREDS_TEMPLATE3 = """credentials:
69 default-credential: abot-epc
73 project-domain-name: {project_domain_n}
74 tenant-name: {tenant_n}
75 user-domain-name: {user_domain_n}
79 class JujuEpc(vnf.VnfOnBoarding):
80 # pylint:disable=too-many-instance-attributes
81 """Abot EPC deployed with JUJU Orchestrator Case"""
83 __logger = logging.getLogger(__name__)
87 def __init__(self, **kwargs):
88 if "case_name" not in kwargs:
89 kwargs["case_name"] = "juju_epc"
90 super(JujuEpc, self).__init__(**kwargs)
92 # Retrieve the configuration
93 self.case_dir = pkg_resources.resource_filename(
94 'functest', 'opnfv_tests/vnf/epc')
96 self.config = getattr(
97 config.CONF, 'vnf_{}_config'.format(self.case_name))
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))
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),
113 descriptor=get_config("vnf.descriptor", self.config_file),
114 requirements=get_config("vnf.requirements", self.config_file)
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),
121 self.__logger.debug("VNF configuration: %s", self.vnf)
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)
128 self.public_auth_url = None
130 self.res_dir = os.path.join(
131 getattr(config.CONF, 'dir_results'), self.case_name)
133 def _bypass_juju_netdiscovery_bug(self, name):
134 user_creator = OpenStackUser(
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)
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")
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)
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")
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,
173 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
174 self.__logger.info("%s\n%s", " ".join(cmd), output)
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")
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,
193 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
194 self.__logger.info("%s\n%s", " ".join(cmd), output)
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(
207 rule_settings=sec_grp_rules))
208 security_group.create()
209 self.created_object.append(security_group)
212 """Prepare testcase (Additional pre-configuration steps)."""
213 self.__logger.info("Additional pre-configuration steps")
214 super(JujuEpc, self).prepare()
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
222 self.__logger.info("ENV:\n%s", env.string())
224 self.public_auth_url = keystone_utils.get_endpoint(
225 self.snaps_creds, 'identity')
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()
236 self._register_credentials_v2()
238 def deploy_orchestrator(self): # pylint: disable=too-many-locals
240 Create network, subnet, router
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(
249 'vnf_{}_private_subnet_name'.format(self.case_name)),
251 private_subnet_cidr = getattr(
252 config.CONF, 'vnf_{}_private_subnet_cidr'.format(self.case_name))
253 abot_router = '{}-{}'.format(
255 'vnf_{}_external_router'.format(self.case_name)),
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)
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(
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'],
283 flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
284 flavor_creator.create()
285 self.created_object.append(flavor_creator)
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)
306 self.__logger.info("Starting Juju Bootstrap process...")
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',
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:
322 "Exception with Juju Bootstrap: %s\n%s",
325 except Exception: # pylint: disable=broad-except
326 self.__logger.exception("Some issue with Juju Bootstrap ...")
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)
338 self.__logger.info("%s workload is %s", name, status)
340 self.__logger.error("%s workload differs from %s", name, status)
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'],
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 .....")
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:
368 "Exception with Juju VNF Deployment: %s\n%s",
371 except Exception: # pylint: disable=broad-except
372 self.__logger.exception("Some issue with the VNF Deployment ..")
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):
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)
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)
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)
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)
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)
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)
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',
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(
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,
446 "Test VNF result: Passed: %d, Failed:%d, Skipped: %d",
447 short_result['passed'],
448 short_result['failures'], short_result['skipped'])
452 """Clean created objects/functions."""
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:
466 "Exception with Juju Cleanup: %s\n%s",
468 except Exception: # pylint: disable=broad-except
469 self.__logger.exception("General issue during the undeployment ..")
471 if not self.orchestrator['requirements']['preserve_setup']:
472 self.__logger.info('Remove the Abot_epc OS object ..')
473 super(JujuEpc, self).clean()
478 # ----------------------------------------------------------
482 # -----------------------------------------------------------
483 def get_config(parameter, file_path):
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
489 with open(file_path) as config_file:
490 file_yaml = yaml.safe_load(config_file)
493 for element in parameter.split("."):
494 value = value.get(element)
496 raise ValueError("The parameter %s is not defined in"
497 " reporting.yaml" % parameter)
501 def sig_test_format(sig_test):
503 Process the signaling result to have a short result
508 for data_test in sig_test:
509 if data_test['result'] == "passed":
511 elif data_test['result'] == "failed":
513 elif data_test['result'] == "skipped":
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
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)
528 tests = update_data(tests)
530 flatten_steps = tests['elements'][0].pop('flatten_steps')
531 for steps in flatten_steps:
532 steps['result'] = steps['step_status']
535 logging.error("Could not post data to ElasticSearch host")
540 def update_data(obj):
541 """ Update Result data"""
543 obj['feature_file'] = os.path.splitext(os.path.basename(obj['uri']))[0]
545 for element in obj['elements']:
546 element['final_result'] = "passed"
547 element['flatten_steps'] = []
549 for step in element['steps']:
551 step['result'][step['result']['status']] = 1
552 if step['result']['status'].lower() in ['fail', 'failed']:
553 element['final_result'] = "failed"
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))
562 # Need to put the tag in OBJ and not ELEMENT
564 element['tags'] = deepcopy(obj['tags'])
565 for tag in obj['tags']:
566 element[tag['name']] = 1
568 for tag in element['tags']:
569 element[tag['name']] = 1
571 except Exception: # pylint: disable=broad-except
572 logging.error("Error in updating data, %s", sys.exc_info()[0])
578 def get_instances(nova_client):
579 """ To get all vm info of a project """
581 instances = nova_client.servers.list()
583 except Exception as exc: # pylint: disable=broad-except
584 logging.error("Error [get_instances(nova_client)]: %s", exc)
588 def get_instance_metadata(nova_client, instance):
589 """ Get instance Metadata - Instance ID """
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)