39bbb76787e100205c7e30d86f503678a0a3743f
[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 functest.energy import energy
22 from functest.opnfv_tests.openstack.snaps import snaps_utils
23 import functest.opnfv_tests.vnf.ims.clearwater_ims_base as clearwater_ims_base
24 from functest.utils import config
25
26 from snaps.config.flavor import FlavorConfig
27 from snaps.config.image import ImageConfig
28 from snaps.config.keypair import KeypairConfig
29 from snaps.config.network import NetworkConfig, PortConfig, SubnetConfig
30 from snaps.config.router import RouterConfig
31 from snaps.config.security_group import (
32     Direction, Protocol, SecurityGroupConfig, SecurityGroupRuleConfig)
33 from snaps.config.vm_inst import FloatingIpConfig, VmInstanceConfig
34
35 from snaps.openstack.create_flavor import OpenStackFlavor
36 from snaps.openstack.create_image import OpenStackImage
37 from snaps.openstack.create_instance import OpenStackVmInstance
38 from snaps.openstack.create_keypairs import OpenStackKeypair
39 from snaps.openstack.create_network import OpenStackNetwork
40 from snaps.openstack.create_router import OpenStackRouter
41 from snaps.openstack.create_security_group import OpenStackSecurityGroup
42 from snaps.openstack.utils import keystone_utils
43
44
45 __author__ = "Valentin Boucher <valentin.boucher@orange.com>"
46
47
48 class CloudifyIms(clearwater_ims_base.ClearwaterOnBoardingBase):
49     """Clearwater vIMS deployed with Cloudify Orchestrator Case."""
50
51     __logger = logging.getLogger(__name__)
52
53     def __init__(self, **kwargs):
54         """Initialize CloudifyIms testcase object."""
55         if "case_name" not in kwargs:
56             kwargs["case_name"] = "cloudify_ims"
57         super(CloudifyIms, self).__init__(**kwargs)
58
59         # Retrieve the configuration
60         try:
61             self.config = getattr(
62                 config.CONF, 'vnf_{}_config'.format(self.case_name))
63         except Exception:
64             raise Exception("VNF config file not found")
65
66         config_file = os.path.join(self.case_dir, self.config)
67         self.orchestrator = dict(
68             requirements=get_config("orchestrator.requirements", config_file),
69         )
70         self.details['orchestrator'] = dict(
71             name=get_config("orchestrator.name", config_file),
72             version=get_config("orchestrator.version", config_file),
73             status='ERROR',
74             result=''
75         )
76         self.__logger.debug("Orchestrator configuration %s", self.orchestrator)
77         self.vnf = dict(
78             descriptor=get_config("vnf.descriptor", config_file),
79             inputs=get_config("vnf.inputs", config_file),
80             requirements=get_config("vnf.requirements", config_file)
81         )
82         self.details['vnf'] = dict(
83             descriptor_version=self.vnf['descriptor']['version'],
84             name=get_config("vnf.name", config_file),
85             version=get_config("vnf.version", config_file),
86         )
87         self.__logger.debug("VNF configuration: %s", self.vnf)
88
89         self.details['test_vnf'] = dict(
90             name=get_config("vnf_test_suite.name", config_file),
91             version=get_config("vnf_test_suite.version", config_file)
92         )
93         self.images = get_config("tenant_images", config_file)
94         self.__logger.info("Images needed for vIMS: %s", self.images)
95
96     def prepare(self):
97         """Prepare testscase (Additional pre-configuration steps)."""
98         super(CloudifyIms, self).prepare()
99
100         self.__logger.info("Additional pre-configuration steps")
101
102         compute_quotas = self.os_project.get_compute_quotas()
103         network_quotas = self.os_project.get_network_quotas()
104
105         for key, value in (
106                 self.vnf['requirements']['compute_quotas'].items()):
107             setattr(compute_quotas, key, value)
108
109         for key, value in (
110                 self.vnf['requirements']['network_quotas'].items()):
111             setattr(network_quotas, key, value)
112
113         compute_quotas = self.os_project.update_compute_quotas(compute_quotas)
114         network_quotas = self.os_project.update_network_quotas(network_quotas)
115
116         # needs some images
117         self.__logger.info("Upload some OS images if it doesn't exist")
118         for image_name, image_file in self.images.iteritems():
119             self.__logger.info("image: %s, file: %s", image_name, image_file)
120             if image_file and image_name:
121                 image_creator = OpenStackImage(
122                     self.snaps_creds,
123                     ImageConfig(
124                         name=image_name, image_user='cloud',
125                         img_format='qcow2', image_file=image_file))
126                 image_creator.create()
127                 self.created_object.append(image_creator)
128
129     def deploy_orchestrator(self):
130         """
131         Deploy Cloudify Manager.
132
133         network, security group, fip, VM creation
134         """
135         # network creation
136
137         start_time = time.time()
138         self.__logger.info("Creating keypair ...")
139         kp_file = os.path.join(self.data_dir, "cloudify_ims.pem")
140         keypair_settings = KeypairConfig(
141             name='cloudify_ims_kp-{}'.format(self.uuid),
142             private_filepath=kp_file)
143         keypair_creator = OpenStackKeypair(self.snaps_creds, keypair_settings)
144         keypair_creator.create()
145         self.created_object.append(keypair_creator)
146
147         self.__logger.info("Creating full network ...")
148         subnet_settings = SubnetConfig(
149             name='cloudify_ims_subnet-{}'.format(self.uuid),
150             cidr='10.67.79.0/24')
151         network_settings = NetworkConfig(
152             name='cloudify_ims_network-{}'.format(self.uuid),
153             subnet_settings=[subnet_settings])
154         network_creator = OpenStackNetwork(self.snaps_creds, network_settings)
155         network_creator.create()
156         self.created_object.append(network_creator)
157         ext_net_name = snaps_utils.get_ext_net_name(self.snaps_creds)
158         router_creator = OpenStackRouter(
159             self.snaps_creds,
160             RouterConfig(
161                 name='cloudify_ims_router-{}'.format(self.uuid),
162                 external_gateway=ext_net_name,
163                 internal_subnets=[subnet_settings.name]))
164         router_creator.create()
165         self.created_object.append(router_creator)
166
167         # security group creation
168         self.__logger.info("Creating security group for cloudify manager vm")
169         sg_rules = list()
170         sg_rules.append(
171             SecurityGroupRuleConfig(
172                 sec_grp_name="sg-cloudify-manager-{}".format(self.uuid),
173                 direction=Direction.ingress, protocol=Protocol.tcp,
174                 port_range_min=1, port_range_max=65535))
175         sg_rules.append(
176             SecurityGroupRuleConfig(
177                 sec_grp_name="sg-cloudify-manager-{}".format(self.uuid),
178                 direction=Direction.ingress, protocol=Protocol.udp,
179                 port_range_min=1, port_range_max=65535))
180
181         security_group_creator = OpenStackSecurityGroup(
182             self.snaps_creds,
183             SecurityGroupConfig(
184                 name="sg-cloudify-manager-{}".format(self.uuid),
185                 rule_settings=sg_rules))
186
187         security_group_creator.create()
188         self.created_object.append(security_group_creator)
189
190         # orchestrator VM flavor
191         self.__logger.info("Get or create flavor for cloudify manager vm ...")
192
193         flavor_settings = FlavorConfig(
194             name=self.orchestrator['requirements']['flavor']['name'],
195             ram=self.orchestrator['requirements']['flavor']['ram_min'],
196             disk=50,
197             vcpus=2)
198         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
199         flavor_creator.create()
200         self.created_object.append(flavor_creator)
201         image_settings = ImageConfig(
202             name=self.orchestrator['requirements']['os_image'],
203             image_user='centos',
204             exists=True)
205
206         port_settings = PortConfig(
207             name='cloudify_manager_port-{}'.format(self.uuid),
208             network_name=network_settings.name)
209
210         manager_settings = VmInstanceConfig(
211             name='cloudify_manager-{}'.format(self.uuid),
212             flavor=flavor_settings.name,
213             port_settings=[port_settings],
214             security_group_names=[
215                 security_group_creator.sec_grp_settings.name],
216             floating_ip_settings=[FloatingIpConfig(
217                 name='cloudify_manager_fip-{}'.format(self.uuid),
218                 port_name=port_settings.name,
219                 router_name=router_creator.router_settings.name)])
220
221         manager_creator = OpenStackVmInstance(
222             self.snaps_creds, manager_settings, image_settings,
223             keypair_settings)
224
225         self.__logger.info("Creating cloudify manager VM")
226         manager_creator.create()
227         self.created_object.append(manager_creator)
228
229         public_auth_url = keystone_utils.get_endpoint(
230             self.snaps_creds, 'identity')
231
232         cfy_creds = dict(
233             keystone_username=self.snaps_creds.username,
234             keystone_password=self.snaps_creds.password,
235             keystone_tenant_name=self.snaps_creds.project_name,
236             keystone_url=public_auth_url,
237             region=self.snaps_creds.region_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)