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