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