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