Merge "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 import config
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                 config.CONF, '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             self.run_blocking_ssh_command(ssh, cmd)
284             cmd = "sudo chmod 444 /etc/cloudify/cloudify_ims.pem"
285             self.run_blocking_ssh_command(ssh, cmd)
286             cmd = "sudo yum install -y gcc python-devel"
287             self.run_blocking_ssh_command(
288                 ssh, cmd, "Unable to install packages on manager")
289
290         self.details['orchestrator'].update(status='PASS', duration=duration)
291
292         self.vnf['inputs'].update(dict(
293             external_network_name=ext_net_name,
294             network_name=network_settings.name,
295             key_pair_name=keypair_settings.name
296         ))
297         self.result = 1/3 * 100
298         return True
299
300     def deploy_vnf(self):
301         """Deploy Clearwater IMS."""
302         start_time = time.time()
303
304         self.__logger.info("Upload VNFD")
305         cfy_client = self.orchestrator['object']
306         descriptor = self.vnf['descriptor']
307         cfy_client.blueprints.publish_archive(descriptor.get('url'),
308                                               descriptor.get('name'),
309                                               descriptor.get('file_name'))
310
311         self.__logger.info("Get or create flavor for all clearwater vm")
312         flavor_settings = FlavorConfig(
313             name=self.vnf['requirements']['flavor']['name'],
314             ram=self.vnf['requirements']['flavor']['ram_min'],
315             disk=25,
316             vcpus=1)
317         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
318         flavor_creator.create()
319         self.created_object.append(flavor_creator)
320
321         self.vnf['inputs'].update(dict(
322             flavor_id=self.vnf['requirements']['flavor']['name'],
323         ))
324
325         self.__logger.info("Create VNF Instance")
326         cfy_client.deployments.create(descriptor.get('name'),
327                                       descriptor.get('name'),
328                                       self.vnf.get('inputs'))
329
330         wait_for_execution(cfy_client,
331                            _get_deployment_environment_creation_execution(
332                                cfy_client, descriptor.get('name')),
333                            self.__logger,
334                            timeout=300)
335
336         self.__logger.info("Start the VNF Instance deployment")
337         execution = cfy_client.executions.start(descriptor.get('name'),
338                                                 'install')
339         # Show execution log
340         execution = wait_for_execution(cfy_client, execution, self.__logger)
341
342         duration = time.time() - start_time
343
344         self.__logger.info(execution)
345         if execution.status == 'terminated':
346             self.details['vnf'].update(status='PASS', duration=duration)
347             self.result += 1/3 * 100
348             result = True
349         else:
350             self.details['vnf'].update(status='FAIL', duration=duration)
351             result = False
352         return result
353
354     def test_vnf(self):
355         """Run test on clearwater ims instance."""
356         start_time = time.time()
357
358         cfy_client = self.orchestrator['object']
359
360         outputs = cfy_client.deployments.outputs.get(
361             self.vnf['descriptor'].get('name'))['outputs']
362         dns_ip = outputs['dns_ip']
363         ellis_ip = outputs['ellis_ip']
364         self.config_ellis(ellis_ip)
365
366         if not dns_ip:
367             return False
368
369         vims_test_result = self.run_clearwater_live_test(
370             dns_ip=dns_ip,
371             public_domain=self.vnf['inputs']["public_domain"])
372         duration = time.time() - start_time
373         short_result, nb_test = sig_test_format(vims_test_result)
374         self.__logger.info(short_result)
375         self.details['test_vnf'].update(result=short_result,
376                                         full_result=vims_test_result,
377                                         duration=duration)
378         try:
379             vnf_test_rate = short_result['passed'] / nb_test
380             # orchestrator + vnf + test_vnf
381             self.result += vnf_test_rate / 3 * 100
382         except ZeroDivisionError:
383             self.__logger.error("No test has been executed")
384             self.details['test_vnf'].update(status='FAIL')
385             return False
386
387         return True
388
389     def clean(self):
390         """Clean created objects/functions."""
391         try:
392             cfy_client = self.orchestrator['object']
393             dep_name = self.vnf['descriptor'].get('name')
394             # kill existing execution
395             self.__logger.info('Deleting the current deployment')
396             exec_list = cfy_client.executions.list(dep_name)
397             for execution in exec_list:
398                 if execution['status'] == "started":
399                     try:
400                         cfy_client.executions.cancel(execution['id'],
401                                                      force=True)
402                     except Exception:  # pylint: disable=broad-except
403                         self.__logger.warn("Can't cancel the current exec")
404
405             execution = cfy_client.executions.start(
406                 dep_name,
407                 'uninstall',
408                 parameters=dict(ignore_failure=True),
409                 force=True)
410
411             wait_for_execution(cfy_client, execution, self.__logger)
412             cfy_client.deployments.delete(self.vnf['descriptor'].get('name'))
413             cfy_client.blueprints.delete(self.vnf['descriptor'].get('name'))
414         except Exception:  # pylint: disable=broad-except
415             self.__logger.warn("Some issue during the undeployment ..")
416             self.__logger.warn("Tenant clean continue ..")
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)