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