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
25 from functest.core import singlevm
26 from functest.utils import config
27 from functest.utils import env
29 __author__ = "Amarendra Meher <amarendra@rebaca.com>"
30 __author__ = "Soumaya K Nayek <soumaya.nayek@rebaca.com>"
32 CLOUD_TEMPLATE = """clouds:
35 auth-types: [userpass]
41 CREDS_TEMPLATE2 = """credentials:
43 default-credential: abot-epc
47 project-domain-name: {project_domain_n}
48 tenant-name: {tenant_n}"""
50 CREDS_TEMPLATE = """credentials:
52 default-credential: abot-epc
56 project-domain-name: {project_domain_n}
57 tenant-name: {tenant_n}
58 user-domain-name: {user_domain_n}
62 class JujuEpc(singlevm.VmReady2):
63 # pylint:disable=too-many-instance-attributes
64 """Abot EPC deployed with JUJU Orchestrator Case"""
66 __logger = logging.getLogger(__name__)
68 cidr = '192.168.121.0/24'
70 filename = ('/home/opnfv/functest/images/'
71 'ubuntu-16.04-server-cloudimg-amd64-disk1.img')
72 filename_alt = ('/home/opnfv/functest/images/'
73 'ubuntu-14.04-server-cloudimg-amd64-disk1.img')
85 def __init__(self, **kwargs):
86 if "case_name" not in kwargs:
87 kwargs["case_name"] = "juju_epc"
88 super(JujuEpc, self).__init__(**kwargs)
90 # Retrieve the configuration
91 self.case_dir = pkg_resources.resource_filename(
92 'functest', 'opnfv_tests/vnf/epc')
94 self.config = getattr(
95 config.CONF, 'vnf_{}_config'.format(self.case_name))
97 raise Exception("VNF config file not found")
98 self.config_file = os.path.join(self.case_dir, self.config)
99 self.orchestrator = dict(requirements=get_config(
100 "orchestrator.requirements", self.config_file))
102 self.created_object = []
103 self.details['orchestrator'] = dict(
104 name=get_config("orchestrator.name", self.config_file),
105 version=get_config("orchestrator.version", self.config_file),
111 descriptor=get_config("vnf.descriptor", self.config_file),
112 requirements=get_config("vnf.requirements", self.config_file)
114 self.details['vnf'] = dict(
115 descriptor_version=self.vnf['descriptor']['version'],
116 name=get_config("vnf.name", self.config_file),
117 version=get_config("vnf.version", self.config_file),
119 self.__logger.debug("VNF configuration: %s", self.vnf)
121 self.details['test_vnf'] = dict(
122 name=get_config("vnf_test_suite.name", self.config_file),
123 version=get_config("vnf_test_suite.version", self.config_file),
124 tag_name=get_config("vnf_test_suite.tag_name", self.config_file)
127 self.res_dir = os.path.join(
128 getattr(config.CONF, 'dir_results'), self.case_name)
131 self.public_auth_url = self.get_public_auth_url(self.orig_cloud)
132 if not self.public_auth_url.endswith(('v3', 'v3/')):
133 self.public_auth_url = six.moves.urllib.parse.urljoin(
134 self.public_auth_url, 'v3')
135 except Exception: # pylint: disable=broad-except
136 self.public_auth_url = None
138 self.image_alt = None
139 self.flavor_alt = None
141 def check_requirements(self):
142 if env.get('NEW_USER_ROLE').lower() == "admin":
144 "Defining NEW_USER_ROLE=admin will easily break the testcase "
145 "because Juju doesn't manage tenancy (e.g. subnet "
148 def _register_cloud(self):
149 assert self.public_auth_url
150 self.__logger.info("Creating Cloud for Abot-epc .....")
151 clouds_yaml = os.path.join(self.res_dir, "clouds.yaml")
153 'url': self.public_auth_url,
154 'region': self.cloud.region_name if self.cloud.region_name else (
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, stderr=subprocess.STDOUT)
160 self.__logger.info("%s\n%s", " ".join(cmd), output)
162 def _register_credentials(self):
163 self.__logger.info("Creating Credentials for Abot-epc .....")
164 credentials_yaml = os.path.join(self.res_dir, "credentials.yaml")
166 'pass': self.project.password,
167 'tenant_n': self.project.project.name,
168 'user_n': self.project.user.name,
169 'project_domain_n': self.cloud.auth.get(
170 "project_domain_name", "Default"),
171 'user_domain_n': self.cloud.auth.get(
172 "user_domain_name", "Default")}
173 with open(credentials_yaml, 'w') as yfile:
174 yfile.write(CREDS_TEMPLATE.format(**creds_data))
175 cmd = ['juju', 'add-credential', 'abot-epc', '-f', credentials_yaml,
177 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
178 self.__logger.info("%s\n%s", " ".join(cmd), output)
181 """Prepare testcase (Additional pre-configuration steps)."""
182 assert self.public_auth_url
183 self.__logger.info("Additional pre-configuration steps")
185 os.makedirs(self.res_dir)
186 except OSError as ex:
187 if ex.errno != errno.EEXIST:
188 self.__logger.exception("Cannot create %s", self.res_dir)
191 self.__logger.info("ENV:\n%s", env.string())
192 self._register_cloud()
193 self._register_credentials()
195 def publish_image(self, name=None):
196 image = super(JujuEpc, self).publish_image(name)
197 cmd = ['juju', 'metadata', 'generate-image', '-d', '/root',
198 '-i', image.id, '-s', 'xenial',
199 '-r', self.cloud.region_name if self.cloud.region_name else (
201 '-u', self.public_auth_url]
202 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
203 self.__logger.info("%s\n%s", " ".join(cmd), output)
206 def publish_image_alt(self, name=None):
207 image_alt = super(JujuEpc, self).publish_image_alt(name)
208 cmd = ['juju', 'metadata', 'generate-image', '-d', '/root',
209 '-i', image_alt.id, '-s', 'trusty',
210 '-r', self.cloud.region_name if self.cloud.region_name else (
212 '-u', self.public_auth_url]
213 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
214 self.__logger.info("%s\n%s", " ".join(cmd), output)
217 def deploy_orchestrator(self): # pylint: disable=too-many-locals
219 Create network, subnet, router
223 self.image_alt = self.publish_image_alt()
224 self.flavor_alt = self.create_flavor_alt()
225 self.__logger.info("Starting Juju Bootstrap process...")
227 cmd = ['timeout', '-t', JujuEpc.juju_timeout,
229 'abot-epc/{}'.format(
230 self.cloud.region_name if self.cloud.region_name else (
233 '--agent-version', '2.2.9',
234 '--metadata-source', '/root',
235 '--constraints', 'mem=2G',
236 '--bootstrap-series', 'xenial',
237 '--config', 'network={}'.format(self.network.id),
238 '--config', 'ssl-hostname-verification=false',
239 '--config', 'use-floating-ip=true',
240 '--config', 'use-default-secgroup=true',
242 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
243 self.__logger.info("%s\n%s", " ".join(cmd), output)
244 except subprocess.CalledProcessError as cpe:
246 "Exception with Juju Bootstrap: %s\n%s",
249 except Exception: # pylint: disable=broad-except
250 self.__logger.exception("Some issue with Juju Bootstrap ...")
255 def check_app(self, name='abot-epc-basic', status='active'):
256 """Check application status."""
257 cmd = ['juju', 'status', '--format', 'short', name]
258 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
259 self.__logger.info("%s\n%s", " ".join(cmd), output)
260 ret = re.search(r'(?=workload:({})\))'.format(status), output)
262 self.__logger.info("%s workload is %s", name, status)
264 self.__logger.error("%s workload differs from %s", name, status)
267 def deploy_vnf(self):
268 """Deploy ABOT-OAI-EPC."""
269 self.__logger.info("Upload VNFD")
270 descriptor = self.vnf['descriptor']
271 self.__logger.info("Deploying Abot-epc bundle file ...")
272 cmd = ['juju', 'deploy', '{}'.format(descriptor.get('file_name'))]
273 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
274 self.__logger.info("%s\n%s", " ".join(cmd), output)
275 self.__logger.info("Waiting for instances .....")
277 cmd = ['timeout', '-t', JujuEpc.juju_timeout, 'juju-wait']
278 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
279 self.__logger.info("%s\n%s", " ".join(cmd), output)
280 self.__logger.info("Deployed Abot-epc on Openstack")
281 except subprocess.CalledProcessError as cpe:
283 "Exception with Juju VNF Deployment: %s\n%s",
286 except Exception: # pylint: disable=broad-except
287 self.__logger.exception("Some issue with the VNF Deployment ..")
290 self.__logger.info("Checking status of ABot and EPC units ...")
291 cmd = ['juju', 'status']
292 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
293 self.__logger.debug("%s\n%s", " ".join(cmd), output)
294 for app in ['abot-epc-basic', 'oai-epc', 'oai-hss']:
295 if not self.check_app(app):
298 self.__logger.info("Transferring the feature files to Abot_node ...")
299 cmd = ['timeout', '-t', JujuEpc.juju_timeout,
300 'juju', 'scp', '--', '-r', '-v',
301 '{}/featureFiles'.format(self.case_dir), 'abot-epc-basic/0:~/']
302 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
303 self.__logger.info("%s\n%s", " ".join(cmd), output)
305 self.__logger.info("Copying the feature files within Abot_node ")
306 cmd = ['timeout', '-t', JujuEpc.juju_timeout,
307 'juju', 'ssh', 'abot-epc-basic/0',
308 'sudo', 'cp', '-vfR', '~/featureFiles/*',
309 '/etc/rebaca-test-suite/featureFiles']
310 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
311 self.__logger.info("%s\n%s", " ".join(cmd), output)
315 """Run test on ABoT."""
316 start_time = time.time()
317 self.__logger.info("Running VNF Test cases....")
318 cmd = ['juju', 'run-action', 'abot-epc-basic/0', 'run',
319 'tagnames={}'.format(self.details['test_vnf']['tag_name'])]
320 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
321 self.__logger.info("%s\n%s", " ".join(cmd), output)
323 cmd = ['timeout', '-t', JujuEpc.juju_timeout, 'juju-wait']
324 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
325 self.__logger.info("%s\n%s", " ".join(cmd), output)
327 duration = time.time() - start_time
328 self.__logger.info("Getting results from Abot node....")
329 cmd = ['timeout', '-t', JujuEpc.juju_timeout,
330 'juju', 'scp', '--', '-v',
332 '/var/lib/abot-epc-basic/artifacts/TestResults.json',
333 '{}/.'.format(self.res_dir)]
334 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
335 self.__logger.info("%s\n%s", " ".join(cmd), output)
336 self.__logger.info("Parsing the Test results...")
337 res = (process_abot_test_result('{}/TestResults.json'.format(
339 short_result = sig_test_format(res)
340 self.__logger.info(short_result)
341 self.details['test_vnf'].update(
342 status='PASS', result=short_result, full_result=res,
345 "Test VNF result: Passed: %d, Failed:%d, Skipped: %d",
346 short_result['passed'],
347 short_result['failures'], short_result['skipped'])
350 def run(self, **kwargs):
351 self.start_time = time.time()
353 assert super(JujuEpc, self).run(**kwargs) == self.EX_OK
355 if (self.deploy_orchestrator() and
356 self.deploy_vnf() and
358 self.stop_time = time.time()
362 self.stop_time = time.time()
363 return self.EX_TESTCASE_FAILED
364 except Exception: # pylint: disable=broad-except
365 self.stop_time = time.time()
366 self.__logger.exception("Exception on VNF testing")
367 return self.EX_TESTCASE_FAILED
370 """Clean created objects/functions."""
372 cmd = ['juju', 'debug-log', '--replay', '--no-tail']
373 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
374 self.__logger.debug("%s\n%s", " ".join(cmd), output)
375 self.__logger.info("Destroying Orchestrator...")
376 cmd = ['timeout', '-t', JujuEpc.juju_timeout,
377 'juju', 'destroy-controller', '-y', 'abot-controller',
378 '--destroy-all-models']
379 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
380 self.__logger.info("%s\n%s", " ".join(cmd), output)
381 except subprocess.CalledProcessError as cpe:
383 "Exception with Juju Cleanup: %s\n%s",
385 except Exception: # pylint: disable=broad-except
386 self.__logger.exception("General issue during the undeployment ..")
387 for fip in self.cloud.list_floating_ips():
388 self.cloud.delete_floating_ip(fip.id)
390 self.cloud.delete_image(self.image_alt)
392 self.orig_cloud.delete_flavor(self.flavor_alt.id)
393 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']
452 except Exception: # pylint: disable=broad-except
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])