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."""
18 from copy import deepcopy
19 from urlparse import urljoin
23 from functest.core import vnf
24 from functest.opnfv_tests.openstack.snaps import snaps_utils
25 from functest.utils import config
27 from snaps.config.flavor import FlavorConfig
28 from snaps.config.image import ImageConfig
29 from snaps.config.network import NetworkConfig, SubnetConfig
30 from snaps.config.router import RouterConfig
31 from snaps.config.security_group import (
32 Direction, Protocol, SecurityGroupConfig, SecurityGroupRuleConfig)
33 from snaps.config.user import UserConfig
34 from snaps.openstack.create_flavor import OpenStackFlavor
35 from snaps.openstack.create_image import OpenStackImage
36 from snaps.openstack.create_network import OpenStackNetwork
37 from snaps.openstack.create_router import OpenStackRouter
38 from snaps.openstack.create_security_group import OpenStackSecurityGroup
39 from snaps.openstack.create_user import OpenStackUser
40 from snaps.openstack.utils import keystone_utils
41 from snaps.openstack.utils import nova_utils
43 __author__ = "Amarendra Meher <amarendra@rebaca.com>"
44 __author__ = "Soumaya K Nayek <soumaya.nayek@rebaca.com>"
46 CLOUD_TEMPLATE = """clouds:
49 auth-types: [userpass]
55 CREDS_TEMPLATE2 = """credentials:
57 default-credential: abot-epc
61 project-domain-name: {project_domain_n}
62 tenant-name: {tenant_n}"""
64 CREDS_TEMPLATE3 = """credentials:
66 default-credential: abot-epc
70 project-domain-name: {project_domain_n}
71 tenant-name: {tenant_n}
72 user-domain-name: {user_domain_n}
76 class JujuEpc(vnf.VnfOnBoarding):
77 # pylint:disable=too-many-instance-attributes
78 """Abot EPC deployed with JUJU Orchestrator Case"""
80 __logger = logging.getLogger(__name__)
82 def __init__(self, **kwargs):
83 if "case_name" not in kwargs:
84 kwargs["case_name"] = "juju_epc"
85 super(JujuEpc, self).__init__(**kwargs)
87 # Retrieve the configuration
88 self.case_dir = pkg_resources.resource_filename(
89 'functest', 'opnfv_tests/vnf/epc')
91 self.config = getattr(
92 config.CONF, 'vnf_{}_config'.format(self.case_name))
94 raise Exception("VNF config file not found")
95 self.config_file = os.path.join(self.case_dir, self.config)
96 self.orchestrator = dict(requirements=get_config(
97 "orchestrator.requirements", self.config_file))
99 self.created_object = []
100 self.details['orchestrator'] = dict(
101 name=get_config("orchestrator.name", self.config_file),
102 version=get_config("orchestrator.version", self.config_file),
108 descriptor=get_config("vnf.descriptor", self.config_file),
109 requirements=get_config("vnf.requirements", self.config_file)
111 self.details['vnf'] = dict(
112 descriptor_version=self.vnf['descriptor']['version'],
113 name=get_config("vnf.name", self.config_file),
114 version=get_config("vnf.version", self.config_file),
116 self.__logger.debug("VNF configuration: %s", self.vnf)
118 self.details['test_vnf'] = dict(
119 name=get_config("vnf_test_suite.name", self.config_file),
120 version=get_config("vnf_test_suite.version", self.config_file),
121 tag_name=get_config("vnf_test_suite.tag_name", self.config_file)
123 self.public_auth_url = None
125 self.res_dir = os.path.join(
126 getattr(config.CONF, 'dir_results'), self.case_name)
128 def _bypass_juju_network_discovery_bug(self, name):
129 user_creator = OpenStackUser(
132 name=name, password=str(uuid.uuid4()),
133 roles={'_member_': self.tenant_name}))
134 user_creator.create()
135 self.created_object.append(user_creator)
138 def _register_cloud(self):
139 self.__logger.info("Creating Cloud for Abot-epc .....")
140 clouds_yaml = os.path.join(self.res_dir, "clouds.yaml")
142 'url': self.public_auth_url,
143 'region': self.snaps_creds.region_name}
144 with open(clouds_yaml, 'w') as yfile:
145 yfile.write(CLOUD_TEMPLATE.format(**cloud_data))
147 'juju add-cloud abot-epc -f {} --replace'.format(clouds_yaml)):
148 raise vnf.VnfPreparationException
150 def _register_credentials_v2(self):
151 self.__logger.info("Creating Credentials for Abot-epc .....")
152 user_creator = self._bypass_juju_network_discovery_bug(
153 'juju_network_discovery_bug')
154 snaps_creds = user_creator.get_os_creds(self.snaps_creds.project_name)
155 credentials_yaml = os.path.join(self.res_dir, "credentials.yaml")
157 'pass': snaps_creds.password,
158 'tenant_n': snaps_creds.project_name,
159 'user_n': snaps_creds.username}
160 with open(credentials_yaml, 'w') as yfile:
161 yfile.write(CREDS_TEMPLATE2.format(**creds_data))
163 'juju add-credential abot-epc -f {} --replace'.format(
165 raise vnf.VnfPreparationException
167 def _register_credentials_v3(self):
168 self.__logger.info("Creating Credentials for Abot-epc .....")
169 user_creator = self._bypass_juju_network_discovery_bug(
170 'juju_network_discovery_bug')
171 snaps_creds = user_creator.get_os_creds(self.snaps_creds.project_name)
172 credentials_yaml = os.path.join(self.res_dir, "credentials.yaml")
174 'pass': snaps_creds.password,
175 'tenant_n': snaps_creds.project_name,
176 'user_n': snaps_creds.username,
177 'project_domain_n': snaps_creds.project_domain_name,
178 'user_domain_n': snaps_creds.user_domain_name}
179 with open(credentials_yaml, 'w') as yfile:
180 yfile.write(CREDS_TEMPLATE3.format(**creds_data))
182 'juju add-credential abot-epc -f {} --replace'.format(
184 raise vnf.VnfPreparationException
186 def _add_custom_rule(self, sec_grp_name):
187 """ To add custom rule for SCTP Traffic """
188 sec_grp_rules = list()
189 sec_grp_rules.append(
190 SecurityGroupRuleConfig(
191 sec_grp_name=sec_grp_name, direction=Direction.ingress,
192 protocol=Protocol.sctp))
193 security_group = OpenStackSecurityGroup(
197 rule_settings=sec_grp_rules))
198 security_group.create()
199 self.created_object.append(security_group)
202 """Prepare testcase (Additional pre-configuration steps)."""
203 self.__logger.info("Additional pre-configuration steps")
204 super(JujuEpc, self).prepare()
206 os.makedirs(self.res_dir)
207 except OSError as ex:
208 if ex.errno != errno.EEXIST:
209 self.__logger.exception("Cannot create %s", self.res_dir)
210 raise vnf.VnfPreparationException
211 self.public_auth_url = keystone_utils.get_endpoint(
212 self.snaps_creds, 'identity')
213 # it enforces a versioned public identity endpoint as juju simply
214 # adds /auth/tokens wich fails vs an unversioned endpoint.
215 if not self.public_auth_url.endswith(('v3', 'v3/', 'v2.0', 'v2.0/')):
216 self.public_auth_url = urljoin(self.public_auth_url, 'v3')
217 self._register_cloud()
218 if self.snaps_creds.identity_api_version == 3:
219 self._register_credentials_v3()
221 self._register_credentials_v2()
223 def deploy_orchestrator(self): # pylint: disable=too-many-locals
225 Create network, subnet, router
229 self.__logger.info("Deployed Orchestrator")
230 private_net_name = getattr(
231 config.CONF, 'vnf_{}_private_net_name'.format(self.case_name))
232 private_subnet_name = getattr(
233 config.CONF, 'vnf_{}_private_subnet_name'.format(self.case_name))
234 private_subnet_cidr = getattr(
235 config.CONF, 'vnf_{}_private_subnet_cidr'.format(self.case_name))
236 abot_router = getattr(
237 config.CONF, 'vnf_{}_external_router'.format(self.case_name))
239 self.__logger.info("Creating full network ...")
240 subnet_settings = SubnetConfig(
241 name=private_subnet_name, cidr=private_subnet_cidr)
242 network_settings = NetworkConfig(
243 name=private_net_name, subnet_settings=[subnet_settings])
244 network_creator = OpenStackNetwork(self.snaps_creds, network_settings)
245 net_id = network_creator.create().id
246 self.created_object.append(network_creator)
248 ext_net_name = snaps_utils.get_ext_net_name(self.snaps_creds)
249 self.__logger.info("Creating network Router ....")
250 router_creator = OpenStackRouter(
251 self.snaps_creds, RouterConfig(
253 external_gateway=ext_net_name,
254 internal_subnets=[subnet_settings.name]))
255 router_creator.create()
256 self.created_object.append(router_creator)
257 self.__logger.info("Creating Flavor ....")
258 flavor_settings = FlavorConfig(
259 name=self.orchestrator['requirements']['flavor']['name'],
260 ram=self.orchestrator['requirements']['flavor']['ram_min'],
262 flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
263 flavor_creator.create()
264 self.created_object.append(flavor_creator)
265 self.__logger.info("Upload some OS images if it doesn't exist")
266 images = get_config("tenant_images", self.config_file)
267 self.__logger.info("Images needed for vEPC: %s", images)
268 for image_name, image_file in images.iteritems():
269 self.__logger.info("image: %s, file: %s", image_name, image_file)
270 if image_file and image_name:
271 image_creator = OpenStackImage(self.snaps_creds, ImageConfig(
272 name=image_name, image_user='cloud', img_format='qcow2',
273 image_file=image_file))
274 image_id = image_creator.create().id
276 'juju metadata generate-image -d ~ -i {} -s {} -r '
278 image_id, image_name, self.snaps_creds.region_name,
279 self.public_auth_url))
280 self.created_object.append(image_creator)
281 self.__logger.info("Network ID : %s", net_id)
282 juju_bootstrap_command = (
283 'juju bootstrap abot-epc abot-controller --config network={} '
284 '--metadata-source ~ --config ssl-hostname-verification=false '
285 '--constraints mem=2G --bootstrap-series xenial '
286 '--config use-floating-ip=true --debug '
287 '--config use-default-secgroup=true'.format(net_id))
288 os.system(juju_bootstrap_command)
291 def deploy_vnf(self):
292 """Deploy ABOT-OAI-EPC."""
293 self.__logger.info("Upload VNFD")
294 descriptor = self.vnf['descriptor']
295 self.__logger.info("Get or create flavor for all Abot-EPC")
296 flavor_settings = FlavorConfig(
297 name=self.vnf['requirements']['flavor']['name'],
298 ram=self.vnf['requirements']['flavor']['ram_min'],
301 flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
302 flavor_creator.create()
303 self.created_object.append(flavor_creator)
304 self.__logger.info("Deploying Abot-epc bundle file ...")
305 os.system('juju deploy {}'.format('/' + descriptor.get('file_name')))
306 self.__logger.info("Waiting for instances .....")
307 status = os.system('juju-wait')
308 self.__logger.info("juju wait completed: %s", status)
309 self.__logger.info("Deployed Abot-epc on Openstack")
310 nova_client = nova_utils.nova_client(self.snaps_creds)
312 instances = get_instances(nova_client)
313 self.__logger.info("List of Instance: %s", instances)
314 for items in instances:
315 metadata = get_instance_metadata(nova_client, items)
316 if 'juju-units-deployed' in metadata:
317 sec_group = ('juju-' +
318 metadata['juju-controller-uuid'] +
319 '-' + metadata['juju-model-uuid'])
320 self.__logger.info("Instance: %s", sec_group)
322 self.__logger.info("Adding Security group rule....")
323 # This will add sctp rule to a common Security Group Created
324 # by juju and shared to all deployed units.
325 self._add_custom_rule(sec_group)
326 self.__logger.info("Copying the feature files to Abot_node ")
327 os.system('juju scp -- -r {}/featureFiles abot-'
328 'epc-basic/0:~/'.format(self.case_dir))
329 self.__logger.info("Copying the feature files in Abot_node ")
330 os.system("juju ssh abot-epc-basic/0 'sudo rsync -azvv "
331 "~/featureFiles /etc/rebaca-test-suite"
335 epcstatus = os.system('juju status oai-epc | '
336 'grep {} | grep {} | grep {}'
337 .format('EPC', 'is', 'running'))
343 os.system('juju-wait')
348 """Run test on ABoT."""
349 start_time = time.time()
350 self.__logger.info("Running VNF Test cases....")
351 os.system('juju run-action abot-epc-basic/0 run '
352 'tagnames={}'.format(self.details['test_vnf']['tag_name']))
353 os.system('juju-wait')
354 duration = time.time() - start_time
355 self.__logger.info("Getting results from Abot node....")
356 os.system('juju scp abot-epc-basic/0:/var/lib/abot-'
357 'epc-basic/artifacts/TestResults.json {}/.'
358 .format(self.case_dir))
359 self.__logger.info("Parsing the Test results...")
360 res = (process_abot_test_result('{}/TestResults.'
361 'json'.format(self.case_dir)))
362 short_result = sig_test_format(res)
363 self.__logger.info(short_result)
364 self.details['test_vnf'].update(status='PASS',
369 self.__logger.info("Test VNF result: Passed: %d, Failed:"
370 "%d, Skipped: %d", short_result['passed'],
371 short_result['failures'], short_result['skipped'])
375 """Clean created objects/functions."""
377 if not self.orchestrator['requirements']['preserve_setup']:
378 self.__logger.info("Removing deployment files...")
379 testresult = os.path.join(self.case_dir, 'TestResults.json')
380 if os.path.exists(testresult):
381 os.remove(testresult)
382 self.__logger.info("Destroying Orchestrator...")
383 os.system('juju destroy-controller -y abot-controller '
384 '--destroy-all-models')
385 except Exception: # pylint: disable=broad-except
386 self.__logger.warn("Some issue during the undeployment ..")
387 self.__logger.warn("Tenant clean continue ..")
389 if not self.orchestrator['requirements']['preserve_setup']:
390 self.__logger.info('Remove the Abot_epc OS object ..')
391 super(JujuEpc, self).clean()
396 # ----------------------------------------------------------
400 # -----------------------------------------------------------
401 def get_config(parameter, file_path):
403 Returns the value of a given parameter in file.yaml
404 parameter must be given in string format with dots
405 Example: general.openstack.image_name
407 with open(file_path) as config_file:
408 file_yaml = yaml.safe_load(config_file)
411 for element in parameter.split("."):
412 value = value.get(element)
414 raise ValueError("The parameter %s is not defined in"
415 " reporting.yaml" % parameter)
419 def sig_test_format(sig_test):
421 Process the signaling result to have a short result
426 for data_test in sig_test:
427 if data_test['result'] == "passed":
429 elif data_test['result'] == "failed":
431 elif data_test['result'] == "skipped":
433 total_sig_test_result = {}
434 total_sig_test_result['passed'] = nb_passed
435 total_sig_test_result['failures'] = nb_failures
436 total_sig_test_result['skipped'] = nb_skipped
437 return total_sig_test_result
440 def process_abot_test_result(file_path):
441 """ Process ABoT Result """
442 with open(file_path) as test_result:
443 data = json.load(test_result)
446 tests = update_data(tests)
448 flatten_steps = tests['elements'][0].pop('flatten_steps')
449 for steps in flatten_steps:
450 steps['result'] = steps['step_status']
453 logging.error("Could not post data to ElasticSearch host")
458 def update_data(obj):
459 """ Update Result data"""
461 obj['feature_file'] = os.path.splitext(os.path.basename(obj['uri']))[0]
463 for element in obj['elements']:
464 element['final_result'] = "passed"
465 element['flatten_steps'] = []
467 for step in element['steps']:
469 step['result'][step['result']['status']] = 1
470 if step['result']['status'].lower() in ['fail', 'failed']:
471 element['final_result'] = "failed"
473 temp_dict['feature_file'] = obj['feature_file']
474 temp_dict['step_name'] = step['name']
475 temp_dict['step_status'] = step['result']['status']
476 temp_dict['step_duration'] = step['result'].get('duration', 0)
477 temp_dict['step_' + step['result']['status']] = 1
478 element['flatten_steps'].append(deepcopy(temp_dict))
480 # Need to put the tag in OBJ and not ELEMENT
482 element['tags'] = deepcopy(obj['tags'])
483 for tag in obj['tags']:
484 element[tag['name']] = 1
486 for tag in element['tags']:
487 element[tag['name']] = 1
489 except Exception: # pylint: disable=broad-except
490 logging.error("Error in updating data, %s", sys.exc_info()[0])
496 def get_instances(nova_client):
497 """ To get all vm info of a project """
499 instances = nova_client.servers.list()
501 except Exception as exc: # pylint: disable=broad-except
502 logging.error("Error [get_instances(nova_client)]: %s", exc)
506 def get_instance_metadata(nova_client, instance):
507 """ Get instance Metadata - Instance ID """
509 instance = nova_client.servers.get(instance.id)
510 return instance.metadata
511 except Exception as exc: # pylint: disable=broad-except
512 logging.error("Error [get_instance_status(nova_client)]: %s", exc)