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__)
85 juju_wait_timeout = '3600'
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 = ['juju', 'metadata', 'generate-image', '-d', '/root',
291 '-i', image_id, '-s', image_name,
292 '-r', self.snaps_creds.region_name,
293 '-u', self.public_auth_url]
294 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
295 self.__logger.info("%s\n%s", " ".join(cmd), output)
296 self.created_object.append(image_creator)
297 self.__logger.info("Network ID : %s", net_id)
298 cmd = ['juju', 'bootstrap', 'abot-epc', 'abot-controller',
299 '--metadata-source', '/root',
300 '--constraints', 'mem=2G',
301 '--bootstrap-series', 'xenial',
302 '--config', 'network={}'.format(net_id),
303 '--config', 'ssl-hostname-verification=false',
304 '--config', 'use-floating-ip=true',
305 '--config', 'use-default-secgroup=true',
307 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
308 self.__logger.info("%s\n%s", " ".join(cmd), output)
311 def check_app(self, name='abot-epc-basic', status='active'):
312 """Check application status."""
313 cmd = ['juju', 'status', '--format', 'short', name]
314 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
315 self.__logger.info("%s\n%s", " ".join(cmd), output)
316 ret = re.search(r'(?=workload:({})\))'.format(status), output)
318 self.__logger.info("%s workload is %s", name, status)
320 self.__logger.error("%s workload differs from %s", name, status)
323 def deploy_vnf(self):
324 """Deploy ABOT-OAI-EPC."""
325 self.__logger.info("Upload VNFD")
326 descriptor = self.vnf['descriptor']
327 self.__logger.info("Get or create flavor for all Abot-EPC")
328 flavor_settings = FlavorConfig(
329 name=self.vnf['requirements']['flavor']['name'],
330 ram=self.vnf['requirements']['flavor']['ram_min'],
333 flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
334 flavor_creator.create()
335 self.created_object.append(flavor_creator)
336 self.__logger.info("Deploying Abot-epc bundle file ...")
337 cmd = ['juju', 'deploy', '{}'.format(descriptor.get('file_name'))]
338 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
339 self.__logger.info("%s\n%s", " ".join(cmd), output)
340 self.__logger.info("Waiting for instances .....")
341 cmd = ['timeout', '-t', JujuEpc.juju_wait_timeout, 'juju-wait']
342 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
343 self.__logger.info("%s\n%s", " ".join(cmd), output)
344 self.__logger.info("Deployed Abot-epc on Openstack")
345 nova_client = nova_utils.nova_client(self.snaps_creds)
346 instances = get_instances(nova_client)
347 self.__logger.info("List of Instance: %s", instances)
348 for items in instances:
349 metadata = get_instance_metadata(nova_client, items)
350 if 'juju-units-deployed' in metadata:
351 sec_group = 'juju-{}-{}'.format(
352 metadata['juju-controller-uuid'],
353 metadata['juju-model-uuid'])
354 self.__logger.info("Instance: %s", sec_group)
356 self.__logger.info("Adding Security group rule....")
357 # This will add sctp rule to a common Security Group Created
358 # by juju and shared to all deployed units.
359 self._add_custom_rule(sec_group)
360 cmd = ['juju', 'status']
361 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
362 self.__logger.debug("%s\n%s", " ".join(cmd), output)
363 for app in ['abot-epc-basic', 'oai-epc', 'oai-hss']:
364 if not self.check_app(app):
366 self.__logger.info("Copying the feature files to Abot_node ")
367 cmd = ['juju', 'scp', '--', '-r', '-v',
368 '{}/featureFiles'.format(self.case_dir), 'abot-epc-basic/0:~/']
369 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
370 self.__logger.info("%s\n%s", " ".join(cmd), output)
371 self.__logger.info("Copying the feature files in Abot_node ")
372 cmd = ['juju', 'ssh', 'abot-epc-basic/0',
373 'sudo', 'rsync', '-azvv', '~/featureFiles',
374 '/etc/rebaca-test-suite/featureFiles']
375 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
376 self.__logger.info("%s\n%s", " ".join(cmd), output)
380 """Run test on ABoT."""
381 start_time = time.time()
382 self.__logger.info("Running VNF Test cases....")
383 cmd = ['juju', 'run-action', 'abot-epc-basic/0', 'run',
384 'tagnames={}'.format(self.details['test_vnf']['tag_name'])]
385 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
386 self.__logger.info("%s\n%s", " ".join(cmd), output)
387 cmd = ['timeout', '-t', JujuEpc.juju_wait_timeout, 'juju-wait']
388 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
389 self.__logger.info("%s\n%s", " ".join(cmd), output)
390 duration = time.time() - start_time
391 self.__logger.info("Getting results from Abot node....")
392 cmd = ['juju', 'scp', '--', '-v',
394 '/var/lib/abot-epc-basic/artifacts/TestResults.json',
395 '{}/.'.format(self.res_dir)]
396 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
397 self.__logger.info("%s\n%s", " ".join(cmd), output)
398 self.__logger.info("Parsing the Test results...")
399 res = (process_abot_test_result('{}/TestResults.json'.format(
401 short_result = sig_test_format(res)
402 self.__logger.info(short_result)
403 self.details['test_vnf'].update(
404 status='PASS', result=short_result, full_result=res,
407 "Test VNF result: Passed: %d, Failed:%d, Skipped: %d",
408 short_result['passed'],
409 short_result['failures'], short_result['skipped'])
413 """Clean created objects/functions."""
415 cmd = ['juju', 'debug-log', '--replay', '--no-tail']
416 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
417 self.__logger.debug("%s\n%s", " ".join(cmd), output)
418 if not self.orchestrator['requirements']['preserve_setup']:
419 self.__logger.info("Destroying Orchestrator...")
420 cmd = ['juju', 'destroy-controller', '-y', 'abot-controller',
421 '--destroy-all-models']
422 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
423 self.__logger.info("%s\n%s", " ".join(cmd), output)
424 except Exception: # pylint: disable=broad-except
425 self.__logger.exception("Some issue during the undeployment ..")
427 if not self.orchestrator['requirements']['preserve_setup']:
428 self.__logger.info('Remove the Abot_epc OS object ..')
429 super(JujuEpc, self).clean()
434 # ----------------------------------------------------------
438 # -----------------------------------------------------------
439 def get_config(parameter, file_path):
441 Returns the value of a given parameter in file.yaml
442 parameter must be given in string format with dots
443 Example: general.openstack.image_name
445 with open(file_path) as config_file:
446 file_yaml = yaml.safe_load(config_file)
449 for element in parameter.split("."):
450 value = value.get(element)
452 raise ValueError("The parameter %s is not defined in"
453 " reporting.yaml" % parameter)
457 def sig_test_format(sig_test):
459 Process the signaling result to have a short result
464 for data_test in sig_test:
465 if data_test['result'] == "passed":
467 elif data_test['result'] == "failed":
469 elif data_test['result'] == "skipped":
471 total_sig_test_result = {}
472 total_sig_test_result['passed'] = nb_passed
473 total_sig_test_result['failures'] = nb_failures
474 total_sig_test_result['skipped'] = nb_skipped
475 return total_sig_test_result
478 def process_abot_test_result(file_path):
479 """ Process ABoT Result """
480 with open(file_path) as test_result:
481 data = json.load(test_result)
484 tests = update_data(tests)
486 flatten_steps = tests['elements'][0].pop('flatten_steps')
487 for steps in flatten_steps:
488 steps['result'] = steps['step_status']
491 logging.error("Could not post data to ElasticSearch host")
496 def update_data(obj):
497 """ Update Result data"""
499 obj['feature_file'] = os.path.splitext(os.path.basename(obj['uri']))[0]
501 for element in obj['elements']:
502 element['final_result'] = "passed"
503 element['flatten_steps'] = []
505 for step in element['steps']:
507 step['result'][step['result']['status']] = 1
508 if step['result']['status'].lower() in ['fail', 'failed']:
509 element['final_result'] = "failed"
511 temp_dict['feature_file'] = obj['feature_file']
512 temp_dict['step_name'] = step['name']
513 temp_dict['step_status'] = step['result']['status']
514 temp_dict['step_duration'] = step['result'].get('duration', 0)
515 temp_dict['step_' + step['result']['status']] = 1
516 element['flatten_steps'].append(deepcopy(temp_dict))
518 # Need to put the tag in OBJ and not ELEMENT
520 element['tags'] = deepcopy(obj['tags'])
521 for tag in obj['tags']:
522 element[tag['name']] = 1
524 for tag in element['tags']:
525 element[tag['name']] = 1
527 except Exception: # pylint: disable=broad-except
528 logging.error("Error in updating data, %s", sys.exc_info()[0])
534 def get_instances(nova_client):
535 """ To get all vm info of a project """
537 instances = nova_client.servers.list()
539 except Exception as exc: # pylint: disable=broad-except
540 logging.error("Error [get_instances(nova_client)]: %s", exc)
544 def get_instance_metadata(nova_client, instance):
545 """ Get instance Metadata - Instance ID """
547 instance = nova_client.servers.get(instance.id)
548 return instance.metadata
549 except Exception as exc: # pylint: disable=broad-except
550 logging.error("Error [get_instance_status(nova_client)]: %s", exc)