Switch to Python3 divide in cloudify_ims
[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
262         self.orchestrator['object'] = cfy_client
263
264         self.__logger.info("Attemps running status of the Manager")
265         for loop in range(10):
266             try:
267                 self.__logger.debug(
268                     "status %s", cfy_client.manager.get_status())
269                 cfy_status = cfy_client.manager.get_status()['status']
270                 self.__logger.info(
271                     "The current manager status is %s", cfy_status)
272                 if str(cfy_status) != 'running':
273                     raise Exception("Cloudify Manager isn't up and running")
274                 self.__logger.info("Put OpenStack creds in manager")
275                 secrets_list = cfy_client.secrets.list()
276                 for k, val in cfy_creds.iteritems():
277                     if not any(d.get('key', None) == k for d in secrets_list):
278                         cfy_client.secrets.create(k, val)
279                     else:
280                         cfy_client.secrets.update(k, val)
281                 break
282             except Exception:  # pylint: disable=broad-except
283                 self.logger.info(
284                     "try %s: Cloudify Manager isn't up and running", loop + 1)
285                 time.sleep(30)
286         else:
287             self.logger.error("Cloudify Manager isn't up and running")
288             return False
289
290         duration = time.time() - start_time
291
292         if manager_creator.vm_ssh_active(block=True):
293             self.__logger.info("Put private keypair in manager")
294             ssh = manager_creator.ssh_client()
295             scp = SCPClient(ssh.get_transport(), socket_timeout=15.0)
296             scp.put(kp_file, '~/')
297             cmd = "sudo cp ~/cloudify_ims.pem /etc/cloudify/"
298             self.run_blocking_ssh_command(ssh, cmd)
299             cmd = "sudo chmod 444 /etc/cloudify/cloudify_ims.pem"
300             self.run_blocking_ssh_command(ssh, cmd)
301             # cmd2 is badly unpinned by Cloudify
302             cmd = "sudo yum install -y gcc python-devel python-cmd2"
303             self.run_blocking_ssh_command(
304                 ssh, cmd, "Unable to install packages on manager")
305             self.run_blocking_ssh_command(ssh, 'cfy status')
306         else:
307             self.__logger.error("Cannot connect to manager")
308             return False
309
310         self.details['orchestrator'].update(status='PASS', duration=duration)
311
312         self.vnf['inputs'].update(dict(
313             external_network_name=ext_net_name,
314             network_name=network_settings.name,
315             key_pair_name=keypair_settings.name
316         ))
317         self.result = 1/3 * 100
318         return True
319
320     def deploy_vnf(self):
321         """Deploy Clearwater IMS."""
322         start_time = time.time()
323
324         self.__logger.info("Upload VNFD")
325         cfy_client = self.orchestrator['object']
326         descriptor = self.vnf['descriptor']
327         cfy_client.blueprints.upload(
328             descriptor.get('file_name'), descriptor.get('name'))
329         self.__logger.info("Get or create flavor for all clearwater vm")
330         flavor_settings = FlavorConfig(
331             name="{}-{}".format(
332                 self.vnf['requirements']['flavor']['name'],
333                 self.uuid),
334             ram=self.vnf['requirements']['flavor']['ram_min'],
335             disk=25,
336             vcpus=2)
337         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
338         flavor_creator.create()
339         self.created_object.append(flavor_creator)
340
341         self.vnf['inputs'].update(dict(
342             flavor_id=flavor_settings.name,
343         ))
344
345         self.__logger.info("Create VNF Instance")
346         cfy_client.deployments.create(descriptor.get('name'),
347                                       descriptor.get('name'),
348                                       self.vnf.get('inputs'))
349
350         wait_for_execution(cfy_client,
351                            _get_deployment_environment_creation_execution(
352                                cfy_client, descriptor.get('name')),
353                            self.__logger,
354                            timeout=300)
355
356         self.__logger.info("Start the VNF Instance deployment")
357         execution = cfy_client.executions.start(descriptor.get('name'),
358                                                 'install')
359         # Show execution log
360         execution = wait_for_execution(
361             cfy_client, execution, self.__logger, timeout=3600)
362
363         duration = time.time() - start_time
364
365         self.__logger.info(execution)
366         if execution.status == 'terminated':
367             self.details['vnf'].update(status='PASS', duration=duration)
368             self.result += 1/3 * 100
369             result = True
370         else:
371             self.details['vnf'].update(status='FAIL', duration=duration)
372             result = False
373         return result
374
375     def test_vnf(self):
376         """Run test on clearwater ims instance."""
377         start_time = time.time()
378
379         cfy_client = self.orchestrator['object']
380
381         outputs = cfy_client.deployments.outputs.get(
382             self.vnf['descriptor'].get('name'))['outputs']
383         dns_ip = outputs['dns_ip']
384         ellis_ip = outputs['ellis_ip']
385         self.config_ellis(ellis_ip)
386
387         if not dns_ip:
388             return False
389
390         vims_test_result = self.run_clearwater_live_test(
391             dns_ip=dns_ip,
392             public_domain=self.vnf['inputs']["public_domain"])
393         duration = time.time() - start_time
394         short_result, nb_test = sig_test_format(vims_test_result)
395         self.__logger.info(short_result)
396         self.details['test_vnf'].update(result=short_result,
397                                         full_result=vims_test_result,
398                                         duration=duration)
399         try:
400             vnf_test_rate = short_result['passed'] / nb_test
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
408         return True if vnf_test_rate > 0 else False
409
410     def clean(self):
411         """Clean created objects/functions."""
412         try:
413             cfy_client = self.orchestrator['object']
414             dep_name = self.vnf['descriptor'].get('name')
415             # kill existing execution
416             self.__logger.info('Deleting the current deployment')
417             exec_list = cfy_client.executions.list(dep_name)
418             for execution in exec_list:
419                 if execution['status'] == "started":
420                     try:
421                         cfy_client.executions.cancel(execution['id'],
422                                                      force=True)
423                     except Exception:  # pylint: disable=broad-except
424                         self.__logger.warn("Can't cancel the current exec")
425
426             execution = cfy_client.executions.start(
427                 dep_name,
428                 'uninstall',
429                 parameters=dict(ignore_failure=True),
430                 force=True)
431
432             wait_for_execution(cfy_client, execution, self.__logger)
433             cfy_client.deployments.delete(self.vnf['descriptor'].get('name'))
434             cfy_client.blueprints.delete(self.vnf['descriptor'].get('name'))
435         except Exception:  # pylint: disable=broad-except
436             self.__logger.exception("Some issue during the undeployment ..")
437
438         super(CloudifyIms, self).clean()
439
440     @staticmethod
441     def run_blocking_ssh_command(ssh, cmd,
442                                  error_msg="Unable to run this command"):
443         """Command to run ssh command with the exit status."""
444         _, stdout, stderr = ssh.exec_command(cmd)
445         CloudifyIms.__logger.debug("SSH %s stdout: %s", cmd, stdout.read())
446         if stdout.channel.recv_exit_status() != 0:
447             CloudifyIms.__logger.error("SSH %s stderr: %s", cmd, stderr.read())
448             raise Exception(error_msg)
449
450     @energy.enable_recording
451     def run(self, **kwargs):
452         """Execute CloudifyIms test case."""
453         return super(CloudifyIms, self).run(**kwargs)
454
455
456 # ----------------------------------------------------------
457 #
458 #               YAML UTILS
459 #
460 # -----------------------------------------------------------
461 def get_config(parameter, file_path):
462     """
463     Get config parameter.
464
465     Returns the value of a given parameter in file.yaml
466     parameter must be given in string format with dots
467     Example: general.openstack.image_name
468     """
469     with open(file_path) as config_file:
470         file_yaml = yaml.safe_load(config_file)
471     config_file.close()
472     value = file_yaml
473     for element in parameter.split("."):
474         value = value.get(element)
475         if value is None:
476             raise ValueError("The parameter %s is not defined in"
477                              " reporting.yaml" % parameter)
478     return value
479
480
481 def wait_for_execution(client, execution, logger, timeout=3600, ):
482     """Wait for a workflow execution on Cloudify Manager."""
483     # if execution already ended - return without waiting
484     if execution.status in Execution.END_STATES:
485         return execution
486
487     if timeout is not None:
488         deadline = time.time() + timeout
489
490     # Poll for execution status and execution logs, until execution ends
491     # and we receive an event of type in WORKFLOW_END_TYPES
492     offset = 0
493     batch_size = 50
494     event_list = []
495     execution_ended = False
496     while True:
497         event_list = client.events.list(
498             execution_id=execution.id,
499             _offset=offset,
500             _size=batch_size,
501             include_logs=True,
502             sort='@timestamp').items
503
504         offset = offset + len(event_list)
505         for event in event_list:
506             logger.debug(event.get('message'))
507
508         if timeout is not None:
509             if time.time() > deadline:
510                 raise RuntimeError(
511                     'execution of operation {0} for deployment {1} '
512                     'timed out'.format(execution.workflow_id,
513                                        execution.deployment_id))
514             else:
515                 # update the remaining timeout
516                 timeout = deadline - time.time()
517
518         if not execution_ended:
519             execution = client.executions.get(execution.id)
520             execution_ended = execution.status in Execution.END_STATES
521
522         if execution_ended:
523             break
524
525         time.sleep(5)
526
527     return execution
528
529
530 def _get_deployment_environment_creation_execution(client, deployment_id):
531     """
532     Get the execution id of a env preparation.
533
534     network, security group, fip, VM creation
535     """
536     executions = client.executions.list(deployment_id=deployment_id)
537     for execution in executions:
538         if execution.workflow_id == 'create_deployment_environment':
539             return execution
540     raise RuntimeError('Failed to get create_deployment_environment '
541                        'workflow execution.'
542                        'Available executions: {0}'.format(executions))
543
544
545 def sig_test_format(sig_test):
546     """Process the signaling result to have a short result."""
547     nb_passed = 0
548     nb_failures = 0
549     nb_skipped = 0
550     for data_test in sig_test:
551         if data_test['result'] == "Passed":
552             nb_passed += 1
553         elif data_test['result'] == "Failed":
554             nb_failures += 1
555         elif data_test['result'] == "Skipped":
556             nb_skipped += 1
557     short_sig_test_result = {}
558     short_sig_test_result['passed'] = nb_passed
559     short_sig_test_result['failures'] = nb_failures
560     short_sig_test_result['skipped'] = nb_skipped
561     nb_test = nb_passed + nb_skipped
562     return (short_sig_test_result, nb_test)