Support different user/project domain values
[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 functest.energy import energy
22 from functest.opnfv_tests.openstack.snaps import snaps_utils
23 import functest.opnfv_tests.vnf.ims.clearwater_ims_base as clearwater_ims_base
24 from functest.utils.constants import CONST
25
26 from snaps.config.flavor import FlavorConfig
27 from snaps.config.image import ImageConfig
28 from snaps.config.keypair import KeypairConfig
29 from snaps.config.network import NetworkConfig, PortConfig, SubnetConfig
30 from snaps.config.router import RouterConfig
31 from snaps.config.security_group import (
32     Direction, Protocol, SecurityGroupConfig, SecurityGroupRuleConfig)
33 from snaps.config.vm_inst import FloatingIpConfig, VmInstanceConfig
34
35 from snaps.openstack.create_flavor import OpenStackFlavor
36 from snaps.openstack.create_image import OpenStackImage
37 from snaps.openstack.create_instance import OpenStackVmInstance
38 from snaps.openstack.create_keypairs import OpenStackKeypair
39 from snaps.openstack.create_network import OpenStackNetwork
40 from snaps.openstack.create_router import OpenStackRouter
41 from snaps.openstack.create_security_group import OpenStackSecurityGroup
42 from snaps.openstack.utils import keystone_utils
43
44
45 __author__ = "Valentin Boucher <valentin.boucher@orange.com>"
46
47
48 class CloudifyIms(clearwater_ims_base.ClearwaterOnBoardingBase):
49     """Clearwater vIMS deployed with Cloudify Orchestrator Case."""
50
51     __logger = logging.getLogger(__name__)
52
53     def __init__(self, **kwargs):
54         """Initialize CloudifyIms testcase object."""
55         if "case_name" not in kwargs:
56             kwargs["case_name"] = "cloudify_ims"
57         super(CloudifyIms, self).__init__(**kwargs)
58
59         # Retrieve the configuration
60         try:
61             self.config = getattr(
62                 CONST, 'vnf_{}_config'.format(self.case_name))
63         except Exception:
64             raise Exception("VNF config file not found")
65
66         config_file = os.path.join(self.case_dir, self.config)
67         self.orchestrator = dict(
68             requirements=get_config("orchestrator.requirements", config_file),
69         )
70         self.details['orchestrator'] = dict(
71             name=get_config("orchestrator.name", config_file),
72             version=get_config("orchestrator.version", config_file),
73             status='ERROR',
74             result=''
75         )
76         self.__logger.debug("Orchestrator configuration %s", self.orchestrator)
77         self.vnf = dict(
78             descriptor=get_config("vnf.descriptor", config_file),
79             inputs=get_config("vnf.inputs", config_file),
80             requirements=get_config("vnf.requirements", config_file)
81         )
82         self.details['vnf'] = dict(
83             descriptor_version=self.vnf['descriptor']['version'],
84             name=get_config("vnf.name", config_file),
85             version=get_config("vnf.version", config_file),
86         )
87         self.__logger.debug("VNF configuration: %s", self.vnf)
88
89         self.details['test_vnf'] = dict(
90             name=get_config("vnf_test_suite.name", config_file),
91             version=get_config("vnf_test_suite.version", config_file)
92         )
93         self.images = get_config("tenant_images", config_file)
94         self.__logger.info("Images needed for vIMS: %s", self.images)
95
96     def prepare(self):
97         """Prepare testscase (Additional pre-configuration steps)."""
98         super(CloudifyIms, self).prepare()
99
100         self.__logger.info("Additional pre-configuration steps")
101
102         compute_quotas = self.os_project.get_compute_quotas()
103         network_quotas = self.os_project.get_network_quotas()
104
105         for key, value in (
106                 self.vnf['requirements']['compute_quotas'].items()):
107             setattr(compute_quotas, key, value)
108
109         for key, value in (
110                 self.vnf['requirements']['network_quotas'].items()):
111             setattr(network_quotas, key, value)
112
113         compute_quotas = self.os_project.update_compute_quotas(compute_quotas)
114         network_quotas = self.os_project.update_network_quotas(network_quotas)
115
116         # needs some images
117         self.__logger.info("Upload some OS images if it doesn't exist")
118         for image_name, image_file in self.images.iteritems():
119             self.__logger.info("image: %s, file: %s", image_name, image_file)
120             if image_file and image_name:
121                 image_creator = OpenStackImage(
122                     self.snaps_creds,
123                     ImageConfig(
124                         name=image_name, image_user='cloud',
125                         img_format='qcow2', image_file=image_file))
126                 image_creator.create()
127                 self.created_object.append(image_creator)
128
129     def deploy_orchestrator(self):
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         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.debug("The current manager status is %s",
255                                     cfy_status)
256             except Exception:  # pylint: disable=broad-except
257                 self.__logger.exception(
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             run_blocking_ssh_command(ssh, cmd)
284             cmd = "sudo chmod 444 /etc/cloudify/cloudify_ims.pem"
285             run_blocking_ssh_command(ssh, cmd)
286             cmd = "sudo yum install -y gcc python-devel"
287             run_blocking_ssh_command(ssh, cmd, "Unable to install packages \
288                                                 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:  # 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:  # pylint: disable=broad-except
415             self.__logger.warn("Some issue during the undeployment ..")
416             self.__logger.warn("Tenant clean continue ..")
417
418         super(CloudifyIms, self).clean()
419
420     @energy.enable_recording
421     def run(self, **kwargs):
422         """Execute CloudifyIms test case."""
423         return super(CloudifyIms, self).run(**kwargs)
424
425
426 # ----------------------------------------------------------
427 #
428 #               YAML UTILS
429 #
430 # -----------------------------------------------------------
431 def get_config(parameter, file_path):
432     """
433     Get config parameter.
434
435     Returns the value of a given parameter in file.yaml
436     parameter must be given in string format with dots
437     Example: general.openstack.image_name
438     """
439     with open(file_path) as config_file:
440         file_yaml = yaml.safe_load(config_file)
441     config_file.close()
442     value = file_yaml
443     for element in parameter.split("."):
444         value = value.get(element)
445         if value is None:
446             raise ValueError("The parameter %s is not defined in"
447                              " reporting.yaml" % parameter)
448     return value
449
450
451 def wait_for_execution(client, execution, logger, timeout=1500, ):
452     """Wait for a workflow execution on Cloudify Manager."""
453     # if execution already ended - return without waiting
454     if execution.status in Execution.END_STATES:
455         return execution
456
457     if timeout is not None:
458         deadline = time.time() + timeout
459
460     # Poll for execution status and execution logs, until execution ends
461     # and we receive an event of type in WORKFLOW_END_TYPES
462     offset = 0
463     batch_size = 50
464     event_list = []
465     execution_ended = False
466     while True:
467         event_list = client.events.list(
468             execution_id=execution.id,
469             _offset=offset,
470             _size=batch_size,
471             include_logs=False,
472             sort='@timestamp').items
473
474         offset = offset + len(event_list)
475         for event in event_list:
476             logger.debug(event.get('message'))
477
478         if timeout is not None:
479             if time.time() > deadline:
480                 raise RuntimeError(
481                     'execution of operation {0} for deployment {1} '
482                     'timed out'.format(execution.workflow_id,
483                                        execution.deployment_id))
484             else:
485                 # update the remaining timeout
486                 timeout = deadline - time.time()
487
488         if not execution_ended:
489             execution = client.executions.get(execution.id)
490             execution_ended = execution.status in Execution.END_STATES
491
492         if execution_ended:
493             break
494
495         time.sleep(5)
496
497     return execution
498
499
500 def _get_deployment_environment_creation_execution(client, deployment_id):
501     """
502     Get the execution id of a env preparation.
503
504     network, security group, fip, VM creation
505     """
506     executions = client.executions.list(deployment_id=deployment_id)
507     for execution in executions:
508         if execution.workflow_id == 'create_deployment_environment':
509             return execution
510     raise RuntimeError('Failed to get create_deployment_environment '
511                        'workflow execution.'
512                        'Available executions: {0}'.format(executions))
513
514
515 def sig_test_format(sig_test):
516     """Process the signaling result to have a short result."""
517     nb_passed = 0
518     nb_failures = 0
519     nb_skipped = 0
520     for data_test in sig_test:
521         if data_test['result'] == "Passed":
522             nb_passed += 1
523         elif data_test['result'] == "Failed":
524             nb_failures += 1
525         elif data_test['result'] == "Skipped":
526             nb_skipped += 1
527     short_sig_test_result = {}
528     short_sig_test_result['passed'] = nb_passed
529     short_sig_test_result['failures'] = nb_failures
530     short_sig_test_result['skipped'] = nb_skipped
531     nb_test = nb_passed + nb_skipped
532     return (short_sig_test_result, nb_test)
533
534
535 def run_blocking_ssh_command(ssh, cmd, error_msg="Unable to run this command"):
536     """Command to run ssh command with the exit status."""
537     stdin, stdout, stderr = ssh.exec_command(cmd)
538     if stdout.channel.recv_exit_status() != 0:
539         raise Exception(error_msg)