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
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.SingleVm2):
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')
83 def __init__(self, **kwargs):
84 if "case_name" not in kwargs:
85 kwargs["case_name"] = "juju_epc"
86 super().__init__(**kwargs)
88 # Retrieve the configuration
89 self.case_dir = pkg_resources.resource_filename(
90 'functest', 'opnfv_tests/vnf/epc')
92 self.config = getattr(
93 config.CONF, f'vnf_{self.case_name}_config')
94 except Exception as exc:
95 raise Exception("VNF config file not found") from exc
96 self.config_file = os.path.join(self.case_dir, self.config)
97 self.orchestrator = dict(
98 requirements=functest_utils.get_parameter_from_yaml(
99 "orchestrator.requirements", self.config_file))
101 self.created_object = []
102 self.details['orchestrator'] = dict(
103 name=functest_utils.get_parameter_from_yaml(
104 "orchestrator.name", self.config_file),
105 version=functest_utils.get_parameter_from_yaml(
106 "orchestrator.version", self.config_file),
112 descriptor=functest_utils.get_parameter_from_yaml(
113 "vnf.descriptor", self.config_file),
114 requirements=functest_utils.get_parameter_from_yaml(
115 "vnf.requirements", self.config_file)
117 self.details['vnf'] = dict(
118 descriptor_version=self.vnf['descriptor']['version'],
119 name=functest_utils.get_parameter_from_yaml(
120 "vnf.name", self.config_file),
121 version=functest_utils.get_parameter_from_yaml(
122 "vnf.version", self.config_file),
124 self.__logger.debug("VNF configuration: %s", self.vnf)
126 self.details['test_vnf'] = dict(
127 name=functest_utils.get_parameter_from_yaml(
128 "vnf_test_suite.name", self.config_file),
129 version=functest_utils.get_parameter_from_yaml(
130 "vnf_test_suite.version", self.config_file),
131 tag_name=functest_utils.get_parameter_from_yaml(
132 "vnf_test_suite.tag_name", self.config_file)
135 self.res_dir = os.path.join(
136 getattr(config.CONF, 'dir_results'), self.case_name)
139 self.public_auth_url = self.get_public_auth_url(self.orig_cloud)
140 if not self.public_auth_url.endswith(('v3', 'v3/')):
141 self.public_auth_url = f"{self.public_auth_url}/v3"
142 except Exception: # pylint: disable=broad-except
143 self.public_auth_url = None
145 self.image_alt = None
146 self.flavor_alt = None
148 def _install_juju(self):
149 (_, stdout, stderr) = self.ssh.exec_command(
150 'sudo snap install juju --channel=2.3/stable --classic')
151 self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
152 self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
153 return not stdout.channel.recv_exit_status()
155 def _install_juju_wait(self):
156 (_, stdout, stderr) = self.ssh.exec_command(
157 'sudo apt-get update && sudo apt-get install python3-pip -y && '
158 'sudo pip3 install juju_wait===2.6.4')
159 self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
160 self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
161 return not stdout.channel.recv_exit_status()
163 def _register_cloud(self):
164 assert self.public_auth_url
165 self.__logger.info("Creating Cloud for Abot-epc .....")
166 clouds_yaml = os.path.join(self.res_dir, "clouds.yaml")
168 'url': self.public_auth_url,
169 'region': self.cloud.region_name if self.cloud.region_name else (
171 with open(clouds_yaml, 'w', encoding='utf-8') as yfile:
172 yfile.write(CLOUD_TEMPLATE.format(**cloud_data))
173 scpc = scp.SCPClient(self.ssh.get_transport())
174 scpc.put(clouds_yaml, remote_path='~/')
175 (_, stdout, stderr) = self.ssh.exec_command(
176 '/snap/bin/juju add-cloud abot-epc -f clouds.yaml --replace')
177 self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
178 self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
179 return not stdout.channel.recv_exit_status()
181 def _register_credentials(self):
182 self.__logger.info("Creating Credentials for Abot-epc .....")
183 credentials_yaml = os.path.join(self.res_dir, "credentials.yaml")
185 'pass': self.project.password,
186 'tenant_n': self.project.project.name,
187 'user_n': self.project.user.name,
188 'project_domain_n': self.cloud.auth.get(
189 "project_domain_name", "Default"),
190 'user_domain_n': self.cloud.auth.get(
191 "user_domain_name", "Default")}
192 with open(credentials_yaml, 'w', encoding='utf-8') as yfile:
193 yfile.write(CREDS_TEMPLATE.format(**creds_data))
194 scpc = scp.SCPClient(self.ssh.get_transport())
195 scpc.put(credentials_yaml, remote_path='~/')
196 (_, stdout, stderr) = self.ssh.exec_command(
197 '/snap/bin/juju add-credential abot-epc -f credentials.yaml '
198 ' --replace --debug')
199 self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
200 self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
201 return not stdout.channel.recv_exit_status()
203 def _publish_image(self):
204 region_name = self.cloud.region_name if self.cloud.region_name else (
206 (_, stdout, stderr) = self.ssh.exec_command(
207 '/snap/bin/juju metadata generate-image -d /home/ubuntu '
208 f'-i {self.image.id} -s xenial -r {region_name} '
209 f'-u {self.public_auth_url}')
210 self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
211 self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
212 return not stdout.channel.recv_exit_status()
214 def publish_image_alt(self, name=None):
215 image_alt = super().publish_image_alt(name)
216 region_name = self.cloud.region_name if self.cloud.region_name else (
218 (_, stdout, stderr) = self.ssh.exec_command(
219 '/snap/bin/juju metadata generate-image -d /home/ubuntu '
220 f'-i {image_alt.id} -s trusty -r {region_name} '
221 f'-u {self.public_auth_url}')
222 self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
223 self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
226 def deploy_orchestrator(self): # pylint: disable=too-many-locals
228 Create network, subnet, router
232 self._publish_image()
233 self.image_alt = self.publish_image_alt()
234 self.flavor_alt = self.create_flavor_alt()
235 self.__logger.info("Starting Juju Bootstrap process...")
236 region_name = self.cloud.region_name if self.cloud.region_name else (
238 (_, stdout, stderr) = self.ssh.exec_command(
239 f'timeout {JujuEpc.juju_timeout} '
240 f'/snap/bin/juju bootstrap abot-epc/{region_name} abot-controller '
241 '--agent-version 2.3.9 --metadata-source /home/ubuntu '
242 '--constraints mem=2G --bootstrap-series xenial '
243 f'--config network={self.network.id} '
244 '--config ssl-hostname-verification=false '
245 f'--config external-network={self.ext_net.id} '
246 '--config use-floating-ip=true '
247 '--config use-default-secgroup=true '
249 self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
250 self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
251 return not stdout.channel.recv_exit_status()
253 def check_app(self, name='abot-epc-basic', status='active'):
254 """Check application status."""
256 (_, stdout, stderr) = self.ssh.exec_command(
257 f'/snap/bin/juju status --format short {name}')
258 output = stdout.read().decode("utf-8")
259 self.__logger.debug("stdout:\n%s", output)
260 self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
261 if stdout.channel.recv_exit_status():
264 rf'(?=workload:({status})\))', output)
266 self.__logger.info("%s workload is %s", name, status)
269 "loop %d: %s workload differs from %s", i + 1, name, status)
272 self.__logger.error("%s workload differs from %s", name, status)
276 def deploy_vnf(self):
277 """Deploy ABOT-OAI-EPC."""
278 self.__logger.info("Upload VNFD")
279 scpc = scp.SCPClient(self.ssh.get_transport())
281 '/src/epc-requirements/abot_charm', remote_path='~/',
283 self.__logger.info("Deploying Abot-epc bundle file ...")
284 (_, stdout, stderr) = self.ssh.exec_command(
285 'sudo mkdir -p /src/epc-requirements && '
286 'sudo mv abot_charm /src/epc-requirements/abot_charm && '
287 '/snap/bin/juju deploy '
288 '/src/epc-requirements/abot_charm/functest-abot-epc-bundle/'
290 self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
291 self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
292 if stdout.channel.recv_exit_status():
293 return not stdout.channel.recv_exit_status()
294 (_, stdout, stderr) = self.ssh.exec_command(
295 'PATH=/snap/bin/:$PATH '
296 f'timeout {JujuEpc.juju_timeout} juju-wait')
297 self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
298 self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
299 if stdout.channel.recv_exit_status():
300 return not stdout.channel.recv_exit_status()
301 self.__logger.info("Checking status of ABot and EPC units ...")
302 (_, stdout, stderr) = self.ssh.exec_command('/snap/bin/juju status')
303 output = stdout.read().decode("utf-8")
304 self.__logger.debug("stdout:\n%s", output)
305 self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
306 if stdout.channel.recv_exit_status():
307 return not stdout.channel.recv_exit_status()
308 for app in ['abot-epc-basic', 'oai-epc', 'oai-hss']:
309 if not self.check_app(app):
311 scpc = scp.SCPClient(self.ssh.get_transport())
313 f'{self.case_dir}/featureFiles', remote_path='~/',
315 (_, stdout, stderr) = self.ssh.exec_command(
316 f'timeout {JujuEpc.juju_timeout} /snap/bin/juju scp -- -r -v '
317 '~/featureFiles abot-epc-basic/0:/etc/rebaca-test-suite/')
318 output = stdout.read().decode("utf-8")
319 self.__logger.debug("stdout:\n%s", output)
320 self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
321 return not stdout.channel.recv_exit_status()
324 """Run test on ABoT."""
325 start_time = time.time()
326 (_, stdout, stderr) = self.ssh.exec_command(
327 "/snap/bin/juju run-action abot-epc-basic/0 "
328 f"run tagnames={self.details['test_vnf']['tag_name']}")
329 self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
330 self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
331 if stdout.channel.recv_exit_status():
332 return not stdout.channel.recv_exit_status()
333 (_, stdout, stderr) = self.ssh.exec_command(
334 'PATH=/snap/bin/:$PATH '
335 f'timeout {JujuEpc.juju_timeout} juju-wait')
336 self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
337 self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
338 if stdout.channel.recv_exit_status():
339 return not stdout.channel.recv_exit_status()
340 duration = time.time() - start_time
341 self.__logger.info("Getting results from Abot node....")
342 (_, stdout, stderr) = self.ssh.exec_command(
343 f'timeout {JujuEpc.juju_timeout} /snap/bin/juju scp '
344 '-- -v abot-epc-basic/0:'
345 '/var/lib/abot-epc-basic/artifacts/TestResults.json .')
346 self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
347 self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
348 if stdout.channel.recv_exit_status():
349 return not stdout.channel.recv_exit_status()
350 scpc = scp.SCPClient(self.ssh.get_transport())
351 scpc.get('TestResults.json', self.res_dir)
352 self.__logger.info("Parsing the Test results...")
353 res = process_abot_test_result(f'{self.res_dir}/TestResults.json')
354 short_result = sig_test_format(res)
355 self.__logger.info(short_result)
356 self.details['test_vnf'].update(
357 status='PASS', result=short_result, full_result=res,
360 "Test VNF result: Passed: %d, Failed:%d, Skipped: %d",
361 short_result['passed'],
362 short_result['failures'], short_result['skipped'])
366 """Prepare testcase (Additional pre-configuration steps)."""
367 assert self.public_auth_url
368 self.__logger.info("Additional pre-configuration steps")
370 os.makedirs(self.res_dir)
371 except OSError as ex:
372 if ex.errno != errno.EEXIST:
373 self.__logger.exception("Cannot create %s", self.res_dir)
374 raise Exception from ex
375 self.__logger.info("ENV:\n%s", env.string())
377 assert self._install_juju()
378 assert self._install_juju_wait()
379 assert self._register_cloud()
380 assert self._register_credentials()
381 assert self.deploy_orchestrator()
382 assert self.deploy_vnf()
383 assert self.test_vnf()
384 except Exception: # pylint: disable=broad-except
385 self.__logger.exception("juju_epc failed")
390 """Clean created objects/functions."""
391 (_, stdout, stderr) = self.ssh.exec_command(
392 '/snap/bin/juju debug-log --replay --no-tail')
393 self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
394 self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
395 (_, stdout, stderr) = self.ssh.exec_command(
396 '/snap/bin/juju destroy-controller -y abot-controller '
397 '--destroy-all-models')
398 self.__logger.debug("stdout:\n%s", stdout.read().decode("utf-8"))
399 self.__logger.debug("stderr:\n%s", stderr.read().decode("utf-8"))
400 for fip in self.cloud.list_floating_ips():
401 self.cloud.delete_floating_ip(fip.id)
403 self.cloud.delete_image(self.image_alt)
405 self.orig_cloud.delete_flavor(self.flavor_alt.id)
409 def sig_test_format(sig_test):
411 Process the signaling result to have a short result
416 for data_test in sig_test:
417 if data_test['result'] == "passed":
419 elif data_test['result'] == "failed":
421 elif data_test['result'] == "skipped":
423 total_sig_test_result = {}
424 total_sig_test_result['passed'] = nb_passed
425 total_sig_test_result['failures'] = nb_failures
426 total_sig_test_result['skipped'] = nb_skipped
427 return total_sig_test_result
430 def process_abot_test_result(file_path):
431 """ Process ABoT Result """
432 with open(file_path, encoding='utf-8') as test_result:
433 data = json.load(test_result)
436 tests = update_data(tests)
438 flatten_steps = tests['elements'][0].pop('flatten_steps')
439 for steps in flatten_steps:
440 steps['result'] = steps['step_status']
442 except Exception: # pylint: disable=broad-except
443 logging.error("Could not post data to ElasticSearch host")
448 def update_data(obj):
449 """ Update Result data"""
451 obj['feature_file'] = os.path.splitext(os.path.basename(obj['uri']))[0]
453 for element in obj['elements']:
454 element['final_result'] = "passed"
455 element['flatten_steps'] = []
457 for step in element['steps']:
459 step['result'][step['result']['status']] = 1
460 if step['result']['status'].lower() in ['fail', 'failed']:
461 element['final_result'] = "failed"
463 temp_dict['feature_file'] = obj['feature_file']
464 temp_dict['step_name'] = step['name']
465 temp_dict['step_status'] = step['result']['status']
466 temp_dict['step_duration'] = step['result'].get('duration', 0)
467 temp_dict['step_' + step['result']['status']] = 1
468 element['flatten_steps'].append(deepcopy(temp_dict))
470 # Need to put the tag in OBJ and not ELEMENT
472 element['tags'] = deepcopy(obj['tags'])
473 for tag in obj['tags']:
474 element[tag['name']] = 1
476 for tag in element['tags']:
477 element[tag['name']] = 1
479 except Exception: # pylint: disable=broad-except
480 logging.error("Error in updating data, %s", sys.exc_info()[0])