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."""
19 from copy import deepcopy
20 from urlparse import urljoin
24 from functest.core import vnf
25 from functest.opnfv_tests.openstack.snaps import snaps_utils
26 from functest.utils import config
27 from functest.utils import env
29 from snaps.config.flavor import FlavorConfig
30 from snaps.config.image import ImageConfig
31 from snaps.config.network import NetworkConfig, SubnetConfig
32 from snaps.config.router import RouterConfig
33 from snaps.config.security_group import (
34 Direction, Protocol, SecurityGroupConfig, SecurityGroupRuleConfig)
35 from snaps.config.user import UserConfig
36 from snaps.openstack.create_flavor import OpenStackFlavor
37 from snaps.openstack.create_image import OpenStackImage
38 from snaps.openstack.create_network import OpenStackNetwork
39 from snaps.openstack.create_router import OpenStackRouter
40 from snaps.openstack.create_security_group import OpenStackSecurityGroup
41 from snaps.openstack.create_user import OpenStackUser
42 from snaps.openstack.utils import keystone_utils
43 from snaps.openstack.utils import nova_utils
45 __author__ = "Amarendra Meher <amarendra@rebaca.com>"
46 __author__ = "Soumaya K Nayek <soumaya.nayek@rebaca.com>"
48 CLOUD_TEMPLATE = """clouds:
51 auth-types: [userpass]
57 CREDS_TEMPLATE2 = """credentials:
59 default-credential: abot-epc
63 project-domain-name: {project_domain_n}
64 tenant-name: {tenant_n}"""
66 CREDS_TEMPLATE3 = """credentials:
68 default-credential: abot-epc
72 project-domain-name: {project_domain_n}
73 tenant-name: {tenant_n}
74 user-domain-name: {user_domain_n}
78 class JujuEpc(vnf.VnfOnBoarding):
79 # pylint:disable=too-many-instance-attributes
80 """Abot EPC deployed with JUJU Orchestrator Case"""
82 __logger = logging.getLogger(__name__)
84 def __init__(self, **kwargs):
85 if "case_name" not in kwargs:
86 kwargs["case_name"] = "juju_epc"
87 super(JujuEpc, self).__init__(**kwargs)
89 # Retrieve the configuration
90 self.case_dir = pkg_resources.resource_filename(
91 'functest', 'opnfv_tests/vnf/epc')
93 self.config = getattr(
94 config.CONF, 'vnf_{}_config'.format(self.case_name))
96 raise Exception("VNF config file not found")
97 self.config_file = os.path.join(self.case_dir, self.config)
98 self.orchestrator = dict(requirements=get_config(
99 "orchestrator.requirements", self.config_file))
101 self.created_object = []
102 self.details['orchestrator'] = dict(
103 name=get_config("orchestrator.name", self.config_file),
104 version=get_config("orchestrator.version", self.config_file),
110 descriptor=get_config("vnf.descriptor", self.config_file),
111 requirements=get_config("vnf.requirements", self.config_file)
113 self.details['vnf'] = dict(
114 descriptor_version=self.vnf['descriptor']['version'],
115 name=get_config("vnf.name", self.config_file),
116 version=get_config("vnf.version", self.config_file),
118 self.__logger.debug("VNF configuration: %s", self.vnf)
120 self.details['test_vnf'] = dict(
121 name=get_config("vnf_test_suite.name", self.config_file),
122 version=get_config("vnf_test_suite.version", self.config_file),
123 tag_name=get_config("vnf_test_suite.tag_name", self.config_file)
125 self.public_auth_url = None
127 self.res_dir = os.path.join(
128 getattr(config.CONF, 'dir_results'), self.case_name)
130 def _bypass_juju_network_discovery_bug(self, name):
131 user_creator = OpenStackUser(
135 password=str(uuid.uuid4()),
136 project_name=self.tenant_name,
137 domain=self.snaps_creds.user_domain_name,
138 roles={'_member_': self.tenant_name}))
139 user_creator.create()
140 self.created_object.append(user_creator)
143 def _register_cloud(self):
144 self.__logger.info("Creating Cloud for Abot-epc .....")
145 clouds_yaml = os.path.join(self.res_dir, "clouds.yaml")
146 # It allows gating APEX and ensures this testcase is working till
147 # https://jira.opnfv.org/browse/APEX-570 is fixed in APEX.
148 # It must be removed as soon as possible to disable per installer
149 # processing in Functest.
150 region = self.snaps_creds.region_name
151 if not region and env.get('INSTALLER_TYPE') == 'apex':
154 'url': self.public_auth_url,
156 with open(clouds_yaml, 'w') as yfile:
157 yfile.write(CLOUD_TEMPLATE.format(**cloud_data))
158 cmd = ['juju', 'add-cloud', 'abot-epc', '-f', clouds_yaml, '--replace']
159 output = subprocess.check_output(cmd)
160 self.__logger.info("%s\n%s", " ".join(cmd), output)
162 def _register_credentials_v2(self):
163 self.__logger.info("Creating Credentials for Abot-epc .....")
164 user_creator = self._bypass_juju_network_discovery_bug(
165 'juju_network_discovery_bug')
166 snaps_creds = user_creator.get_os_creds(self.snaps_creds.project_name)
167 self.__logger.debug("snaps creds: %s", snaps_creds)
168 credentials_yaml = os.path.join(self.res_dir, "credentials.yaml")
170 'pass': snaps_creds.password,
171 'tenant_n': snaps_creds.project_name,
172 'user_n': snaps_creds.username}
173 with open(credentials_yaml, 'w') as yfile:
174 yfile.write(CREDS_TEMPLATE2.format(**creds_data))
175 cmd = ['juju', 'add-credential', 'abot-epc', '-f', credentials_yaml,
177 output = subprocess.check_output(cmd)
178 self.__logger.info("%s\n%s", " ".join(cmd), output)
180 def _register_credentials_v3(self):
181 self.__logger.info("Creating Credentials for Abot-epc .....")
182 user_creator = self._bypass_juju_network_discovery_bug(
183 'juju_network_discovery_bug')
184 snaps_creds = user_creator.get_os_creds(self.snaps_creds.project_name)
185 self.__logger.debug("snaps creds: %s", snaps_creds)
186 credentials_yaml = os.path.join(self.res_dir, "credentials.yaml")
188 'pass': snaps_creds.password,
189 'tenant_n': snaps_creds.project_name,
190 'user_n': snaps_creds.username,
191 'project_domain_n': snaps_creds.project_domain_name,
192 'user_domain_n': snaps_creds.user_domain_name}
193 with open(credentials_yaml, 'w') as yfile:
194 yfile.write(CREDS_TEMPLATE3.format(**creds_data))
195 cmd = ['juju', 'add-credential', 'abot-epc', '-f', credentials_yaml,
197 output = subprocess.check_output(cmd)
198 self.__logger.info("%s\n%s", " ".join(cmd), output)
200 def _add_custom_rule(self, sec_grp_name):
201 """ To add custom rule for SCTP Traffic """
202 sec_grp_rules = list()
203 sec_grp_rules.append(
204 SecurityGroupRuleConfig(
205 sec_grp_name=sec_grp_name, direction=Direction.ingress,
206 protocol=Protocol.sctp))
207 security_group = OpenStackSecurityGroup(
211 rule_settings=sec_grp_rules))
212 security_group.create()
213 self.created_object.append(security_group)
216 """Prepare testcase (Additional pre-configuration steps)."""
217 self.__logger.info("Additional pre-configuration steps")
218 super(JujuEpc, self).prepare()
220 os.makedirs(self.res_dir)
221 except OSError as ex:
222 if ex.errno != errno.EEXIST:
223 self.__logger.exception("Cannot create %s", self.res_dir)
224 raise vnf.VnfPreparationException
225 self.public_auth_url = keystone_utils.get_endpoint(
226 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("Deployed 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 ...")
257 subnet_settings = SubnetConfig(
258 name=private_subnet_name,
259 cidr=private_subnet_cidr,
260 dns_nameservers=[env.get('NAMESERVER')])
261 network_settings = NetworkConfig(
262 name=private_net_name, subnet_settings=[subnet_settings])
263 network_creator = OpenStackNetwork(self.snaps_creds, network_settings)
264 net_id = network_creator.create().id
265 self.created_object.append(network_creator)
267 ext_net_name = snaps_utils.get_ext_net_name(self.snaps_creds)
268 self.__logger.info("Creating network Router ....")
269 router_creator = OpenStackRouter(
270 self.snaps_creds, RouterConfig(
272 external_gateway=ext_net_name,
273 internal_subnets=[subnet_settings.name]))
274 router_creator.create()
275 self.created_object.append(router_creator)
276 self.__logger.info("Creating Flavor ....")
277 flavor_settings = FlavorConfig(
278 name=self.orchestrator['requirements']['flavor']['name'],
279 ram=self.orchestrator['requirements']['flavor']['ram_min'],
281 flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
282 flavor_creator.create()
283 self.created_object.append(flavor_creator)
284 self.__logger.info("Upload some OS images if it doesn't exist")
285 images = get_config("tenant_images", self.config_file)
286 self.__logger.info("Images needed for vEPC: %s", images)
287 for image_name, image_file in images.iteritems():
288 self.__logger.info("image: %s, file: %s", image_name, image_file)
289 if image_file and image_name:
290 image_creator = OpenStackImage(self.snaps_creds, ImageConfig(
291 name=image_name, image_user='cloud', img_format='qcow2',
292 image_file=image_file))
293 image_id = image_creator.create().id
294 # It allows gating APEX and ensures this testcase is working
295 # till https://jira.opnfv.org/browse/APEX-570 is fixed in APEX.
296 # It must be removed as soon as possible to disable per
297 # installer processing in Functest.
298 region = self.snaps_creds.region_name
299 if not region and env.get('INSTALLER_TYPE') == 'apex':
301 cmd = ['juju', 'metadata', 'generate-image', '-d', '~', '-i',
302 image_id, '-s', image_name, '-r', region, '-u',
303 self.public_auth_url]
304 output = subprocess.check_output(cmd)
305 self.__logger.info("%s\n%s", " ".join(cmd), output)
306 self.created_object.append(image_creator)
307 self.__logger.info("Network ID : %s", net_id)
308 juju_bootstrap_command = (
309 'juju bootstrap abot-epc abot-controller --config network={} '
310 '--metadata-source ~ --config ssl-hostname-verification=false '
311 '--constraints mem=2G --bootstrap-series xenial '
312 '--config use-floating-ip=true --debug '
313 '--config use-default-secgroup=true'.format(net_id))
314 if os.system(juju_bootstrap_command) != 0:
318 def deploy_vnf(self):
319 """Deploy ABOT-OAI-EPC."""
320 self.__logger.info("Upload VNFD")
321 descriptor = self.vnf['descriptor']
322 self.__logger.info("Get or create flavor for all Abot-EPC")
323 flavor_settings = FlavorConfig(
324 name=self.vnf['requirements']['flavor']['name'],
325 ram=self.vnf['requirements']['flavor']['ram_min'],
328 flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
329 flavor_creator.create()
330 self.created_object.append(flavor_creator)
331 self.__logger.info("Deploying Abot-epc bundle file ...")
332 os.system('juju deploy {}'.format('/' + descriptor.get('file_name')))
333 self.__logger.info("Waiting for instances .....")
334 status = os.system('juju-wait')
335 self.__logger.info("juju wait completed: %s", status)
336 self.__logger.info("Deployed Abot-epc on Openstack")
337 nova_client = nova_utils.nova_client(self.snaps_creds)
339 instances = get_instances(nova_client)
340 self.__logger.info("List of Instance: %s", instances)
341 for items in instances:
342 metadata = get_instance_metadata(nova_client, items)
343 if 'juju-units-deployed' in metadata:
344 sec_group = ('juju-' +
345 metadata['juju-controller-uuid'] +
346 '-' + metadata['juju-model-uuid'])
347 self.__logger.info("Instance: %s", sec_group)
349 self.__logger.info("Adding Security group rule....")
350 # This will add sctp rule to a common Security Group Created
351 # by juju and shared to all deployed units.
352 self._add_custom_rule(sec_group)
353 self.__logger.info("Copying the feature files to Abot_node ")
354 os.system('juju scp -- -r {}/featureFiles abot-'
355 'epc-basic/0:~/'.format(self.case_dir))
356 self.__logger.info("Copying the feature files in Abot_node ")
357 os.system("juju ssh abot-epc-basic/0 'sudo rsync -azvv "
358 "~/featureFiles /etc/rebaca-test-suite"
362 epcstatus = os.system('juju status oai-epc | '
363 'grep {} | grep {} | grep {}'
364 .format('EPC', 'is', 'running'))
370 os.system('juju-wait')
375 """Run test on ABoT."""
376 start_time = time.time()
377 self.__logger.info("Running VNF Test cases....")
378 os.system('juju run-action abot-epc-basic/0 run '
379 'tagnames={}'.format(self.details['test_vnf']['tag_name']))
380 os.system('juju-wait')
381 duration = time.time() - start_time
382 self.__logger.info("Getting results from Abot node....")
383 os.system('juju scp abot-epc-basic/0:/var/lib/abot-'
384 'epc-basic/artifacts/TestResults.json {}/.'
385 .format(self.res_dir))
386 self.__logger.info("Parsing the Test results...")
387 res = (process_abot_test_result('{}/TestResults.json'.format(
389 short_result = sig_test_format(res)
390 self.__logger.info(short_result)
391 self.details['test_vnf'].update(status='PASS',
396 self.__logger.info("Test VNF result: Passed: %d, Failed:"
397 "%d, Skipped: %d", short_result['passed'],
398 short_result['failures'], short_result['skipped'])
402 """Clean created objects/functions."""
404 if not self.orchestrator['requirements']['preserve_setup']:
405 self.__logger.info("Destroying Orchestrator...")
406 os.system('juju destroy-controller -y abot-controller '
407 '--destroy-all-models')
408 except Exception: # pylint: disable=broad-except
409 self.__logger.warn("Some issue during the undeployment ..")
410 self.__logger.warn("Tenant clean continue ..")
412 if not self.orchestrator['requirements']['preserve_setup']:
413 self.__logger.info('Remove the Abot_epc OS object ..')
414 super(JujuEpc, self).clean()
419 # ----------------------------------------------------------
423 # -----------------------------------------------------------
424 def get_config(parameter, file_path):
426 Returns the value of a given parameter in file.yaml
427 parameter must be given in string format with dots
428 Example: general.openstack.image_name
430 with open(file_path) as config_file:
431 file_yaml = yaml.safe_load(config_file)
434 for element in parameter.split("."):
435 value = value.get(element)
437 raise ValueError("The parameter %s is not defined in"
438 " reporting.yaml" % parameter)
442 def sig_test_format(sig_test):
444 Process the signaling result to have a short result
449 for data_test in sig_test:
450 if data_test['result'] == "passed":
452 elif data_test['result'] == "failed":
454 elif data_test['result'] == "skipped":
456 total_sig_test_result = {}
457 total_sig_test_result['passed'] = nb_passed
458 total_sig_test_result['failures'] = nb_failures
459 total_sig_test_result['skipped'] = nb_skipped
460 return total_sig_test_result
463 def process_abot_test_result(file_path):
464 """ Process ABoT Result """
465 with open(file_path) as test_result:
466 data = json.load(test_result)
469 tests = update_data(tests)
471 flatten_steps = tests['elements'][0].pop('flatten_steps')
472 for steps in flatten_steps:
473 steps['result'] = steps['step_status']
476 logging.error("Could not post data to ElasticSearch host")
481 def update_data(obj):
482 """ Update Result data"""
484 obj['feature_file'] = os.path.splitext(os.path.basename(obj['uri']))[0]
486 for element in obj['elements']:
487 element['final_result'] = "passed"
488 element['flatten_steps'] = []
490 for step in element['steps']:
492 step['result'][step['result']['status']] = 1
493 if step['result']['status'].lower() in ['fail', 'failed']:
494 element['final_result'] = "failed"
496 temp_dict['feature_file'] = obj['feature_file']
497 temp_dict['step_name'] = step['name']
498 temp_dict['step_status'] = step['result']['status']
499 temp_dict['step_duration'] = step['result'].get('duration', 0)
500 temp_dict['step_' + step['result']['status']] = 1
501 element['flatten_steps'].append(deepcopy(temp_dict))
503 # Need to put the tag in OBJ and not ELEMENT
505 element['tags'] = deepcopy(obj['tags'])
506 for tag in obj['tags']:
507 element[tag['name']] = 1
509 for tag in element['tags']:
510 element[tag['name']] = 1
512 except Exception: # pylint: disable=broad-except
513 logging.error("Error in updating data, %s", sys.exc_info()[0])
519 def get_instances(nova_client):
520 """ To get all vm info of a project """
522 instances = nova_client.servers.list()
524 except Exception as exc: # pylint: disable=broad-except
525 logging.error("Error [get_instances(nova_client)]: %s", exc)
529 def get_instance_metadata(nova_client, instance):
530 """ Get instance Metadata - Instance ID """
532 instance = nova_client.servers.get(instance.id)
533 return instance.metadata
534 except Exception as exc: # pylint: disable=broad-except
535 logging.error("Error [get_instance_status(nova_client)]: %s", exc)