Merge "Add debug logs in cloudify_ims"
[functest.git] / functest / opnfv_tests / vnf / ims / cloudify_ims.py
1 #!/usr/bin/env python
2
3 # Copyright (c) 2017 Orange and others.
4 #
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Apache License, Version 2.0
7 # which accompanies this distribution, and is available at
8 # http://www.apache.org/licenses/LICENSE-2.0
9
10 """CloudifyIms testcase implementation."""
11
12 import logging
13 import os
14 import time
15
16 from cloudify_rest_client import CloudifyClient
17 from cloudify_rest_client.executions import Execution
18 from scp import SCPClient
19 import yaml
20
21 from 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                 cfy_status = cfy_client.manager.get_status()['status']
255                 self.__logger.info(
256                     "The current manager status is %s", cfy_status)
257             except Exception:  # pylint: disable=broad-except
258                 self.__logger.info(
259                     "Cloudify Manager isn't up and running. Retrying ...")
260             retry = retry - 1
261             time.sleep(30)
262
263         if str(cfy_status) == 'running':
264             self.__logger.info("Cloudify Manager is up and running")
265         else:
266             raise Exception("Cloudify Manager isn't up and running")
267
268         self.__logger.info("Put OpenStack creds in manager")
269         secrets_list = cfy_client.secrets.list()
270         for k, val in cfy_creds.iteritems():
271             if not any(d.get('key', None) == k for d in secrets_list):
272                 cfy_client.secrets.create(k, val)
273             else:
274                 cfy_client.secrets.update(k, val)
275
276         duration = time.time() - start_time
277
278         self.__logger.info("Put private keypair in manager")
279         if manager_creator.vm_ssh_active(block=True):
280             ssh = manager_creator.ssh_client()
281             scp = SCPClient(ssh.get_transport(), socket_timeout=15.0)
282             scp.put(kp_file, '~/')
283             cmd = "sudo cp ~/cloudify_ims.pem /etc/cloudify/"
284             self.run_blocking_ssh_command(ssh, cmd)
285             cmd = "sudo chmod 444 /etc/cloudify/cloudify_ims.pem"
286             self.run_blocking_ssh_command(ssh, cmd)
287             cmd = "sudo yum install -y gcc python-devel"
288             self.run_blocking_ssh_command(
289                 ssh, cmd, "Unable to install packages on manager")
290
291         self.details['orchestrator'].update(status='PASS', duration=duration)
292
293         self.vnf['inputs'].update(dict(
294             external_network_name=ext_net_name,
295             network_name=network_settings.name,
296             key_pair_name=keypair_settings.name
297         ))
298         self.result = 1/3 * 100
299         return True
300
301     def deploy_vnf(self):
302         """Deploy Clearwater IMS."""
303         start_time = time.time()
304
305         self.__logger.info("Upload VNFD")
306         cfy_client = self.orchestrator['object']
307         descriptor = self.vnf['descriptor']
308         cfy_client.blueprints.publish_archive(descriptor.get('url'),
309                                               descriptor.get('name'),
310                                               descriptor.get('file_name'))
311
312         self.__logger.info("Get or create flavor for all clearwater vm")
313         flavor_settings = FlavorConfig(
314             name=self.vnf['requirements']['flavor']['name'],
315             ram=self.vnf['requirements']['flavor']['ram_min'],
316             disk=25,
317             vcpus=1)
318         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
319         flavor_creator.create()
320         self.created_object.append(flavor_creator)
321
322         self.vnf['inputs'].update(dict(
323             flavor_id=self.vnf['requirements']['flavor']['name'],
324         ))
325
326         self.__logger.info("Create VNF Instance")
327         cfy_client.deployments.create(descriptor.get('name'),
328                                       descriptor.get('name'),
329                                       self.vnf.get('inputs'))
330
331         wait_for_execution(cfy_client,
332                            _get_deployment_environment_creation_execution(
333                                cfy_client, descriptor.get('name')),
334                            self.__logger,
335                            timeout=300)
336
337         self.__logger.info("Start the VNF Instance deployment")
338         execution = cfy_client.executions.start(descriptor.get('name'),
339                                                 'install')
340         # Show execution log
341         execution = wait_for_execution(cfy_client, execution, self.__logger)
342
343         duration = time.time() - start_time
344
345         self.__logger.info(execution)
346         if execution.status == 'terminated':
347             self.details['vnf'].update(status='PASS', duration=duration)
348             self.result += 1/3 * 100
349             result = True
350         else:
351             self.details['vnf'].update(status='FAIL', duration=duration)
352             result = False
353         return result
354
355     def test_vnf(self):
356         """Run test on clearwater ims instance."""
357         start_time = time.time()
358
359         cfy_client = self.orchestrator['object']
360
361         outputs = cfy_client.deployments.outputs.get(
362             self.vnf['descriptor'].get('name'))['outputs']
363         dns_ip = outputs['dns_ip']
364         ellis_ip = outputs['ellis_ip']
365         self.config_ellis(ellis_ip)
366
367         if not dns_ip:
368             return False
369
370         vims_test_result = self.run_clearwater_live_test(
371             dns_ip=dns_ip,
372             public_domain=self.vnf['inputs']["public_domain"])
373         duration = time.time() - start_time
374         short_result, nb_test = sig_test_format(vims_test_result)
375         self.__logger.info(short_result)
376         self.details['test_vnf'].update(result=short_result,
377                                         full_result=vims_test_result,
378                                         duration=duration)
379         try:
380             vnf_test_rate = short_result['passed'] / nb_test
381             # orchestrator + vnf + test_vnf
382             self.result += vnf_test_rate / 3 * 100
383         except ZeroDivisionError:
384             self.__logger.error("No test has been executed")
385             self.details['test_vnf'].update(status='FAIL')
386             return False
387
388         return True
389
390     def clean(self):
391         """Clean created objects/functions."""
392         try:
393             cfy_client = self.orchestrator['object']
394             dep_name = self.vnf['descriptor'].get('name')
395             # kill existing execution
396             self.__logger.info('Deleting the current deployment')
397             exec_list = cfy_client.executions.list(dep_name)
398             for execution in exec_list:
399                 if execution['status'] == "started":
400                     try:
401                         cfy_client.executions.cancel(execution['id'],
402                                                      force=True)
403                     except Exception:  # pylint: disable=broad-except
404                         self.__logger.warn("Can't cancel the current exec")
405
406             execution = cfy_client.executions.start(
407                 dep_name,
408                 'uninstall',
409                 parameters=dict(ignore_failure=True),
410                 force=True)
411
412             wait_for_execution(cfy_client, execution, self.__logger)
413             cfy_client.deployments.delete(self.vnf['descriptor'].get('name'))
414             cfy_client.blueprints.delete(self.vnf['descriptor'].get('name'))
415         except Exception:  # pylint: disable=broad-except
416             self.__logger.exception("Some issue during the undeployment ..")
417
418         super(CloudifyIms, self).clean()
419
420     @staticmethod
421     def run_blocking_ssh_command(ssh, cmd,
422                                  error_msg="Unable to run this command"):
423         """Command to run ssh command with the exit status."""
424         _, stdout, stderr = ssh.exec_command(cmd)
425         CloudifyIms.__logger.debug("SSH %s stdout: %s", cmd, stdout.read())
426         if stdout.channel.recv_exit_status() != 0:
427             CloudifyIms.__logger.error("SSH %s stderr: %s", cmd, stderr.read())
428             raise Exception(error_msg)
429
430     @energy.enable_recording
431     def run(self, **kwargs):
432         """Execute CloudifyIms test case."""
433         return super(CloudifyIms, self).run(**kwargs)
434
435
436 # ----------------------------------------------------------
437 #
438 #               YAML UTILS
439 #
440 # -----------------------------------------------------------
441 def get_config(parameter, file_path):
442     """
443     Get config parameter.
444
445     Returns the value of a given parameter in file.yaml
446     parameter must be given in string format with dots
447     Example: general.openstack.image_name
448     """
449     with open(file_path) as config_file:
450         file_yaml = yaml.safe_load(config_file)
451     config_file.close()
452     value = file_yaml
453     for element in parameter.split("."):
454         value = value.get(element)
455         if value is None:
456             raise ValueError("The parameter %s is not defined in"
457                              " reporting.yaml" % parameter)
458     return value
459
460
461 def wait_for_execution(client, execution, logger, timeout=1500, ):
462     """Wait for a workflow execution on Cloudify Manager."""
463     # if execution already ended - return without waiting
464     if execution.status in Execution.END_STATES:
465         return execution
466
467     if timeout is not None:
468         deadline = time.time() + timeout
469
470     # Poll for execution status and execution logs, until execution ends
471     # and we receive an event of type in WORKFLOW_END_TYPES
472     offset = 0
473     batch_size = 50
474     event_list = []
475     execution_ended = False
476     while True:
477         event_list = client.events.list(
478             execution_id=execution.id,
479             _offset=offset,
480             _size=batch_size,
481             include_logs=False,
482             sort='@timestamp').items
483
484         offset = offset + len(event_list)
485         for event in event_list:
486             logger.debug(event.get('message'))
487
488         if timeout is not None:
489             if time.time() > deadline:
490                 raise RuntimeError(
491                     'execution of operation {0} for deployment {1} '
492                     'timed out'.format(execution.workflow_id,
493                                        execution.deployment_id))
494             else:
495                 # update the remaining timeout
496                 timeout = deadline - time.time()
497
498         if not execution_ended:
499             execution = client.executions.get(execution.id)
500             execution_ended = execution.status in Execution.END_STATES
501
502         if execution_ended:
503             break
504
505         time.sleep(5)
506
507     return execution
508
509
510 def _get_deployment_environment_creation_execution(client, deployment_id):
511     """
512     Get the execution id of a env preparation.
513
514     network, security group, fip, VM creation
515     """
516     executions = client.executions.list(deployment_id=deployment_id)
517     for execution in executions:
518         if execution.workflow_id == 'create_deployment_environment':
519             return execution
520     raise RuntimeError('Failed to get create_deployment_environment '
521                        'workflow execution.'
522                        'Available executions: {0}'.format(executions))
523
524
525 def sig_test_format(sig_test):
526     """Process the signaling result to have a short result."""
527     nb_passed = 0
528     nb_failures = 0
529     nb_skipped = 0
530     for data_test in sig_test:
531         if data_test['result'] == "Passed":
532             nb_passed += 1
533         elif data_test['result'] == "Failed":
534             nb_failures += 1
535         elif data_test['result'] == "Skipped":
536             nb_skipped += 1
537     short_sig_test_result = {}
538     short_sig_test_result['passed'] = nb_passed
539     short_sig_test_result['failures'] = nb_failures
540     short_sig_test_result['skipped'] = nb_skipped
541     nb_test = nb_passed + nb_skipped
542     return (short_sig_test_result, nb_test)