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.case_dir))
386 self.__logger.info("Parsing the Test results...")
387 res = (process_abot_test_result('{}/TestResults.'
388 'json'.format(self.case_dir)))
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("Removing deployment files...")
406 testresult = os.path.join(self.case_dir, 'TestResults.json')
407 if os.path.exists(testresult):
408 os.remove(testresult)
409 self.__logger.info("Destroying Orchestrator...")
410 os.system('juju destroy-controller -y abot-controller '
411 '--destroy-all-models')
412 except Exception: # pylint: disable=broad-except
413 self.__logger.warn("Some issue during the undeployment ..")
414 self.__logger.warn("Tenant clean continue ..")
416 if not self.orchestrator['requirements']['preserve_setup']:
417 self.__logger.info('Remove the Abot_epc OS object ..')
418 super(JujuEpc, self).clean()
423 # ----------------------------------------------------------
427 # -----------------------------------------------------------
428 def get_config(parameter, file_path):
430 Returns the value of a given parameter in file.yaml
431 parameter must be given in string format with dots
432 Example: general.openstack.image_name
434 with open(file_path) as config_file:
435 file_yaml = yaml.safe_load(config_file)
438 for element in parameter.split("."):
439 value = value.get(element)
441 raise ValueError("The parameter %s is not defined in"
442 " reporting.yaml" % parameter)
446 def sig_test_format(sig_test):
448 Process the signaling result to have a short result
453 for data_test in sig_test:
454 if data_test['result'] == "passed":
456 elif data_test['result'] == "failed":
458 elif data_test['result'] == "skipped":
460 total_sig_test_result = {}
461 total_sig_test_result['passed'] = nb_passed
462 total_sig_test_result['failures'] = nb_failures
463 total_sig_test_result['skipped'] = nb_skipped
464 return total_sig_test_result
467 def process_abot_test_result(file_path):
468 """ Process ABoT Result """
469 with open(file_path) as test_result:
470 data = json.load(test_result)
473 tests = update_data(tests)
475 flatten_steps = tests['elements'][0].pop('flatten_steps')
476 for steps in flatten_steps:
477 steps['result'] = steps['step_status']
480 logging.error("Could not post data to ElasticSearch host")
485 def update_data(obj):
486 """ Update Result data"""
488 obj['feature_file'] = os.path.splitext(os.path.basename(obj['uri']))[0]
490 for element in obj['elements']:
491 element['final_result'] = "passed"
492 element['flatten_steps'] = []
494 for step in element['steps']:
496 step['result'][step['result']['status']] = 1
497 if step['result']['status'].lower() in ['fail', 'failed']:
498 element['final_result'] = "failed"
500 temp_dict['feature_file'] = obj['feature_file']
501 temp_dict['step_name'] = step['name']
502 temp_dict['step_status'] = step['result']['status']
503 temp_dict['step_duration'] = step['result'].get('duration', 0)
504 temp_dict['step_' + step['result']['status']] = 1
505 element['flatten_steps'].append(deepcopy(temp_dict))
507 # Need to put the tag in OBJ and not ELEMENT
509 element['tags'] = deepcopy(obj['tags'])
510 for tag in obj['tags']:
511 element[tag['name']] = 1
513 for tag in element['tags']:
514 element[tag['name']] = 1
516 except Exception: # pylint: disable=broad-except
517 logging.error("Error in updating data, %s", sys.exc_info()[0])
523 def get_instances(nova_client):
524 """ To get all vm info of a project """
526 instances = nova_client.servers.list()
528 except Exception as exc: # pylint: disable=broad-except
529 logging.error("Error [get_instances(nova_client)]: %s", exc)
533 def get_instance_metadata(nova_client, instance):
534 """ Get instance Metadata - Instance ID """
536 instance = nova_client.servers.get(instance.id)
537 return instance.metadata
538 except Exception as exc: # pylint: disable=broad-except
539 logging.error("Error [get_instance_status(nova_client)]: %s", exc)