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
21 from urlparse import urljoin
25 from functest.core import vnf
26 from functest.opnfv_tests.openstack.snaps import snaps_utils
27 from functest.utils import config
28 from functest.utils import env
30 from snaps.config.flavor import FlavorConfig
31 from snaps.config.image import ImageConfig
32 from snaps.config.network import NetworkConfig, SubnetConfig
33 from snaps.config.router import RouterConfig
34 from snaps.config.security_group import (
35 Direction, Protocol, SecurityGroupConfig, SecurityGroupRuleConfig)
36 from snaps.config.user import UserConfig
37 from snaps.openstack.create_flavor import OpenStackFlavor
38 from snaps.openstack.create_image import OpenStackImage
39 from snaps.openstack.create_network import OpenStackNetwork
40 from snaps.openstack.create_router import OpenStackRouter
41 from snaps.openstack.create_security_group import OpenStackSecurityGroup
42 from snaps.openstack.create_user import OpenStackUser
43 from snaps.openstack.utils import keystone_utils
44 from snaps.openstack.utils import nova_utils
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_network_discovery_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_network_discovery_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_network_discovery_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 = urljoin(self.public_auth_url, 'v3')
231 self._register_cloud()
232 if self.snaps_creds.identity_api_version == 3:
233 self._register_credentials_v3()
235 self._register_credentials_v2()
237 def deploy_orchestrator(self): # pylint: disable=too-many-locals
239 Create network, subnet, router
243 self.__logger.info("Deploying Juju Orchestrator")
244 private_net_name = getattr(
245 config.CONF, 'vnf_{}_private_net_name'.format(self.case_name))
246 private_subnet_name = '{}-{}'.format(
248 'vnf_{}_private_subnet_name'.format(self.case_name)),
250 private_subnet_cidr = getattr(
251 config.CONF, 'vnf_{}_private_subnet_cidr'.format(self.case_name))
252 abot_router = '{}-{}'.format(
254 'vnf_{}_external_router'.format(self.case_name)),
256 self.__logger.info("Creating full network with nameserver: %s",
257 env.get('NAMESERVER'))
258 subnet_settings = SubnetConfig(
259 name=private_subnet_name,
260 cidr=private_subnet_cidr,
261 dns_nameservers=[env.get('NAMESERVER')])
262 network_settings = NetworkConfig(
263 name=private_net_name, subnet_settings=[subnet_settings])
264 network_creator = OpenStackNetwork(self.snaps_creds, network_settings)
265 net_id = network_creator.create().id
266 self.created_object.append(network_creator)
268 ext_net_name = snaps_utils.get_ext_net_name(self.snaps_creds)
269 self.__logger.info("Creating network Router ....")
270 router_creator = OpenStackRouter(
271 self.snaps_creds, RouterConfig(
273 external_gateway=ext_net_name,
274 internal_subnets=[subnet_settings.name]))
275 router_creator.create()
276 self.created_object.append(router_creator)
277 self.__logger.info("Creating Flavor ....")
278 flavor_settings = FlavorConfig(
279 name=self.orchestrator['requirements']['flavor']['name'],
280 ram=self.orchestrator['requirements']['flavor']['ram_min'],
282 flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
283 flavor_creator.create()
284 self.created_object.append(flavor_creator)
286 self.__logger.info("Upload some OS images if it doesn't exist")
287 images = get_config("tenant_images", self.config_file)
288 self.__logger.info("Images needed for vEPC: %s", images)
289 for image_name, image_file in images.iteritems():
290 self.__logger.info("image: %s, file: %s", image_name, image_file)
291 if image_file and image_name:
292 image_creator = OpenStackImage(self.snaps_creds, ImageConfig(
293 name=image_name, image_user='cloud', img_format='qcow2',
294 image_file=image_file))
295 image_id = image_creator.create().id
296 cmd = ['juju', 'metadata', 'generate-image', '-d', '/root',
297 '-i', image_id, '-s', image_name,
298 '-r', self.snaps_creds.region_name,
299 '-u', self.public_auth_url]
300 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
301 self.__logger.info("%s\n%s", " ".join(cmd), output)
302 self.created_object.append(image_creator)
303 self.__logger.info("Network ID : %s", net_id)
305 self.__logger.info("Starting Juju Bootstrap process...")
307 cmd = ['timeout', '-t', JujuEpc.juju_timeout,
308 'juju', 'bootstrap', 'abot-epc', 'abot-controller',
309 '--metadata-source', '/root',
310 '--constraints', 'mem=2G',
311 '--bootstrap-series', 'xenial',
312 '--config', 'network={}'.format(net_id),
313 '--config', 'ssl-hostname-verification=false',
314 '--config', 'use-floating-ip=true',
315 '--config', 'use-default-secgroup=true',
317 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
318 self.__logger.info("%s\n%s", " ".join(cmd), output)
319 except subprocess.CalledProcessError as cpe:
321 "Exception with Juju Bootstrap: %s\n%s",
324 except Exception: # pylint: disable=broad-except
325 self.__logger.exception("Some issue with Juju Bootstrap ...")
330 def check_app(self, name='abot-epc-basic', status='active'):
331 """Check application status."""
332 cmd = ['juju', 'status', '--format', 'short', name]
333 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
334 self.__logger.info("%s\n%s", " ".join(cmd), output)
335 ret = re.search(r'(?=workload:({})\))'.format(status), output)
337 self.__logger.info("%s workload is %s", name, status)
339 self.__logger.error("%s workload differs from %s", name, status)
342 def deploy_vnf(self):
343 """Deploy ABOT-OAI-EPC."""
344 self.__logger.info("Upload VNFD")
345 descriptor = self.vnf['descriptor']
346 self.__logger.info("Get or create flavor for all Abot-EPC")
347 flavor_settings = FlavorConfig(
348 name=self.vnf['requirements']['flavor']['name'],
349 ram=self.vnf['requirements']['flavor']['ram_min'],
352 flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
353 flavor_creator.create()
354 self.created_object.append(flavor_creator)
355 self.__logger.info("Deploying Abot-epc bundle file ...")
356 cmd = ['juju', 'deploy', '{}'.format(descriptor.get('file_name'))]
357 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
358 self.__logger.info("%s\n%s", " ".join(cmd), output)
359 self.__logger.info("Waiting for instances .....")
361 cmd = ['timeout', '-t', JujuEpc.juju_timeout, 'juju-wait']
362 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
363 self.__logger.info("%s\n%s", " ".join(cmd), output)
364 self.__logger.info("Deployed Abot-epc on Openstack")
365 except subprocess.CalledProcessError as cpe:
367 "Exception with Juju VNF Deployment: %s\n%s",
370 except Exception: # pylint: disable=broad-except
371 self.__logger.exception("Some issue with the VNF Deployment ..")
374 self.__logger.info("Checking status of ABot and EPC units ...")
375 cmd = ['juju', 'status']
376 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
377 self.__logger.debug("%s\n%s", " ".join(cmd), output)
378 for app in ['abot-epc-basic', 'oai-epc', 'oai-hss']:
379 if not self.check_app(app):
382 nova_client = nova_utils.nova_client(self.snaps_creds)
383 instances = get_instances(nova_client)
384 self.__logger.info("List of Instance: %s", instances)
385 for items in instances:
386 metadata = get_instance_metadata(nova_client, items)
387 if 'juju-units-deployed' in metadata:
388 sec_group = 'juju-{}-{}'.format(
389 metadata['juju-controller-uuid'],
390 metadata['juju-model-uuid'])
391 self.__logger.info("Instance: %s", sec_group)
393 self.__logger.info("Adding Security group rule....")
394 # This will add sctp rule to a common Security Group Created
395 # by juju and shared to all deployed units.
396 self._add_custom_rule(sec_group)
398 self.__logger.info("Transferring the feature files to Abot_node ...")
399 cmd = ['timeout', '-t', JujuEpc.juju_timeout,
400 'juju', 'scp', '--', '-r', '-v',
401 '{}/featureFiles'.format(self.case_dir), 'abot-epc-basic/0:~/']
402 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
403 self.__logger.info("%s\n%s", " ".join(cmd), output)
405 self.__logger.info("Copying the feature files within Abot_node ")
406 cmd = ['timeout', '-t', JujuEpc.juju_timeout,
407 'juju', 'ssh', 'abot-epc-basic/0',
408 'sudo', 'cp', '-vfR', '~/featureFiles/*',
409 '/etc/rebaca-test-suite/featureFiles']
410 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
411 self.__logger.info("%s\n%s", " ".join(cmd), output)
415 """Run test on ABoT."""
416 start_time = time.time()
417 self.__logger.info("Running VNF Test cases....")
418 cmd = ['juju', 'run-action', 'abot-epc-basic/0', 'run',
419 'tagnames={}'.format(self.details['test_vnf']['tag_name'])]
420 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
421 self.__logger.info("%s\n%s", " ".join(cmd), output)
423 cmd = ['timeout', '-t', JujuEpc.juju_timeout, 'juju-wait']
424 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
425 self.__logger.info("%s\n%s", " ".join(cmd), output)
427 duration = time.time() - start_time
428 self.__logger.info("Getting results from Abot node....")
429 cmd = ['timeout', '-t', JujuEpc.juju_timeout,
430 'juju', 'scp', '--', '-v',
432 '/var/lib/abot-epc-basic/artifacts/TestResults.json',
433 '{}/.'.format(self.res_dir)]
434 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
435 self.__logger.info("%s\n%s", " ".join(cmd), output)
436 self.__logger.info("Parsing the Test results...")
437 res = (process_abot_test_result('{}/TestResults.json'.format(
439 short_result = sig_test_format(res)
440 self.__logger.info(short_result)
441 self.details['test_vnf'].update(
442 status='PASS', result=short_result, full_result=res,
445 "Test VNF result: Passed: %d, Failed:%d, Skipped: %d",
446 short_result['passed'],
447 short_result['failures'], short_result['skipped'])
451 """Clean created objects/functions."""
453 cmd = ['juju', 'debug-log', '--replay', '--no-tail']
454 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
455 self.__logger.debug("%s\n%s", " ".join(cmd), output)
456 if not self.orchestrator['requirements']['preserve_setup']:
457 self.__logger.info("Destroying Orchestrator...")
458 cmd = ['timeout', '-t', JujuEpc.juju_timeout,
459 'juju', 'destroy-controller', '-y', 'abot-controller',
460 '--destroy-all-models']
461 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
462 self.__logger.info("%s\n%s", " ".join(cmd), output)
463 except subprocess.CalledProcessError as cpe:
465 "Exception with Juju Cleanup: %s\n%s",
467 except Exception: # pylint: disable=broad-except
468 self.__logger.exception("General issue during the undeployment ..")
470 if not self.orchestrator['requirements']['preserve_setup']:
471 self.__logger.info('Remove the Abot_epc OS object ..')
472 super(JujuEpc, self).clean()
477 # ----------------------------------------------------------
481 # -----------------------------------------------------------
482 def get_config(parameter, file_path):
484 Returns the value of a given parameter in file.yaml
485 parameter must be given in string format with dots
486 Example: general.openstack.image_name
488 with open(file_path) as config_file:
489 file_yaml = yaml.safe_load(config_file)
492 for element in parameter.split("."):
493 value = value.get(element)
495 raise ValueError("The parameter %s is not defined in"
496 " reporting.yaml" % parameter)
500 def sig_test_format(sig_test):
502 Process the signaling result to have a short result
507 for data_test in sig_test:
508 if data_test['result'] == "passed":
510 elif data_test['result'] == "failed":
512 elif data_test['result'] == "skipped":
514 total_sig_test_result = {}
515 total_sig_test_result['passed'] = nb_passed
516 total_sig_test_result['failures'] = nb_failures
517 total_sig_test_result['skipped'] = nb_skipped
518 return total_sig_test_result
521 def process_abot_test_result(file_path):
522 """ Process ABoT Result """
523 with open(file_path) as test_result:
524 data = json.load(test_result)
527 tests = update_data(tests)
529 flatten_steps = tests['elements'][0].pop('flatten_steps')
530 for steps in flatten_steps:
531 steps['result'] = steps['step_status']
534 logging.error("Could not post data to ElasticSearch host")
539 def update_data(obj):
540 """ Update Result data"""
542 obj['feature_file'] = os.path.splitext(os.path.basename(obj['uri']))[0]
544 for element in obj['elements']:
545 element['final_result'] = "passed"
546 element['flatten_steps'] = []
548 for step in element['steps']:
550 step['result'][step['result']['status']] = 1
551 if step['result']['status'].lower() in ['fail', 'failed']:
552 element['final_result'] = "failed"
554 temp_dict['feature_file'] = obj['feature_file']
555 temp_dict['step_name'] = step['name']
556 temp_dict['step_status'] = step['result']['status']
557 temp_dict['step_duration'] = step['result'].get('duration', 0)
558 temp_dict['step_' + step['result']['status']] = 1
559 element['flatten_steps'].append(deepcopy(temp_dict))
561 # Need to put the tag in OBJ and not ELEMENT
563 element['tags'] = deepcopy(obj['tags'])
564 for tag in obj['tags']:
565 element[tag['name']] = 1
567 for tag in element['tags']:
568 element[tag['name']] = 1
570 except Exception: # pylint: disable=broad-except
571 logging.error("Error in updating data, %s", sys.exc_info()[0])
577 def get_instances(nova_client):
578 """ To get all vm info of a project """
580 instances = nova_client.servers.list()
582 except Exception as exc: # pylint: disable=broad-except
583 logging.error("Error [get_instances(nova_client)]: %s", exc)
587 def get_instance_metadata(nova_client, instance):
588 """ Get instance Metadata - Instance ID """
590 instance = nova_client.servers.get(instance.id)
591 return instance.metadata
592 except Exception as exc: # pylint: disable=broad-except
593 logging.error("Error [get_instance_status(nova_client)]: %s", exc)