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