Remove os_utils call from cloudify ims and vrouter
[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 = CONST.__getattribute__(
62                 'vnf_{}_config'.format(self.case_name))
63         except Exception:
64             raise Exception("VNF config file not found")
65
66         self.snaps_creds = ''
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         # needs some images
119         self.__logger.info("Upload some OS images if it doesn't exist")
120         for image_name, image_file in self.images.iteritems():
121             self.__logger.info("image: %s, file: %s", image_name, image_file)
122             if image_file and image_name:
123                 image_creator = OpenStackImage(
124                     self.snaps_creds,
125                     ImageConfig(
126                         name=image_name, image_user='cloud',
127                         img_format='qcow2', image_file=image_file))
128                 image_creator.create()
129                 # self.created_object.append(image_creator)
130
131     def deploy_orchestrator(self):
132         """
133         Deploy Cloudify Manager.
134
135         network, security group, fip, VM creation
136         """
137         # network creation
138
139         start_time = time.time()
140         self.__logger.info("Creating keypair ...")
141         kp_file = os.path.join(self.data_dir, "cloudify_ims.pem")
142         keypair_settings = KeypairConfig(name='cloudify_ims_kp',
143                                          private_filepath=kp_file)
144         keypair_creator = OpenStackKeypair(self.snaps_creds, keypair_settings)
145         keypair_creator.create()
146         self.created_object.append(keypair_creator)
147
148         self.__logger.info("Creating full network ...")
149         subnet_settings = SubnetConfig(name='cloudify_ims_subnet',
150                                        cidr='10.67.79.0/24')
151         network_settings = NetworkConfig(name='cloudify_ims_network',
152                                          subnet_settings=[subnet_settings])
153         network_creator = OpenStackNetwork(self.snaps_creds, network_settings)
154         network_creator.create()
155         self.created_object.append(network_creator)
156         ext_net_name = snaps_utils.get_ext_net_name(self.snaps_creds)
157         router_creator = OpenStackRouter(
158             self.snaps_creds,
159             RouterConfig(
160                 name='cloudify_ims_router',
161                 external_gateway=ext_net_name,
162                 internal_subnets=[subnet_settings.name]))
163         router_creator.create()
164         self.created_object.append(router_creator)
165
166         # security group creation
167         self.__logger.info("Creating security group for cloudify manager vm")
168         sg_rules = list()
169         sg_rules.append(
170             SecurityGroupRuleConfig(
171                 sec_grp_name="sg-cloudify-manager",
172                 direction=Direction.ingress, protocol=Protocol.tcp,
173                 port_range_min=1, port_range_max=65535))
174         sg_rules.append(
175             SecurityGroupRuleConfig(
176                 sec_grp_name="sg-cloudify-manager",
177                 direction=Direction.ingress, protocol=Protocol.udp,
178                 port_range_min=1, port_range_max=65535))
179
180         securit_group_creator = OpenStackSecurityGroup(
181             self.snaps_creds,
182             SecurityGroupConfig(
183                 name="sg-cloudify-manager",
184                 rule_settings=sg_rules))
185
186         securit_group_creator.create()
187         self.created_object.append(securit_group_creator)
188
189         # orchestrator VM flavor
190         self.__logger.info("Get or create flavor for cloudify manager vm ...")
191
192         flavor_settings = FlavorConfig(
193             name=self.orchestrator['requirements']['flavor']['name'],
194             ram=self.orchestrator['requirements']['flavor']['ram_min'],
195             disk=50,
196             vcpus=2)
197         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
198         flavor_creator.create()
199         self.created_object.append(flavor_creator)
200         image_settings = ImageConfig(
201             name=self.orchestrator['requirements']['os_image'],
202             image_user='centos',
203             exists=True)
204
205         port_settings = PortConfig(name='cloudify_manager_port',
206                                    network_name=network_settings.name)
207
208         manager_settings = VmInstanceConfig(
209             name='cloudify_manager',
210             flavor=flavor_settings.name,
211             port_settings=[port_settings],
212             security_group_names=[securit_group_creator.sec_grp_settings.name],
213             floating_ip_settings=[FloatingIpConfig(
214                 name='cloudify_manager_fip',
215                 port_name=port_settings.name,
216                 router_name=router_creator.router_settings.name)])
217
218         manager_creator = OpenStackVmInstance(self.snaps_creds,
219                                               manager_settings,
220                                               image_settings,
221                                               keypair_settings)
222
223         self.__logger.info("Creating cloudify manager VM")
224         manager_creator.create()
225         self.created_object.append(manager_creator)
226
227         public_auth_url = keystone_utils.get_endpoint(
228             self.snaps_creds, 'identity')
229
230         self.__logger.info("Set creds for cloudify manager")
231         cfy_creds = dict(keystone_username=self.tenant_name,
232                          keystone_password=self.tenant_name,
233                          keystone_tenant_name=self.tenant_name,
234                          keystone_url=public_auth_url)
235
236         cfy_client = CloudifyClient(host=manager_creator.get_floating_ip().ip,
237                                     username='admin',
238                                     password='admin',
239                                     tenant='default_tenant')
240
241         self.orchestrator['object'] = cfy_client
242
243         self.__logger.info("Attemps running status of the Manager")
244         cfy_status = None
245         retry = 10
246         while str(cfy_status) != 'running' and retry:
247             try:
248                 cfy_status = cfy_client.manager.get_status()['status']
249                 self.__logger.debug("The current manager status is %s",
250                                     cfy_status)
251             except Exception:  # pylint: disable=broad-except
252                 self.__logger.warning("Cloudify Manager isn't " +
253                                       "up and running. Retrying ...")
254             retry = retry - 1
255             time.sleep(30)
256
257         if str(cfy_status) == 'running':
258             self.__logger.info("Cloudify Manager is up and running")
259         else:
260             raise Exception("Cloudify Manager isn't up and running")
261
262         self.__logger.info("Put OpenStack creds in manager")
263         secrets_list = cfy_client.secrets.list()
264         for k, val in cfy_creds.iteritems():
265             if not any(d.get('key', None) == k for d in secrets_list):
266                 cfy_client.secrets.create(k, val)
267             else:
268                 cfy_client.secrets.update(k, val)
269
270         duration = time.time() - start_time
271
272         self.__logger.info("Put private keypair in manager")
273         if manager_creator.vm_ssh_active(block=True):
274             ssh = manager_creator.ssh_client()
275             scp = SCPClient(ssh.get_transport(), socket_timeout=15.0)
276             scp.put(kp_file, '~/')
277             cmd = "sudo cp ~/cloudify_ims.pem /etc/cloudify/"
278             run_blocking_ssh_command(ssh, cmd)
279             cmd = "sudo chmod 444 /etc/cloudify/cloudify_ims.pem"
280             run_blocking_ssh_command(ssh, cmd)
281             cmd = "sudo yum install -y gcc python-devel"
282             run_blocking_ssh_command(ssh, cmd, "Unable to install packages \
283                                                 on manager")
284
285         self.details['orchestrator'].update(status='PASS', duration=duration)
286
287         self.vnf['inputs'].update(dict(
288             external_network_name=ext_net_name,
289             network_name=network_settings.name
290         ))
291         self.result = 1/3 * 100
292         return True
293
294     def deploy_vnf(self):
295         """Deploy Clearwater IMS."""
296         start_time = time.time()
297
298         self.__logger.info("Upload VNFD")
299         cfy_client = self.orchestrator['object']
300         descriptor = self.vnf['descriptor']
301         cfy_client.blueprints.publish_archive(descriptor.get('url'),
302                                               descriptor.get('name'),
303                                               descriptor.get('file_name'))
304
305         self.__logger.info("Get or create flavor for all clearwater vm")
306         flavor_settings = FlavorConfig(
307             name=self.vnf['requirements']['flavor']['name'],
308             ram=self.vnf['requirements']['flavor']['ram_min'],
309             disk=25,
310             vcpus=1)
311         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
312         flavor_creator.create()
313         self.created_object.append(flavor_creator)
314
315         self.vnf['inputs'].update(dict(
316             flavor_id=self.vnf['requirements']['flavor']['name'],
317         ))
318
319         self.__logger.info("Create VNF Instance")
320         cfy_client.deployments.create(descriptor.get('name'),
321                                       descriptor.get('name'),
322                                       self.vnf.get('inputs'))
323
324         wait_for_execution(cfy_client,
325                            _get_deployment_environment_creation_execution(
326                                cfy_client, descriptor.get('name')),
327                            self.__logger,
328                            timeout=600)
329
330         self.__logger.info("Start the VNF Instance deployment")
331         execution = cfy_client.executions.start(descriptor.get('name'),
332                                                 'install')
333         # Show execution log
334         execution = wait_for_execution(cfy_client, execution, self.__logger)
335
336         duration = time.time() - start_time
337
338         self.__logger.info(execution)
339         if execution.status == 'terminated':
340             self.details['vnf'].update(status='PASS', duration=duration)
341             self.result += 1/3 * 100
342             result = True
343         else:
344             self.details['vnf'].update(status='FAIL', duration=duration)
345             result = False
346         return result
347
348     def test_vnf(self):
349         """Run test on clearwater ims instance."""
350         start_time = time.time()
351
352         cfy_client = self.orchestrator['object']
353
354         outputs = cfy_client.deployments.outputs.get(
355             self.vnf['descriptor'].get('name'))['outputs']
356         dns_ip = outputs['dns_ip']
357         ellis_ip = outputs['ellis_ip']
358         self.config_ellis(ellis_ip)
359
360         if not dns_ip:
361             return False
362
363         vims_test_result = self.run_clearwater_live_test(
364             dns_ip=dns_ip,
365             public_domain=self.vnf['inputs']["public_domain"])
366         duration = time.time() - start_time
367         short_result, nb_test = sig_test_format(vims_test_result)
368         self.__logger.info(short_result)
369         self.details['test_vnf'].update(result=short_result,
370                                         full_result=vims_test_result,
371                                         duration=duration)
372         try:
373             vnf_test_rate = short_result['passed'] / nb_test
374             # orchestrator + vnf + test_vnf
375             self.result += vnf_test_rate / 3 * 100
376         except ZeroDivisionError:
377             self.__logger.error("No test has been executed")
378             self.details['test_vnf'].update(status='FAIL')
379             return False
380
381         return True
382
383     def clean(self):
384         """Clean created objects/functions."""
385         try:
386             cfy_client = self.orchestrator['object']
387             dep_name = self.vnf['descriptor'].get('name')
388             # kill existing execution
389             self.__logger.info('Deleting the current deployment')
390             exec_list = cfy_client.executions.list(dep_name)
391             for execution in exec_list:
392                 if execution['status'] == "started":
393                     try:
394                         cfy_client.executions.cancel(execution['id'],
395                                                      force=True)
396                     except:  # pylint: disable=broad-except
397                         self.__logger.warn("Can't cancel the current exec")
398
399             execution = cfy_client.executions.start(
400                 dep_name,
401                 'uninstall',
402                 parameters=dict(ignore_failure=True),
403                 force=True)
404
405             wait_for_execution(cfy_client, execution, self.__logger)
406             cfy_client.deployments.delete(self.vnf['descriptor'].get('name'))
407             cfy_client.blueprints.delete(self.vnf['descriptor'].get('name'))
408         except:  # pylint: disable=broad-except
409             self.__logger.warn("Some issue during the undeployment ..")
410             self.__logger.warn("Tenant clean continue ..")
411
412         super(CloudifyIms, self).clean()
413
414     @energy.enable_recording
415     def run(self, **kwargs):
416         """Execute CloudifyIms test case."""
417         return super(CloudifyIms, self).run(**kwargs)
418
419
420 # ----------------------------------------------------------
421 #
422 #               YAML UTILS
423 #
424 # -----------------------------------------------------------
425 def get_config(parameter, file_path):
426     """
427     Get config parameter.
428
429     Returns the value of a given parameter in file.yaml
430     parameter must be given in string format with dots
431     Example: general.openstack.image_name
432     """
433     with open(file_path) as config_file:
434         file_yaml = yaml.safe_load(config_file)
435     config_file.close()
436     value = file_yaml
437     for element in parameter.split("."):
438         value = value.get(element)
439         if value is None:
440             raise ValueError("The parameter %s is not defined in"
441                              " reporting.yaml" % parameter)
442     return value
443
444
445 def wait_for_execution(client, execution, logger, timeout=2400, ):
446     """Wait for a workflow execution on Cloudify Manager."""
447     # if execution already ended - return without waiting
448     if execution.status in Execution.END_STATES:
449         return execution
450
451     if timeout is not None:
452         deadline = time.time() + timeout
453
454     # Poll for execution status and execution logs, until execution ends
455     # and we receive an event of type in WORKFLOW_END_TYPES
456     offset = 0
457     batch_size = 50
458     event_list = []
459     execution_ended = False
460     while True:
461         event_list = client.events.list(
462             execution_id=execution.id,
463             _offset=offset,
464             _size=batch_size,
465             include_logs=False,
466             sort='@timestamp').items
467
468         offset = offset + len(event_list)
469         for event in event_list:
470             logger.debug(event.get('message'))
471
472         if timeout is not None:
473             if time.time() > deadline:
474                 raise RuntimeError(
475                     'execution of operation {0} for deployment {1} '
476                     'timed out'.format(execution.workflow_id,
477                                        execution.deployment_id))
478             else:
479                 # update the remaining timeout
480                 timeout = deadline - time.time()
481
482         if not execution_ended:
483             execution = client.executions.get(execution.id)
484             execution_ended = execution.status in Execution.END_STATES
485
486         if execution_ended:
487             break
488
489         time.sleep(5)
490
491     return execution
492
493
494 def _get_deployment_environment_creation_execution(client, deployment_id):
495     """
496     Get the execution id of a env preparation.
497
498     network, security group, fip, VM creation
499     """
500     executions = client.executions.list(deployment_id=deployment_id)
501     for execution in executions:
502         if execution.workflow_id == 'create_deployment_environment':
503             return execution
504     raise RuntimeError('Failed to get create_deployment_environment '
505                        'workflow execution.'
506                        'Available executions: {0}'.format(executions))
507
508
509 def sig_test_format(sig_test):
510     """Process the signaling result to have a short result."""
511     nb_passed = 0
512     nb_failures = 0
513     nb_skipped = 0
514     for data_test in sig_test:
515         if data_test['result'] == "Passed":
516             nb_passed += 1
517         elif data_test['result'] == "Failed":
518             nb_failures += 1
519         elif data_test['result'] == "Skipped":
520             nb_skipped += 1
521     short_sig_test_result = {}
522     short_sig_test_result['passed'] = nb_passed
523     short_sig_test_result['failures'] = nb_failures
524     short_sig_test_result['skipped'] = nb_skipped
525     nb_test = nb_passed + nb_skipped
526     return (short_sig_test_result, nb_test)
527
528
529 def run_blocking_ssh_command(ssh, cmd, error_msg="Unable to run this command"):
530     """Command to run ssh command with the exit status."""
531     stdin, stdout, stderr = ssh.exec_command(cmd)
532     if stdout.channel.recv_exit_status() != 0:
533         raise Exception(error_msg)