278328abfac12774e465e6fe0ff32156264fdb54
[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.constants import CONST
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 = CONST.__getattribute__(
62                 '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             run_blocking_ssh_command(ssh, cmd)
282             cmd = "sudo chmod 444 /etc/cloudify/cloudify_ims.pem"
283             run_blocking_ssh_command(ssh, cmd)
284             cmd = "sudo yum install -y gcc python-devel"
285             run_blocking_ssh_command(ssh, cmd, "Unable to install packages \
286                                                 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:  # 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:  # 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     @energy.enable_recording
419     def run(self, **kwargs):
420         """Execute CloudifyIms test case."""
421         return super(CloudifyIms, self).run(**kwargs)
422
423
424 # ----------------------------------------------------------
425 #
426 #               YAML UTILS
427 #
428 # -----------------------------------------------------------
429 def get_config(parameter, file_path):
430     """
431     Get config parameter.
432
433     Returns the value of a given parameter in file.yaml
434     parameter must be given in string format with dots
435     Example: general.openstack.image_name
436     """
437     with open(file_path) as config_file:
438         file_yaml = yaml.safe_load(config_file)
439     config_file.close()
440     value = file_yaml
441     for element in parameter.split("."):
442         value = value.get(element)
443         if value is None:
444             raise ValueError("The parameter %s is not defined in"
445                              " reporting.yaml" % parameter)
446     return value
447
448
449 def wait_for_execution(client, execution, logger, timeout=1500, ):
450     """Wait for a workflow execution on Cloudify Manager."""
451     # if execution already ended - return without waiting
452     if execution.status in Execution.END_STATES:
453         return execution
454
455     if timeout is not None:
456         deadline = time.time() + timeout
457
458     # Poll for execution status and execution logs, until execution ends
459     # and we receive an event of type in WORKFLOW_END_TYPES
460     offset = 0
461     batch_size = 50
462     event_list = []
463     execution_ended = False
464     while True:
465         event_list = client.events.list(
466             execution_id=execution.id,
467             _offset=offset,
468             _size=batch_size,
469             include_logs=False,
470             sort='@timestamp').items
471
472         offset = offset + len(event_list)
473         for event in event_list:
474             logger.debug(event.get('message'))
475
476         if timeout is not None:
477             if time.time() > deadline:
478                 raise RuntimeError(
479                     'execution of operation {0} for deployment {1} '
480                     'timed out'.format(execution.workflow_id,
481                                        execution.deployment_id))
482             else:
483                 # update the remaining timeout
484                 timeout = deadline - time.time()
485
486         if not execution_ended:
487             execution = client.executions.get(execution.id)
488             execution_ended = execution.status in Execution.END_STATES
489
490         if execution_ended:
491             break
492
493         time.sleep(5)
494
495     return execution
496
497
498 def _get_deployment_environment_creation_execution(client, deployment_id):
499     """
500     Get the execution id of a env preparation.
501
502     network, security group, fip, VM creation
503     """
504     executions = client.executions.list(deployment_id=deployment_id)
505     for execution in executions:
506         if execution.workflow_id == 'create_deployment_environment':
507             return execution
508     raise RuntimeError('Failed to get create_deployment_environment '
509                        'workflow execution.'
510                        'Available executions: {0}'.format(executions))
511
512
513 def sig_test_format(sig_test):
514     """Process the signaling result to have a short result."""
515     nb_passed = 0
516     nb_failures = 0
517     nb_skipped = 0
518     for data_test in sig_test:
519         if data_test['result'] == "Passed":
520             nb_passed += 1
521         elif data_test['result'] == "Failed":
522             nb_failures += 1
523         elif data_test['result'] == "Skipped":
524             nb_skipped += 1
525     short_sig_test_result = {}
526     short_sig_test_result['passed'] = nb_passed
527     short_sig_test_result['failures'] = nb_failures
528     short_sig_test_result['skipped'] = nb_skipped
529     nb_test = nb_passed + nb_skipped
530     return (short_sig_test_result, nb_test)
531
532
533 def run_blocking_ssh_command(ssh, cmd, error_msg="Unable to run this command"):
534     """Command to run ssh command with the exit status."""
535     stdin, stdout, stderr = ssh.exec_command(cmd)
536     if stdout.channel.recv_exit_status() != 0:
537         raise Exception(error_msg)