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