Merge "Remove prepare_env"
[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.config.flavor import FlavorConfig
28 from snaps.config.image import ImageConfig
29 from snaps.config.keypair import KeypairConfig
30 from snaps.config.network import NetworkConfig, PortConfig, SubnetConfig
31 from snaps.config.router import RouterConfig
32 from snaps.config.security_group import (
33     Direction, Protocol, SecurityGroupConfig, SecurityGroupRuleConfig)
34 from snaps.config.vm_inst import FloatingIpConfig, VmInstanceConfig
35
36 from snaps.openstack.create_flavor import OpenStackFlavor
37 from snaps.openstack.create_image import OpenStackImage
38 from snaps.openstack.create_instance import OpenStackVmInstance
39 from snaps.openstack.create_keypairs import OpenStackKeypair
40 from snaps.openstack.create_network import OpenStackNetwork
41 from snaps.openstack.create_router import OpenStackRouter
42 from snaps.openstack.create_security_group import OpenStackSecurityGroup
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         self.snaps_creds = ''
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         # needs some images
119         self.__logger.info("Upload some OS images if it doesn't exist")
120         for image_name, image_file in self.images.iteritems():
121             self.__logger.info("image: %s, file: %s", image_name, image_file)
122             if image_file and image_name:
123                 image_creator = OpenStackImage(
124                     self.snaps_creds,
125                     ImageConfig(
126                         name=image_name, image_user='cloud',
127                         img_format='qcow2', image_file=image_file))
128                 image_creator.create()
129                 # self.created_object.append(image_creator)
130
131     def deploy_orchestrator(self):
132         """
133         Deploy Cloudify Manager.
134
135         network, security group, fip, VM creation
136         """
137         # network creation
138
139         start_time = time.time()
140         self.__logger.info("Creating keypair ...")
141         kp_file = os.path.join(self.data_dir, "cloudify_ims.pem")
142         keypair_settings = KeypairConfig(name='cloudify_ims_kp',
143                                          private_filepath=kp_file)
144         keypair_creator = OpenStackKeypair(self.snaps_creds, keypair_settings)
145         keypair_creator.create()
146         self.created_object.append(keypair_creator)
147
148         self.__logger.info("Creating full network ...")
149         subnet_settings = SubnetConfig(name='cloudify_ims_subnet',
150                                        cidr='10.67.79.0/24')
151         network_settings = NetworkConfig(name='cloudify_ims_network',
152                                          subnet_settings=[subnet_settings])
153         network_creator = OpenStackNetwork(self.snaps_creds, network_settings)
154         network_creator.create()
155         self.created_object.append(network_creator)
156         ext_net_name = snaps_utils.get_ext_net_name(self.snaps_creds)
157         router_creator = OpenStackRouter(
158             self.snaps_creds,
159             RouterConfig(
160                 name='cloudify_ims_router',
161                 external_gateway=ext_net_name,
162                 internal_subnets=[subnet_settings.name]))
163         router_creator.create()
164         self.created_object.append(router_creator)
165
166         # security group creation
167         self.__logger.info("Creating security group for cloudify manager vm")
168         sg_rules = list()
169         sg_rules.append(
170             SecurityGroupRuleConfig(
171                 sec_grp_name="sg-cloudify-manager",
172                 direction=Direction.ingress, protocol=Protocol.tcp,
173                 port_range_min=1, port_range_max=65535))
174         sg_rules.append(
175             SecurityGroupRuleConfig(
176                 sec_grp_name="sg-cloudify-manager",
177                 direction=Direction.ingress, protocol=Protocol.udp,
178                 port_range_min=1, port_range_max=65535))
179
180         securit_group_creator = OpenStackSecurityGroup(
181             self.snaps_creds,
182             SecurityGroupConfig(
183                 name="sg-cloudify-manager",
184                 rule_settings=sg_rules))
185
186         securit_group_creator.create()
187         self.created_object.append(securit_group_creator)
188
189         # orchestrator VM flavor
190         self.__logger.info("Get or create flavor for cloudify manager vm ...")
191
192         flavor_settings = FlavorConfig(
193             name=self.orchestrator['requirements']['flavor']['name'],
194             ram=self.orchestrator['requirements']['flavor']['ram_min'],
195             disk=50,
196             vcpus=2)
197         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
198         flavor_creator.create()
199         self.created_object.append(flavor_creator)
200         image_settings = ImageConfig(
201             name=self.orchestrator['requirements']['os_image'],
202             image_user='centos',
203             exists=True)
204
205         port_settings = PortConfig(name='cloudify_manager_port',
206                                    network_name=network_settings.name)
207
208         manager_settings = VmInstanceConfig(
209             name='cloudify_manager',
210             flavor=flavor_settings.name,
211             port_settings=[port_settings],
212             security_group_names=[securit_group_creator.sec_grp_settings.name],
213             floating_ip_settings=[FloatingIpConfig(
214                 name='cloudify_manager_fip',
215                 port_name=port_settings.name,
216                 router_name=router_creator.router_settings.name)])
217
218         manager_creator = OpenStackVmInstance(self.snaps_creds,
219                                               manager_settings,
220                                               image_settings,
221                                               keypair_settings)
222
223         self.__logger.info("Creating cloudify manager VM")
224         manager_creator.create()
225         self.created_object.append(manager_creator)
226
227         public_auth_url = os_utils.get_endpoint('identity')
228
229         self.__logger.info("Set creds for cloudify manager")
230         cfy_creds = dict(keystone_username=self.tenant_name,
231                          keystone_password=self.tenant_name,
232                          keystone_tenant_name=self.tenant_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=600)
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=2400, ):
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)