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
23 from functest.core import singlevm
24 from functest.utils import config
25 from functest.utils import env
26 from functest.utils import functest_utils
28 __author__ = "Amarendra Meher <amarendra@rebaca.com>"
29 __author__ = "Soumaya K Nayek <soumaya.nayek@rebaca.com>"
31 CLOUD_TEMPLATE = """clouds:
34 auth-types: [userpass]
40 CREDS_TEMPLATE2 = """credentials:
42 default-credential: abot-epc
46 project-domain-name: {project_domain_n}
47 tenant-name: {tenant_n}"""
49 CREDS_TEMPLATE = """credentials:
51 default-credential: abot-epc
55 project-domain-name: {project_domain_n}
56 tenant-name: {tenant_n}
57 user-domain-name: {user_domain_n}
61 class JujuEpc(singlevm.VmReady2):
62 # pylint:disable=too-many-instance-attributes
63 """Abot EPC deployed with JUJU Orchestrator Case"""
65 __logger = logging.getLogger(__name__)
67 cidr = '192.168.120.0/24'
69 filename = ('/home/opnfv/functest/images/'
70 'ubuntu-16.04-server-cloudimg-amd64-disk1.img')
71 filename_alt = ('/home/opnfv/functest/images/'
72 'ubuntu-14.04-server-cloudimg-amd64-disk1.img')
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(
99 requirements=functest_utils.get_parameter_from_yaml(
100 "orchestrator.requirements", self.config_file))
102 self.created_object = []
103 self.details['orchestrator'] = dict(
104 name=functest_utils.get_parameter_from_yaml(
105 "orchestrator.name", self.config_file),
106 version=functest_utils.get_parameter_from_yaml(
107 "orchestrator.version", self.config_file),
113 descriptor=functest_utils.get_parameter_from_yaml(
114 "vnf.descriptor", self.config_file),
115 requirements=functest_utils.get_parameter_from_yaml(
116 "vnf.requirements", self.config_file)
118 self.details['vnf'] = dict(
119 descriptor_version=self.vnf['descriptor']['version'],
120 name=functest_utils.get_parameter_from_yaml(
121 "vnf.name", self.config_file),
122 version=functest_utils.get_parameter_from_yaml(
123 "vnf.version", self.config_file),
125 self.__logger.debug("VNF configuration: %s", self.vnf)
127 self.details['test_vnf'] = dict(
128 name=functest_utils.get_parameter_from_yaml(
129 "vnf_test_suite.name", self.config_file),
130 version=functest_utils.get_parameter_from_yaml(
131 "vnf_test_suite.version", self.config_file),
132 tag_name=functest_utils.get_parameter_from_yaml(
133 "vnf_test_suite.tag_name", self.config_file)
136 self.res_dir = os.path.join(
137 getattr(config.CONF, 'dir_results'), self.case_name)
140 self.public_auth_url = self.get_public_auth_url(self.orig_cloud)
141 if not self.public_auth_url.endswith(('v3', 'v3/')):
142 self.public_auth_url = "{}/v3".format(self.public_auth_url)
143 except Exception: # pylint: disable=broad-except
144 self.public_auth_url = None
146 self.image_alt = None
147 self.flavor_alt = None
149 def check_requirements(self):
150 if not os.path.exists("/src/epc-requirements/go/bin/juju"):
151 self.__logger.warning(
152 "Juju cannot be cross-compiled (arm and arm64) from the time "
154 self.is_skipped = True
156 if env.get('NEW_USER_ROLE').lower() == "admin":
157 self.__logger.warning(
158 "Defining NEW_USER_ROLE=admin will easily break the testcase "
159 "because Juju doesn't manage tenancy (e.g. subnet "
162 def _register_cloud(self):
163 assert self.public_auth_url
164 self.__logger.info("Creating Cloud for Abot-epc .....")
165 clouds_yaml = os.path.join(self.res_dir, "clouds.yaml")
167 'url': self.public_auth_url,
168 'region': self.cloud.region_name if self.cloud.region_name else (
170 with open(clouds_yaml, 'w') as yfile:
171 yfile.write(CLOUD_TEMPLATE.format(**cloud_data))
172 cmd = ['juju', 'add-cloud', 'abot-epc', '-f', clouds_yaml, '--replace']
173 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
174 self.__logger.info("%s\n%s", " ".join(cmd), output.decode("utf-8"))
176 def _register_credentials(self):
177 self.__logger.info("Creating Credentials for Abot-epc .....")
178 credentials_yaml = os.path.join(self.res_dir, "credentials.yaml")
180 'pass': self.project.password,
181 'tenant_n': self.project.project.name,
182 'user_n': self.project.user.name,
183 'project_domain_n': self.cloud.auth.get(
184 "project_domain_name", "Default"),
185 'user_domain_n': self.cloud.auth.get(
186 "user_domain_name", "Default")}
187 with open(credentials_yaml, 'w') as yfile:
188 yfile.write(CREDS_TEMPLATE.format(**creds_data))
189 cmd = ['juju', 'add-credential', 'abot-epc', '-f', credentials_yaml,
191 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
192 self.__logger.info("%s\n%s", " ".join(cmd), output.decode("utf-8"))
195 """Prepare testcase (Additional pre-configuration steps)."""
196 assert self.public_auth_url
197 self.__logger.info("Additional pre-configuration steps")
199 os.makedirs(self.res_dir)
200 except OSError as ex:
201 if ex.errno != errno.EEXIST:
202 self.__logger.exception("Cannot create %s", self.res_dir)
205 self.__logger.info("ENV:\n%s", env.string())
206 self._register_cloud()
207 self._register_credentials()
209 def publish_image(self, name=None):
210 image = super(JujuEpc, self).publish_image(name)
211 cmd = ['juju', 'metadata', 'generate-image', '-d', '/root',
212 '-i', image.id, '-s', 'xenial',
213 '-r', self.cloud.region_name if self.cloud.region_name else (
215 '-u', self.public_auth_url]
216 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
217 self.__logger.info("%s\n%s", " ".join(cmd), output.decode("utf-8"))
220 def publish_image_alt(self, name=None):
221 image_alt = super(JujuEpc, self).publish_image_alt(name)
222 cmd = ['juju', 'metadata', 'generate-image', '-d', '/root',
223 '-i', image_alt.id, '-s', 'trusty',
224 '-r', self.cloud.region_name if self.cloud.region_name else (
226 '-u', self.public_auth_url]
227 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
228 self.__logger.info("%s\n%s", " ".join(cmd), output.decode("utf-8"))
231 def deploy_orchestrator(self): # pylint: disable=too-many-locals
233 Create network, subnet, router
237 self.image_alt = self.publish_image_alt()
238 self.flavor_alt = self.create_flavor_alt()
239 self.__logger.info("Starting Juju Bootstrap process...")
241 cmd = ['timeout', JujuEpc.juju_timeout,
243 'abot-epc/{}'.format(
244 self.cloud.region_name if self.cloud.region_name else (
247 '--agent-version', '2.3.9',
248 '--metadata-source', '/root',
249 '--constraints', 'mem=2G',
250 '--bootstrap-series', 'xenial',
251 '--config', 'network={}'.format(self.network.id),
252 '--config', 'ssl-hostname-verification=false',
253 '--config', 'external-network={}'.format(self.ext_net.id),
254 '--config', 'use-floating-ip=true',
255 '--config', 'use-default-secgroup=true',
257 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
258 self.__logger.info("%s\n%s", " ".join(cmd), output.decode("utf-8"))
259 except subprocess.CalledProcessError as cpe:
261 "Exception with Juju Bootstrap: %s\n%s",
262 cpe.cmd, cpe.output.decode("utf-8"))
264 except Exception: # pylint: disable=broad-except
265 self.__logger.exception("Some issue with Juju Bootstrap ...")
270 def check_app(self, name='abot-epc-basic', status='active'):
271 """Check application status."""
272 cmd = ['juju', 'status', '--format', 'short', name]
274 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
275 self.__logger.info("%s\n%s", " ".join(cmd), output.decode("utf-8"))
277 r'(?=workload:({})\))'.format(status), output.decode("utf-8"))
279 self.__logger.info("%s workload is %s", name, status)
282 "loop %d: %s workload differs from %s", i + 1, name, status)
285 self.__logger.error("%s workload differs from %s", name, status)
289 def deploy_vnf(self):
290 """Deploy ABOT-OAI-EPC."""
291 self.__logger.info("Upload VNFD")
292 descriptor = self.vnf['descriptor']
293 self.__logger.info("Deploying Abot-epc bundle file ...")
294 cmd = ['juju', 'deploy', '{}'.format(descriptor.get('file_name'))]
295 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
296 self.__logger.info("%s\n%s", " ".join(cmd), output.decode("utf-8"))
297 self.__logger.info("Waiting for instances .....")
299 cmd = ['timeout', JujuEpc.juju_timeout, 'juju-wait']
300 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
301 self.__logger.info("%s\n%s", " ".join(cmd), output.decode("utf-8"))
302 self.__logger.info("Deployed Abot-epc on Openstack")
303 except subprocess.CalledProcessError as cpe:
305 "Exception with Juju VNF Deployment: %s\n%s",
306 cpe.cmd, cpe.output.decode("utf-8"))
308 except Exception: # pylint: disable=broad-except
309 self.__logger.exception("Some issue with the VNF Deployment ..")
312 self.__logger.info("Checking status of ABot and EPC units ...")
313 cmd = ['juju', 'status']
314 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
315 self.__logger.debug("%s\n%s", " ".join(cmd), output.decode("utf-8"))
316 for app in ['abot-epc-basic', 'oai-epc', 'oai-hss']:
317 if not self.check_app(app):
320 self.__logger.info("Transferring the feature files to Abot_node ...")
321 cmd = ['timeout', JujuEpc.juju_timeout,
322 'juju', 'scp', '--', '-r', '-v',
323 '{}/featureFiles'.format(self.case_dir), 'abot-epc-basic/0:~/']
324 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
325 self.__logger.info("%s\n%s", " ".join(cmd), output.decode("utf-8"))
327 self.__logger.info("Copying the feature files within Abot_node ")
328 cmd = ['timeout', JujuEpc.juju_timeout,
329 'juju', 'ssh', 'abot-epc-basic/0',
330 'sudo', 'cp', '-vfR', '~/featureFiles/*',
331 '/etc/rebaca-test-suite/featureFiles']
332 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
333 self.__logger.info("%s\n%s", " ".join(cmd), output.decode("utf-8"))
337 """Run test on ABoT."""
338 start_time = time.time()
339 self.__logger.info("Running VNF Test cases....")
340 cmd = ['juju', 'run-action', 'abot-epc-basic/0', 'run',
341 'tagnames={}'.format(self.details['test_vnf']['tag_name'])]
342 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
343 self.__logger.info("%s\n%s", " ".join(cmd), output.decode("utf-8"))
345 cmd = ['timeout', JujuEpc.juju_timeout, 'juju-wait']
346 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
347 self.__logger.info("%s\n%s", " ".join(cmd), output.decode("utf-8"))
349 duration = time.time() - start_time
350 self.__logger.info("Getting results from Abot node....")
351 cmd = ['timeout', JujuEpc.juju_timeout,
352 'juju', 'scp', '--', '-v',
354 '/var/lib/abot-epc-basic/artifacts/TestResults.json',
355 '{}/.'.format(self.res_dir)]
356 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
357 self.__logger.info("%s\n%s", " ".join(cmd), output.decode("utf-8"))
358 self.__logger.info("Parsing the Test results...")
359 res = (process_abot_test_result('{}/TestResults.json'.format(
361 short_result = sig_test_format(res)
362 self.__logger.info(short_result)
363 self.details['test_vnf'].update(
364 status='PASS', result=short_result, full_result=res,
367 "Test VNF result: Passed: %d, Failed:%d, Skipped: %d",
368 short_result['passed'],
369 short_result['failures'], short_result['skipped'])
372 def run(self, **kwargs):
373 self.start_time = time.time()
375 assert super(JujuEpc, self).run(**kwargs) == self.EX_OK
377 if (self.deploy_orchestrator() and
378 self.deploy_vnf() and
380 self.stop_time = time.time()
384 self.stop_time = time.time()
385 return self.EX_TESTCASE_FAILED
386 except Exception: # pylint: disable=broad-except
388 self.stop_time = time.time()
389 self.__logger.exception("Exception on VNF testing")
390 return self.EX_TESTCASE_FAILED
393 """Clean created objects/functions."""
395 cmd = ['juju', 'debug-log', '--replay', '--no-tail']
396 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
398 "%s\n%s", " ".join(cmd), output.decode("utf-8"))
399 self.__logger.info("Destroying Orchestrator...")
400 cmd = ['timeout', JujuEpc.juju_timeout,
401 'juju', 'destroy-controller', '-y', 'abot-controller',
402 '--destroy-all-models']
403 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
404 self.__logger.info("%s\n%s", " ".join(cmd), output.decode("utf-8"))
405 except subprocess.CalledProcessError as cpe:
407 "Exception with Juju Cleanup: %s\n%s",
408 cpe.cmd, cpe.output.decode("utf-8"))
409 except Exception: # pylint: disable=broad-except
410 self.__logger.exception("General issue during the undeployment ..")
411 for fip in self.cloud.list_floating_ips():
412 self.cloud.delete_floating_ip(fip.id)
414 self.cloud.delete_image(self.image_alt)
416 self.orig_cloud.delete_flavor(self.flavor_alt.id)
417 super(JujuEpc, self).clean()
420 def sig_test_format(sig_test):
422 Process the signaling result to have a short result
427 for data_test in sig_test:
428 if data_test['result'] == "passed":
430 elif data_test['result'] == "failed":
432 elif data_test['result'] == "skipped":
434 total_sig_test_result = {}
435 total_sig_test_result['passed'] = nb_passed
436 total_sig_test_result['failures'] = nb_failures
437 total_sig_test_result['skipped'] = nb_skipped
438 return total_sig_test_result
441 def process_abot_test_result(file_path):
442 """ Process ABoT Result """
443 with open(file_path) as test_result:
444 data = json.load(test_result)
447 tests = update_data(tests)
449 flatten_steps = tests['elements'][0].pop('flatten_steps')
450 for steps in flatten_steps:
451 steps['result'] = steps['step_status']
453 except Exception: # pylint: disable=broad-except
454 logging.error("Could not post data to ElasticSearch host")
459 def update_data(obj):
460 """ Update Result data"""
462 obj['feature_file'] = os.path.splitext(os.path.basename(obj['uri']))[0]
464 for element in obj['elements']:
465 element['final_result'] = "passed"
466 element['flatten_steps'] = []
468 for step in element['steps']:
470 step['result'][step['result']['status']] = 1
471 if step['result']['status'].lower() in ['fail', 'failed']:
472 element['final_result'] = "failed"
474 temp_dict['feature_file'] = obj['feature_file']
475 temp_dict['step_name'] = step['name']
476 temp_dict['step_status'] = step['result']['status']
477 temp_dict['step_duration'] = step['result'].get('duration', 0)
478 temp_dict['step_' + step['result']['status']] = 1
479 element['flatten_steps'].append(deepcopy(temp_dict))
481 # Need to put the tag in OBJ and not ELEMENT
483 element['tags'] = deepcopy(obj['tags'])
484 for tag in obj['tags']:
485 element[tag['name']] = 1
487 for tag in element['tags']:
488 element[tag['name']] = 1
490 except Exception: # pylint: disable=broad-except
491 logging.error("Error in updating data, %s", sys.exc_info()[0])