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")
147 'url': self.public_auth_url,
148 'region': self.snaps_creds.region_name}
149 with open(clouds_yaml, 'w') as yfile:
150 yfile.write(CLOUD_TEMPLATE.format(**cloud_data))
151 cmd = ['juju', 'add-cloud', 'abot-epc', '-f', clouds_yaml, '--replace']
152 output = subprocess.check_output(cmd)
153 self.__logger.info("%s\n%s", " ".join(cmd), output)
155 def _register_credentials_v2(self):
156 self.__logger.info("Creating Credentials for Abot-epc .....")
157 user_creator = self._bypass_juju_network_discovery_bug(
158 'juju_network_discovery_bug')
159 snaps_creds = user_creator.get_os_creds(self.snaps_creds.project_name)
160 self.__logger.debug("snaps creds: %s", snaps_creds)
161 credentials_yaml = os.path.join(self.res_dir, "credentials.yaml")
163 'pass': snaps_creds.password,
164 'tenant_n': snaps_creds.project_name,
165 'user_n': snaps_creds.username}
166 with open(credentials_yaml, 'w') as yfile:
167 yfile.write(CREDS_TEMPLATE2.format(**creds_data))
168 cmd = ['juju', 'add-credential', 'abot-epc', '-f', credentials_yaml,
170 output = subprocess.check_output(cmd)
171 self.__logger.info("%s\n%s", " ".join(cmd), output)
173 def _register_credentials_v3(self):
174 self.__logger.info("Creating Credentials for Abot-epc .....")
175 user_creator = self._bypass_juju_network_discovery_bug(
176 'juju_network_discovery_bug')
177 snaps_creds = user_creator.get_os_creds(self.snaps_creds.project_name)
178 self.__logger.debug("snaps creds: %s", snaps_creds)
179 credentials_yaml = os.path.join(self.res_dir, "credentials.yaml")
181 'pass': snaps_creds.password,
182 'tenant_n': snaps_creds.project_name,
183 'user_n': snaps_creds.username,
184 'project_domain_n': snaps_creds.project_domain_name,
185 'user_domain_n': snaps_creds.user_domain_name}
186 with open(credentials_yaml, 'w') as yfile:
187 yfile.write(CREDS_TEMPLATE3.format(**creds_data))
188 cmd = ['juju', 'add-credential', 'abot-epc', '-f', credentials_yaml,
190 output = subprocess.check_output(cmd)
191 self.__logger.info("%s\n%s", " ".join(cmd), output)
193 def _add_custom_rule(self, sec_grp_name):
194 """ To add custom rule for SCTP Traffic """
195 sec_grp_rules = list()
196 sec_grp_rules.append(
197 SecurityGroupRuleConfig(
198 sec_grp_name=sec_grp_name, direction=Direction.ingress,
199 protocol=Protocol.sctp))
200 security_group = OpenStackSecurityGroup(
204 rule_settings=sec_grp_rules))
205 security_group.create()
206 self.created_object.append(security_group)
209 """Prepare testcase (Additional pre-configuration steps)."""
210 self.__logger.info("Additional pre-configuration steps")
211 super(JujuEpc, self).prepare()
213 os.makedirs(self.res_dir)
214 except OSError as ex:
215 if ex.errno != errno.EEXIST:
216 self.__logger.exception("Cannot create %s", self.res_dir)
217 raise vnf.VnfPreparationException
218 self.public_auth_url = keystone_utils.get_endpoint(
219 self.snaps_creds, 'identity')
220 # it enforces a versioned public identity endpoint as juju simply
221 # adds /auth/tokens wich fails vs an unversioned endpoint.
222 if not self.public_auth_url.endswith(('v3', 'v3/', 'v2.0', 'v2.0/')):
223 self.public_auth_url = urljoin(self.public_auth_url, 'v3')
224 self._register_cloud()
225 if self.snaps_creds.identity_api_version == 3:
226 self._register_credentials_v3()
228 self._register_credentials_v2()
230 def deploy_orchestrator(self): # pylint: disable=too-many-locals
232 Create network, subnet, router
236 self.__logger.info("Deployed Orchestrator")
237 private_net_name = getattr(
238 config.CONF, 'vnf_{}_private_net_name'.format(self.case_name))
239 private_subnet_name = '{}-{}'.format(
241 'vnf_{}_private_subnet_name'.format(self.case_name)),
243 private_subnet_cidr = getattr(
244 config.CONF, 'vnf_{}_private_subnet_cidr'.format(self.case_name))
245 abot_router = '{}-{}'.format(
247 'vnf_{}_external_router'.format(self.case_name)),
249 self.__logger.info("Creating full network ...")
250 subnet_settings = SubnetConfig(
251 name=private_subnet_name,
252 cidr=private_subnet_cidr,
253 dns_nameservers=[env.get('NAMESERVER')])
254 network_settings = NetworkConfig(
255 name=private_net_name, subnet_settings=[subnet_settings])
256 network_creator = OpenStackNetwork(self.snaps_creds, network_settings)
257 net_id = network_creator.create().id
258 self.created_object.append(network_creator)
260 ext_net_name = snaps_utils.get_ext_net_name(self.snaps_creds)
261 self.__logger.info("Creating network Router ....")
262 router_creator = OpenStackRouter(
263 self.snaps_creds, RouterConfig(
265 external_gateway=ext_net_name,
266 internal_subnets=[subnet_settings.name]))
267 router_creator.create()
268 self.created_object.append(router_creator)
269 self.__logger.info("Creating Flavor ....")
270 flavor_settings = FlavorConfig(
271 name=self.orchestrator['requirements']['flavor']['name'],
272 ram=self.orchestrator['requirements']['flavor']['ram_min'],
274 flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
275 flavor_creator.create()
276 self.created_object.append(flavor_creator)
277 self.__logger.info("Upload some OS images if it doesn't exist")
278 images = get_config("tenant_images", self.config_file)
279 self.__logger.info("Images needed for vEPC: %s", images)
280 for image_name, image_file in images.iteritems():
281 self.__logger.info("image: %s, file: %s", image_name, image_file)
282 if image_file and image_name:
283 image_creator = OpenStackImage(self.snaps_creds, ImageConfig(
284 name=image_name, image_user='cloud', img_format='qcow2',
285 image_file=image_file))
286 image_id = image_creator.create().id
287 cmd = ['juju', 'metadata', 'generate-image', '-d', '~',
288 '-i', image_id, '-s', image_name,
289 '-r', self.snaps_creds.region_name,
290 '-u', self.public_auth_url]
291 output = subprocess.check_output(cmd)
292 self.__logger.info("%s\n%s", " ".join(cmd), output)
293 self.created_object.append(image_creator)
294 self.__logger.info("Network ID : %s", net_id)
295 juju_bootstrap_command = (
296 'juju bootstrap abot-epc abot-controller --config network={} '
297 '--metadata-source ~ --config ssl-hostname-verification=false '
298 '--constraints mem=2G --bootstrap-series xenial '
299 '--config use-floating-ip=true --debug '
300 '--config use-default-secgroup=true'.format(net_id))
301 if os.system(juju_bootstrap_command) != 0:
305 def deploy_vnf(self):
306 """Deploy ABOT-OAI-EPC."""
307 self.__logger.info("Upload VNFD")
308 descriptor = self.vnf['descriptor']
309 self.__logger.info("Get or create flavor for all Abot-EPC")
310 flavor_settings = FlavorConfig(
311 name=self.vnf['requirements']['flavor']['name'],
312 ram=self.vnf['requirements']['flavor']['ram_min'],
315 flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
316 flavor_creator.create()
317 self.created_object.append(flavor_creator)
318 self.__logger.info("Deploying Abot-epc bundle file ...")
319 os.system('juju deploy {}'.format('/' + descriptor.get('file_name')))
320 self.__logger.info("Waiting for instances .....")
321 status = os.system('juju-wait')
322 self.__logger.info("juju wait completed: %s", status)
323 self.__logger.info("Deployed Abot-epc on Openstack")
324 nova_client = nova_utils.nova_client(self.snaps_creds)
326 instances = get_instances(nova_client)
327 self.__logger.info("List of Instance: %s", instances)
328 for items in instances:
329 metadata = get_instance_metadata(nova_client, items)
330 if 'juju-units-deployed' in metadata:
331 sec_group = ('juju-' +
332 metadata['juju-controller-uuid'] +
333 '-' + metadata['juju-model-uuid'])
334 self.__logger.info("Instance: %s", sec_group)
336 self.__logger.info("Adding Security group rule....")
337 # This will add sctp rule to a common Security Group Created
338 # by juju and shared to all deployed units.
339 self._add_custom_rule(sec_group)
340 self.__logger.info("Copying the feature files to Abot_node ")
341 os.system('juju scp -- -r {}/featureFiles abot-'
342 'epc-basic/0:~/'.format(self.case_dir))
343 self.__logger.info("Copying the feature files in Abot_node ")
344 os.system("juju ssh abot-epc-basic/0 'sudo rsync -azvv "
345 "~/featureFiles /etc/rebaca-test-suite"
349 epcstatus = os.system('juju status oai-epc | '
350 'grep {} | grep {} | grep {}'
351 .format('EPC', 'is', 'running'))
357 os.system('juju-wait')
362 """Run test on ABoT."""
363 start_time = time.time()
364 self.__logger.info("Running VNF Test cases....")
365 os.system('juju run-action abot-epc-basic/0 run '
366 'tagnames={}'.format(self.details['test_vnf']['tag_name']))
367 os.system('juju-wait')
368 duration = time.time() - start_time
369 self.__logger.info("Getting results from Abot node....")
370 os.system('juju scp abot-epc-basic/0:/var/lib/abot-'
371 'epc-basic/artifacts/TestResults.json {}/.'
372 .format(self.res_dir))
373 self.__logger.info("Parsing the Test results...")
374 res = (process_abot_test_result('{}/TestResults.json'.format(
376 short_result = sig_test_format(res)
377 self.__logger.info(short_result)
378 self.details['test_vnf'].update(status='PASS',
383 self.__logger.info("Test VNF result: Passed: %d, Failed:"
384 "%d, Skipped: %d", short_result['passed'],
385 short_result['failures'], short_result['skipped'])
389 """Clean created objects/functions."""
391 if not self.orchestrator['requirements']['preserve_setup']:
392 self.__logger.info("Destroying Orchestrator...")
393 os.system('juju destroy-controller -y abot-controller '
394 '--destroy-all-models')
395 except Exception: # pylint: disable=broad-except
396 self.__logger.warn("Some issue during the undeployment ..")
397 self.__logger.warn("Tenant clean continue ..")
399 if not self.orchestrator['requirements']['preserve_setup']:
400 self.__logger.info('Remove the Abot_epc OS object ..')
401 super(JujuEpc, self).clean()
406 # ----------------------------------------------------------
410 # -----------------------------------------------------------
411 def get_config(parameter, file_path):
413 Returns the value of a given parameter in file.yaml
414 parameter must be given in string format with dots
415 Example: general.openstack.image_name
417 with open(file_path) as config_file:
418 file_yaml = yaml.safe_load(config_file)
421 for element in parameter.split("."):
422 value = value.get(element)
424 raise ValueError("The parameter %s is not defined in"
425 " reporting.yaml" % parameter)
429 def sig_test_format(sig_test):
431 Process the signaling result to have a short result
436 for data_test in sig_test:
437 if data_test['result'] == "passed":
439 elif data_test['result'] == "failed":
441 elif data_test['result'] == "skipped":
443 total_sig_test_result = {}
444 total_sig_test_result['passed'] = nb_passed
445 total_sig_test_result['failures'] = nb_failures
446 total_sig_test_result['skipped'] = nb_skipped
447 return total_sig_test_result
450 def process_abot_test_result(file_path):
451 """ Process ABoT Result """
452 with open(file_path) as test_result:
453 data = json.load(test_result)
456 tests = update_data(tests)
458 flatten_steps = tests['elements'][0].pop('flatten_steps')
459 for steps in flatten_steps:
460 steps['result'] = steps['step_status']
463 logging.error("Could not post data to ElasticSearch host")
468 def update_data(obj):
469 """ Update Result data"""
471 obj['feature_file'] = os.path.splitext(os.path.basename(obj['uri']))[0]
473 for element in obj['elements']:
474 element['final_result'] = "passed"
475 element['flatten_steps'] = []
477 for step in element['steps']:
479 step['result'][step['result']['status']] = 1
480 if step['result']['status'].lower() in ['fail', 'failed']:
481 element['final_result'] = "failed"
483 temp_dict['feature_file'] = obj['feature_file']
484 temp_dict['step_name'] = step['name']
485 temp_dict['step_status'] = step['result']['status']
486 temp_dict['step_duration'] = step['result'].get('duration', 0)
487 temp_dict['step_' + step['result']['status']] = 1
488 element['flatten_steps'].append(deepcopy(temp_dict))
490 # Need to put the tag in OBJ and not ELEMENT
492 element['tags'] = deepcopy(obj['tags'])
493 for tag in obj['tags']:
494 element[tag['name']] = 1
496 for tag in element['tags']:
497 element[tag['name']] = 1
499 except Exception: # pylint: disable=broad-except
500 logging.error("Error in updating data, %s", sys.exc_info()[0])
506 def get_instances(nova_client):
507 """ To get all vm info of a project """
509 instances = nova_client.servers.list()
511 except Exception as exc: # pylint: disable=broad-except
512 logging.error("Error [get_instances(nova_client)]: %s", exc)
516 def get_instance_metadata(nova_client, instance):
517 """ Get instance Metadata - Instance ID """
519 instance = nova_client.servers.get(instance.id)
520 return instance.metadata
521 except Exception as exc: # pylint: disable=broad-except
522 logging.error("Error [get_instance_status(nova_client)]: %s", exc)