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=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
221 self.public_auth_url = keystone_utils.get_endpoint(
222 self.snaps_creds, 'identity')
223 # it enforces a versioned public identity endpoint as juju simply
224 # adds /auth/tokens wich fails vs an unversioned endpoint.
225 if not self.public_auth_url.endswith(('v3', 'v3/', 'v2.0', 'v2.0/')):
226 self.public_auth_url = urljoin(self.public_auth_url, 'v3')
227 self._register_cloud()
228 if self.snaps_creds.identity_api_version == 3:
229 self._register_credentials_v3()
231 self._register_credentials_v2()
233 def deploy_orchestrator(self): # pylint: disable=too-many-locals
235 Create network, subnet, router
239 self.__logger.info("Deployed Orchestrator")
240 private_net_name = getattr(
241 config.CONF, 'vnf_{}_private_net_name'.format(self.case_name))
242 private_subnet_name = '{}-{}'.format(
244 'vnf_{}_private_subnet_name'.format(self.case_name)),
246 private_subnet_cidr = getattr(
247 config.CONF, 'vnf_{}_private_subnet_cidr'.format(self.case_name))
248 abot_router = '{}-{}'.format(
250 'vnf_{}_external_router'.format(self.case_name)),
252 self.__logger.info("Creating full network ...")
253 subnet_settings = SubnetConfig(
254 name=private_subnet_name,
255 cidr=private_subnet_cidr,
256 dns_nameservers=[env.get('NAMESERVER')])
257 network_settings = NetworkConfig(
258 name=private_net_name, subnet_settings=[subnet_settings])
259 network_creator = OpenStackNetwork(self.snaps_creds, network_settings)
260 net_id = network_creator.create().id
261 self.created_object.append(network_creator)
263 ext_net_name = snaps_utils.get_ext_net_name(self.snaps_creds)
264 self.__logger.info("Creating network Router ....")
265 router_creator = OpenStackRouter(
266 self.snaps_creds, RouterConfig(
268 external_gateway=ext_net_name,
269 internal_subnets=[subnet_settings.name]))
270 router_creator.create()
271 self.created_object.append(router_creator)
272 self.__logger.info("Creating Flavor ....")
273 flavor_settings = FlavorConfig(
274 name=self.orchestrator['requirements']['flavor']['name'],
275 ram=self.orchestrator['requirements']['flavor']['ram_min'],
277 flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
278 flavor_creator.create()
279 self.created_object.append(flavor_creator)
280 self.__logger.info("Upload some OS images if it doesn't exist")
281 images = get_config("tenant_images", self.config_file)
282 self.__logger.info("Images needed for vEPC: %s", images)
283 for image_name, image_file in images.iteritems():
284 self.__logger.info("image: %s, file: %s", image_name, image_file)
285 if image_file and image_name:
286 image_creator = OpenStackImage(self.snaps_creds, ImageConfig(
287 name=image_name, image_user='cloud', img_format='qcow2',
288 image_file=image_file))
289 image_id = image_creator.create().id
290 cmd = ['timeout', '-t', JujuEpc.juju_timeout,
291 'juju', 'metadata', 'generate-image', '-d', '/root',
292 '-i', image_id, '-s', image_name,
293 '-r', self.snaps_creds.region_name,
294 '-u', self.public_auth_url]
295 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
296 self.__logger.info("%s\n%s", " ".join(cmd), output)
297 self.created_object.append(image_creator)
298 self.__logger.info("Network ID : %s", net_id)
299 cmd = ['timeout', '-t', JujuEpc.juju_timeout,
300 'juju', 'bootstrap', 'abot-epc', 'abot-controller',
301 '--metadata-source', '/root',
302 '--constraints', 'mem=2G',
303 '--bootstrap-series', 'xenial',
304 '--config', 'network={}'.format(net_id),
305 '--config', 'ssl-hostname-verification=false',
306 '--config', 'use-floating-ip=true',
307 '--config', 'use-default-secgroup=true',
309 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
310 self.__logger.info("%s\n%s", " ".join(cmd), output)
313 def check_app(self, name='abot-epc-basic', status='active'):
314 """Check application status."""
315 cmd = ['juju', 'status', '--format', 'short', name]
316 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
317 self.__logger.info("%s\n%s", " ".join(cmd), output)
318 ret = re.search(r'(?=workload:({})\))'.format(status), output)
320 self.__logger.info("%s workload is %s", name, status)
322 self.__logger.error("%s workload differs from %s", name, status)
325 def deploy_vnf(self):
326 """Deploy ABOT-OAI-EPC."""
327 self.__logger.info("Upload VNFD")
328 descriptor = self.vnf['descriptor']
329 self.__logger.info("Get or create flavor for all Abot-EPC")
330 flavor_settings = FlavorConfig(
331 name=self.vnf['requirements']['flavor']['name'],
332 ram=self.vnf['requirements']['flavor']['ram_min'],
335 flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
336 flavor_creator.create()
337 self.created_object.append(flavor_creator)
338 self.__logger.info("Deploying Abot-epc bundle file ...")
339 cmd = ['timeout', '-t', JujuEpc.juju_timeout,
340 'juju', 'deploy', '{}'.format(descriptor.get('file_name'))]
341 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
342 self.__logger.info("%s\n%s", " ".join(cmd), output)
343 self.__logger.info("Waiting for instances .....")
344 cmd = ['timeout', '-t', JujuEpc.juju_timeout, 'juju-wait']
345 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
346 self.__logger.info("%s\n%s", " ".join(cmd), output)
347 self.__logger.info("Deployed Abot-epc on Openstack")
348 nova_client = nova_utils.nova_client(self.snaps_creds)
349 instances = get_instances(nova_client)
350 self.__logger.info("List of Instance: %s", instances)
351 for items in instances:
352 metadata = get_instance_metadata(nova_client, items)
353 if 'juju-units-deployed' in metadata:
354 sec_group = 'juju-{}-{}'.format(
355 metadata['juju-controller-uuid'],
356 metadata['juju-model-uuid'])
357 self.__logger.info("Instance: %s", sec_group)
359 self.__logger.info("Adding Security group rule....")
360 # This will add sctp rule to a common Security Group Created
361 # by juju and shared to all deployed units.
362 self._add_custom_rule(sec_group)
363 cmd = ['juju', 'status']
364 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
365 self.__logger.debug("%s\n%s", " ".join(cmd), output)
366 for app in ['abot-epc-basic', 'oai-epc', 'oai-hss']:
367 if not self.check_app(app):
369 self.__logger.info("Copying the feature files to Abot_node ")
370 cmd = ['timeout', '-t', JujuEpc.juju_timeout,
371 'juju', 'scp', '--', '-r', '-v',
372 '{}/featureFiles'.format(self.case_dir), 'abot-epc-basic/0:~/']
373 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
374 self.__logger.info("%s\n%s", " ".join(cmd), output)
375 self.__logger.info("Copying the feature files in Abot_node ")
376 cmd = ['timeout', '-t', JujuEpc.juju_timeout,
377 'juju', 'ssh', 'abot-epc-basic/0',
378 'sudo', 'rsync', '-azvv', '~/featureFiles',
379 '/etc/rebaca-test-suite/featureFiles']
380 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
381 self.__logger.info("%s\n%s", " ".join(cmd), output)
385 """Run test on ABoT."""
386 start_time = time.time()
387 self.__logger.info("Running VNF Test cases....")
388 cmd = ['timeout', '-t', JujuEpc.juju_timeout,
389 'juju', 'run-action', 'abot-epc-basic/0', 'run',
390 'tagnames={}'.format(self.details['test_vnf']['tag_name'])]
391 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
392 self.__logger.info("%s\n%s", " ".join(cmd), output)
393 cmd = ['timeout', '-t', JujuEpc.juju_timeout, 'juju-wait']
394 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
395 self.__logger.info("%s\n%s", " ".join(cmd), output)
396 duration = time.time() - start_time
397 self.__logger.info("Getting results from Abot node....")
398 cmd = ['timeout', '-t', JujuEpc.juju_timeout,
399 'juju', 'scp', '--', '-v',
401 '/var/lib/abot-epc-basic/artifacts/TestResults.json',
402 '{}/.'.format(self.res_dir)]
403 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
404 self.__logger.info("%s\n%s", " ".join(cmd), output)
405 self.__logger.info("Parsing the Test results...")
406 res = (process_abot_test_result('{}/TestResults.json'.format(
408 short_result = sig_test_format(res)
409 self.__logger.info(short_result)
410 self.details['test_vnf'].update(
411 status='PASS', result=short_result, full_result=res,
414 "Test VNF result: Passed: %d, Failed:%d, Skipped: %d",
415 short_result['passed'],
416 short_result['failures'], short_result['skipped'])
420 """Clean created objects/functions."""
422 cmd = ['juju', 'debug-log', '--replay', '--no-tail']
423 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
424 self.__logger.debug("%s\n%s", " ".join(cmd), output)
425 if not self.orchestrator['requirements']['preserve_setup']:
426 self.__logger.info("Destroying Orchestrator...")
427 cmd = ['timeout', '-t', JujuEpc.juju_timeout,
428 'juju', 'destroy-controller', '-y', 'abot-controller',
429 '--destroy-all-models']
430 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
431 self.__logger.info("%s\n%s", " ".join(cmd), output)
432 except Exception: # pylint: disable=broad-except
433 self.__logger.exception("Some issue during the undeployment ..")
435 if not self.orchestrator['requirements']['preserve_setup']:
436 self.__logger.info('Remove the Abot_epc OS object ..')
437 super(JujuEpc, self).clean()
442 # ----------------------------------------------------------
446 # -----------------------------------------------------------
447 def get_config(parameter, file_path):
449 Returns the value of a given parameter in file.yaml
450 parameter must be given in string format with dots
451 Example: general.openstack.image_name
453 with open(file_path) as config_file:
454 file_yaml = yaml.safe_load(config_file)
457 for element in parameter.split("."):
458 value = value.get(element)
460 raise ValueError("The parameter %s is not defined in"
461 " reporting.yaml" % parameter)
465 def sig_test_format(sig_test):
467 Process the signaling result to have a short result
472 for data_test in sig_test:
473 if data_test['result'] == "passed":
475 elif data_test['result'] == "failed":
477 elif data_test['result'] == "skipped":
479 total_sig_test_result = {}
480 total_sig_test_result['passed'] = nb_passed
481 total_sig_test_result['failures'] = nb_failures
482 total_sig_test_result['skipped'] = nb_skipped
483 return total_sig_test_result
486 def process_abot_test_result(file_path):
487 """ Process ABoT Result """
488 with open(file_path) as test_result:
489 data = json.load(test_result)
492 tests = update_data(tests)
494 flatten_steps = tests['elements'][0].pop('flatten_steps')
495 for steps in flatten_steps:
496 steps['result'] = steps['step_status']
499 logging.error("Could not post data to ElasticSearch host")
504 def update_data(obj):
505 """ Update Result data"""
507 obj['feature_file'] = os.path.splitext(os.path.basename(obj['uri']))[0]
509 for element in obj['elements']:
510 element['final_result'] = "passed"
511 element['flatten_steps'] = []
513 for step in element['steps']:
515 step['result'][step['result']['status']] = 1
516 if step['result']['status'].lower() in ['fail', 'failed']:
517 element['final_result'] = "failed"
519 temp_dict['feature_file'] = obj['feature_file']
520 temp_dict['step_name'] = step['name']
521 temp_dict['step_status'] = step['result']['status']
522 temp_dict['step_duration'] = step['result'].get('duration', 0)
523 temp_dict['step_' + step['result']['status']] = 1
524 element['flatten_steps'].append(deepcopy(temp_dict))
526 # Need to put the tag in OBJ and not ELEMENT
528 element['tags'] = deepcopy(obj['tags'])
529 for tag in obj['tags']:
530 element[tag['name']] = 1
532 for tag in element['tags']:
533 element[tag['name']] = 1
535 except Exception: # pylint: disable=broad-except
536 logging.error("Error in updating data, %s", sys.exc_info()[0])
542 def get_instances(nova_client):
543 """ To get all vm info of a project """
545 instances = nova_client.servers.list()
547 except Exception as exc: # pylint: disable=broad-except
548 logging.error("Error [get_instances(nova_client)]: %s", exc)
552 def get_instance_metadata(nova_client, instance):
553 """ Get instance Metadata - Instance ID """
555 instance = nova_client.servers.get(instance.id)
556 return instance.metadata
557 except Exception as exc: # pylint: disable=broad-except
558 logging.error("Error [get_instance_status(nova_client)]: %s", exc)