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