Define a new env var for volume device name
[functest.git] / functest / opnfv_tests / vnf / ims / cloudify_ims.py
1 #!/usr/bin/env python
2
3 # Copyright (c) 2017 Orange and others.
4 #
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
10 """CloudifyIms testcase implementation."""
11
12 import logging
13 import os
14 import time
15
16 from cloudify_rest_client import CloudifyClient
17 from cloudify_rest_client.executions import Execution
18 from scp import SCPClient
19 import yaml
20
21 from snaps.config.flavor import FlavorConfig
22 from snaps.config.image import ImageConfig
23 from snaps.config.keypair import KeypairConfig
24 from snaps.config.network import NetworkConfig, PortConfig, SubnetConfig
25 from snaps.config.router import RouterConfig
26 from snaps.config.security_group import (
27     Direction, Protocol, SecurityGroupConfig, SecurityGroupRuleConfig)
28 from snaps.config.vm_inst import FloatingIpConfig, VmInstanceConfig
29 from snaps.openstack.create_flavor import OpenStackFlavor
30 from snaps.openstack.create_image import OpenStackImage
31 from snaps.openstack.create_instance import OpenStackVmInstance
32 from snaps.openstack.create_keypairs import OpenStackKeypair
33 from snaps.openstack.create_network import OpenStackNetwork
34 from snaps.openstack.create_router import OpenStackRouter
35 from snaps.openstack.create_security_group import OpenStackSecurityGroup
36 from snaps.openstack.utils import keystone_utils
37 from xtesting.energy import energy
38
39 from functest.opnfv_tests.openstack.snaps import snaps_utils
40 import functest.opnfv_tests.vnf.ims.clearwater_ims_base as clearwater_ims_base
41 from functest.utils import config
42
43 __author__ = "Valentin Boucher <valentin.boucher@orange.com>"
44
45
46 class CloudifyIms(clearwater_ims_base.ClearwaterOnBoardingBase):
47     """Clearwater vIMS deployed with Cloudify Orchestrator Case."""
48
49     __logger = logging.getLogger(__name__)
50
51     def __init__(self, **kwargs):
52         """Initialize CloudifyIms testcase object."""
53         if "case_name" not in kwargs:
54             kwargs["case_name"] = "cloudify_ims"
55         super(CloudifyIms, self).__init__(**kwargs)
56
57         # Retrieve the configuration
58         try:
59             self.config = getattr(
60                 config.CONF, 'vnf_{}_config'.format(self.case_name))
61         except Exception:
62             raise Exception("VNF config file not found")
63
64         config_file = os.path.join(self.case_dir, self.config)
65         self.orchestrator = dict(
66             requirements=get_config("orchestrator.requirements", config_file),
67         )
68         self.details['orchestrator'] = dict(
69             name=get_config("orchestrator.name", config_file),
70             version=get_config("orchestrator.version", config_file),
71             status='ERROR',
72             result=''
73         )
74         self.__logger.debug("Orchestrator configuration %s", self.orchestrator)
75         self.vnf = dict(
76             descriptor=get_config("vnf.descriptor", config_file),
77             inputs=get_config("vnf.inputs", config_file),
78             requirements=get_config("vnf.requirements", config_file)
79         )
80         self.details['vnf'] = dict(
81             descriptor_version=self.vnf['descriptor']['version'],
82             name=get_config("vnf.name", config_file),
83             version=get_config("vnf.version", config_file),
84         )
85         self.__logger.debug("VNF configuration: %s", self.vnf)
86
87         self.details['test_vnf'] = dict(
88             name=get_config("vnf_test_suite.name", config_file),
89             version=get_config("vnf_test_suite.version", config_file)
90         )
91         self.images = get_config("tenant_images", config_file)
92         self.__logger.info("Images needed for vIMS: %s", self.images)
93
94     def prepare(self):
95         """Prepare testscase (Additional pre-configuration steps)."""
96         super(CloudifyIms, self).prepare()
97
98         self.__logger.info("Additional pre-configuration steps")
99
100         compute_quotas = self.os_project.get_compute_quotas()
101         network_quotas = self.os_project.get_network_quotas()
102
103         for key, value in (
104                 self.vnf['requirements']['compute_quotas'].items()):
105             setattr(compute_quotas, key, value)
106
107         for key, value in (
108                 self.vnf['requirements']['network_quotas'].items()):
109             setattr(network_quotas, key, value)
110
111         compute_quotas = self.os_project.update_compute_quotas(compute_quotas)
112         network_quotas = self.os_project.update_network_quotas(network_quotas)
113
114         # needs some images
115         self.__logger.info("Upload some OS images if it doesn't exist")
116         for image_name, image_file in self.images.iteritems():
117             self.__logger.info("image: %s, file: %s", image_name, image_file)
118             if image_file and image_name:
119                 image_creator = OpenStackImage(
120                     self.snaps_creds,
121                     ImageConfig(
122                         name=image_name, image_user='cloud',
123                         img_format='qcow2', image_file=image_file))
124                 image_creator.create()
125                 self.created_object.append(image_creator)
126
127     def deploy_orchestrator(self):
128         """
129         Deploy Cloudify Manager.
130
131         network, security group, fip, VM creation
132         """
133         # network creation
134
135         start_time = time.time()
136         self.__logger.info("Creating keypair ...")
137         kp_file = os.path.join(self.data_dir, "cloudify_ims.pem")
138         keypair_settings = KeypairConfig(
139             name='cloudify_ims_kp-{}'.format(self.uuid),
140             private_filepath=kp_file)
141         keypair_creator = OpenStackKeypair(self.snaps_creds, keypair_settings)
142         keypair_creator.create()
143         self.created_object.append(keypair_creator)
144
145         self.__logger.info("Creating full network ...")
146         subnet_settings = SubnetConfig(
147             name='cloudify_ims_subnet-{}'.format(self.uuid),
148             cidr='10.67.79.0/24')
149         network_settings = NetworkConfig(
150             name='cloudify_ims_network-{}'.format(self.uuid),
151             subnet_settings=[subnet_settings])
152         network_creator = OpenStackNetwork(self.snaps_creds, network_settings)
153         network_creator.create()
154         self.created_object.append(network_creator)
155         ext_net_name = snaps_utils.get_ext_net_name(self.snaps_creds)
156         router_creator = OpenStackRouter(
157             self.snaps_creds,
158             RouterConfig(
159                 name='cloudify_ims_router-{}'.format(self.uuid),
160                 external_gateway=ext_net_name,
161                 internal_subnets=[subnet_settings.name]))
162         router_creator.create()
163         self.created_object.append(router_creator)
164
165         # security group creation
166         self.__logger.info("Creating security group for cloudify manager vm")
167         sg_rules = list()
168         sg_rules.append(
169             SecurityGroupRuleConfig(
170                 sec_grp_name="sg-cloudify-manager-{}".format(self.uuid),
171                 direction=Direction.ingress, protocol=Protocol.tcp,
172                 port_range_min=1, port_range_max=65535))
173         sg_rules.append(
174             SecurityGroupRuleConfig(
175                 sec_grp_name="sg-cloudify-manager-{}".format(self.uuid),
176                 direction=Direction.ingress, protocol=Protocol.udp,
177                 port_range_min=1, port_range_max=65535))
178
179         security_group_creator = OpenStackSecurityGroup(
180             self.snaps_creds,
181             SecurityGroupConfig(
182                 name="sg-cloudify-manager-{}".format(self.uuid),
183                 rule_settings=sg_rules))
184
185         security_group_creator.create()
186         self.created_object.append(security_group_creator)
187
188         # orchestrator VM flavor
189         self.__logger.info("Get or create flavor for cloudify manager vm ...")
190
191         flavor_settings = FlavorConfig(
192             name=self.orchestrator['requirements']['flavor']['name'],
193             ram=self.orchestrator['requirements']['flavor']['ram_min'],
194             disk=50,
195             vcpus=2)
196         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
197         flavor_creator.create()
198         self.created_object.append(flavor_creator)
199         image_settings = ImageConfig(
200             name=self.orchestrator['requirements']['os_image'],
201             image_user='centos',
202             exists=True)
203
204         port_settings = PortConfig(
205             name='cloudify_manager_port-{}'.format(self.uuid),
206             network_name=network_settings.name)
207
208         manager_settings = VmInstanceConfig(
209             name='cloudify_manager-{}'.format(self.uuid),
210             flavor=flavor_settings.name,
211             port_settings=[port_settings],
212             security_group_names=[
213                 security_group_creator.sec_grp_settings.name],
214             floating_ip_settings=[FloatingIpConfig(
215                 name='cloudify_manager_fip-{}'.format(self.uuid),
216                 port_name=port_settings.name,
217                 router_name=router_creator.router_settings.name)])
218
219         manager_creator = OpenStackVmInstance(
220             self.snaps_creds, manager_settings, image_settings,
221             keypair_settings)
222
223         self.__logger.info("Creating cloudify manager VM")
224         manager_creator.create()
225         self.created_object.append(manager_creator)
226
227         public_auth_url = keystone_utils.get_endpoint(
228             self.snaps_creds, 'identity')
229
230         cfy_creds = dict(
231             keystone_username=self.snaps_creds.username,
232             keystone_password=self.snaps_creds.password,
233             keystone_tenant_name=self.snaps_creds.project_name,
234             keystone_url=public_auth_url,
235             region=self.snaps_creds.region_name,
236             user_domain_name=self.snaps_creds.user_domain_name,
237             project_domain_name=self.snaps_creds.project_domain_name)
238         self.__logger.info("Set creds for cloudify manager %s", cfy_creds)
239
240         cfy_client = CloudifyClient(
241             host=manager_creator.get_floating_ip().ip,
242             username='admin', password='admin', tenant='default_tenant')
243
244         self.orchestrator['object'] = cfy_client
245
246         self.__logger.info("Attemps running status of the Manager")
247         cfy_status = None
248         retry = 10
249         while str(cfy_status) != 'running' and retry:
250             try:
251                 cfy_status = cfy_client.manager.get_status()['status']
252                 self.__logger.debug("The current manager status is %s",
253                                     cfy_status)
254             except Exception:  # pylint: disable=broad-except
255                 self.__logger.exception(
256                     "Cloudify Manager isn't up and running. Retrying ...")
257             retry = retry - 1
258             time.sleep(30)
259
260         if str(cfy_status) == 'running':
261             self.__logger.info("Cloudify Manager is up and running")
262         else:
263             raise Exception("Cloudify Manager isn't up and running")
264
265         self.__logger.info("Put OpenStack creds in manager")
266         secrets_list = cfy_client.secrets.list()
267         for k, val in cfy_creds.iteritems():
268             if not any(d.get('key', None) == k for d in secrets_list):
269                 cfy_client.secrets.create(k, val)
270             else:
271                 cfy_client.secrets.update(k, val)
272
273         duration = time.time() - start_time
274
275         self.__logger.info("Put private keypair in manager")
276         if manager_creator.vm_ssh_active(block=True):
277             ssh = manager_creator.ssh_client()
278             scp = SCPClient(ssh.get_transport(), socket_timeout=15.0)
279             scp.put(kp_file, '~/')
280             cmd = "sudo cp ~/cloudify_ims.pem /etc/cloudify/"
281             self.run_blocking_ssh_command(ssh, cmd)
282             cmd = "sudo chmod 444 /etc/cloudify/cloudify_ims.pem"
283             self.run_blocking_ssh_command(ssh, cmd)
284             cmd = "sudo yum install -y gcc python-devel"
285             self.run_blocking_ssh_command(
286                 ssh, cmd, "Unable to install packages on manager")
287
288         self.details['orchestrator'].update(status='PASS', duration=duration)
289
290         self.vnf['inputs'].update(dict(
291             external_network_name=ext_net_name,
292             network_name=network_settings.name,
293             key_pair_name=keypair_settings.name
294         ))
295         self.result = 1/3 * 100
296         return True
297
298     def deploy_vnf(self):
299         """Deploy Clearwater IMS."""
300         start_time = time.time()
301
302         self.__logger.info("Upload VNFD")
303         cfy_client = self.orchestrator['object']
304         descriptor = self.vnf['descriptor']
305         cfy_client.blueprints.publish_archive(descriptor.get('url'),
306                                               descriptor.get('name'),
307                                               descriptor.get('file_name'))
308
309         self.__logger.info("Get or create flavor for all clearwater vm")
310         flavor_settings = FlavorConfig(
311             name=self.vnf['requirements']['flavor']['name'],
312             ram=self.vnf['requirements']['flavor']['ram_min'],
313             disk=25,
314             vcpus=1)
315         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
316         flavor_creator.create()
317         self.created_object.append(flavor_creator)
318
319         self.vnf['inputs'].update(dict(
320             flavor_id=self.vnf['requirements']['flavor']['name'],
321         ))
322
323         self.__logger.info("Create VNF Instance")
324         cfy_client.deployments.create(descriptor.get('name'),
325                                       descriptor.get('name'),
326                                       self.vnf.get('inputs'))
327
328         wait_for_execution(cfy_client,
329                            _get_deployment_environment_creation_execution(
330                                cfy_client, descriptor.get('name')),
331                            self.__logger,
332                            timeout=300)
333
334         self.__logger.info("Start the VNF Instance deployment")
335         execution = cfy_client.executions.start(descriptor.get('name'),
336                                                 'install')
337         # Show execution log
338         execution = wait_for_execution(cfy_client, execution, self.__logger)
339
340         duration = time.time() - start_time
341
342         self.__logger.info(execution)
343         if execution.status == 'terminated':
344             self.details['vnf'].update(status='PASS', duration=duration)
345             self.result += 1/3 * 100
346             result = True
347         else:
348             self.details['vnf'].update(status='FAIL', duration=duration)
349             result = False
350         return result
351
352     def test_vnf(self):
353         """Run test on clearwater ims instance."""
354         start_time = time.time()
355
356         cfy_client = self.orchestrator['object']
357
358         outputs = cfy_client.deployments.outputs.get(
359             self.vnf['descriptor'].get('name'))['outputs']
360         dns_ip = outputs['dns_ip']
361         ellis_ip = outputs['ellis_ip']
362         self.config_ellis(ellis_ip)
363
364         if not dns_ip:
365             return False
366
367         vims_test_result = self.run_clearwater_live_test(
368             dns_ip=dns_ip,
369             public_domain=self.vnf['inputs']["public_domain"])
370         duration = time.time() - start_time
371         short_result, nb_test = sig_test_format(vims_test_result)
372         self.__logger.info(short_result)
373         self.details['test_vnf'].update(result=short_result,
374                                         full_result=vims_test_result,
375                                         duration=duration)
376         try:
377             vnf_test_rate = short_result['passed'] / nb_test
378             # orchestrator + vnf + test_vnf
379             self.result += vnf_test_rate / 3 * 100
380         except ZeroDivisionError:
381             self.__logger.error("No test has been executed")
382             self.details['test_vnf'].update(status='FAIL')
383             return False
384
385         return True
386
387     def clean(self):
388         """Clean created objects/functions."""
389         try:
390             cfy_client = self.orchestrator['object']
391             dep_name = self.vnf['descriptor'].get('name')
392             # kill existing execution
393             self.__logger.info('Deleting the current deployment')
394             exec_list = cfy_client.executions.list(dep_name)
395             for execution in exec_list:
396                 if execution['status'] == "started":
397                     try:
398                         cfy_client.executions.cancel(execution['id'],
399                                                      force=True)
400                     except Exception:  # pylint: disable=broad-except
401                         self.__logger.warn("Can't cancel the current exec")
402
403             execution = cfy_client.executions.start(
404                 dep_name,
405                 'uninstall',
406                 parameters=dict(ignore_failure=True),
407                 force=True)
408
409             wait_for_execution(cfy_client, execution, self.__logger)
410             cfy_client.deployments.delete(self.vnf['descriptor'].get('name'))
411             cfy_client.blueprints.delete(self.vnf['descriptor'].get('name'))
412         except Exception:  # pylint: disable=broad-except
413             self.__logger.warn("Some issue during the undeployment ..")
414             self.__logger.warn("Tenant clean continue ..")
415
416         super(CloudifyIms, self).clean()
417
418     @staticmethod
419     def run_blocking_ssh_command(ssh, cmd,
420                                  error_msg="Unable to run this command"):
421         """Command to run ssh command with the exit status."""
422         _, stdout, stderr = ssh.exec_command(cmd)
423         CloudifyIms.__logger.debug("SSH %s stdout: %s", cmd, stdout.read())
424         if stdout.channel.recv_exit_status() != 0:
425             CloudifyIms.__logger.error("SSH %s stderr: %s", cmd, stderr.read())
426             raise Exception(error_msg)
427
428     @energy.enable_recording
429     def run(self, **kwargs):
430         """Execute CloudifyIms test case."""
431         return super(CloudifyIms, self).run(**kwargs)
432
433
434 # ----------------------------------------------------------
435 #
436 #               YAML UTILS
437 #
438 # -----------------------------------------------------------
439 def get_config(parameter, file_path):
440     """
441     Get config parameter.
442
443     Returns the value of a given parameter in file.yaml
444     parameter must be given in string format with dots
445     Example: general.openstack.image_name
446     """
447     with open(file_path) as config_file:
448         file_yaml = yaml.safe_load(config_file)
449     config_file.close()
450     value = file_yaml
451     for element in parameter.split("."):
452         value = value.get(element)
453         if value is None:
454             raise ValueError("The parameter %s is not defined in"
455                              " reporting.yaml" % parameter)
456     return value
457
458
459 def wait_for_execution(client, execution, logger, timeout=1500, ):
460     """Wait for a workflow execution on Cloudify Manager."""
461     # if execution already ended - return without waiting
462     if execution.status in Execution.END_STATES:
463         return execution
464
465     if timeout is not None:
466         deadline = time.time() + timeout
467
468     # Poll for execution status and execution logs, until execution ends
469     # and we receive an event of type in WORKFLOW_END_TYPES
470     offset = 0
471     batch_size = 50
472     event_list = []
473     execution_ended = False
474     while True:
475         event_list = client.events.list(
476             execution_id=execution.id,
477             _offset=offset,
478             _size=batch_size,
479             include_logs=False,
480             sort='@timestamp').items
481
482         offset = offset + len(event_list)
483         for event in event_list:
484             logger.debug(event.get('message'))
485
486         if timeout is not None:
487             if time.time() > deadline:
488                 raise RuntimeError(
489                     'execution of operation {0} for deployment {1} '
490                     'timed out'.format(execution.workflow_id,
491                                        execution.deployment_id))
492             else:
493                 # update the remaining timeout
494                 timeout = deadline - time.time()
495
496         if not execution_ended:
497             execution = client.executions.get(execution.id)
498             execution_ended = execution.status in Execution.END_STATES
499
500         if execution_ended:
501             break
502
503         time.sleep(5)
504
505     return execution
506
507
508 def _get_deployment_environment_creation_execution(client, deployment_id):
509     """
510     Get the execution id of a env preparation.
511
512     network, security group, fip, VM creation
513     """
514     executions = client.executions.list(deployment_id=deployment_id)
515     for execution in executions:
516         if execution.workflow_id == 'create_deployment_environment':
517             return execution
518     raise RuntimeError('Failed to get create_deployment_environment '
519                        'workflow execution.'
520                        'Available executions: {0}'.format(executions))
521
522
523 def sig_test_format(sig_test):
524     """Process the signaling result to have a short result."""
525     nb_passed = 0
526     nb_failures = 0
527     nb_skipped = 0
528     for data_test in sig_test:
529         if data_test['result'] == "Passed":
530             nb_passed += 1
531         elif data_test['result'] == "Failed":
532             nb_failures += 1
533         elif data_test['result'] == "Skipped":
534             nb_skipped += 1
535     short_sig_test_result = {}
536     short_sig_test_result['passed'] = nb_passed
537     short_sig_test_result['failures'] = nb_failures
538     short_sig_test_result['skipped'] = nb_skipped
539     nb_test = nb_passed + nb_skipped
540     return (short_sig_test_result, nb_test)