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