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