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