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