217312e3f798158bf9a3a8a8a63fa9e9c0da82c9
[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 # pylint: disable=missing-docstring
11
12 """vrouter testcase implementation."""
13
14 import logging
15 import os
16 import time
17
18 from cloudify_rest_client import CloudifyClient
19 from cloudify_rest_client.executions import Execution
20 from scp import SCPClient
21
22 from functest.opnfv_tests.openstack.snaps import snaps_utils
23 import functest.opnfv_tests.vnf.router.vrouter_base as vrouter_base
24 from functest.opnfv_tests.vnf.router.utilvnf import Utilvnf
25 from functest.utils.constants import CONST
26 from functest.utils import functest_utils
27
28 from git import Repo
29
30 from snaps.config.flavor import FlavorConfig
31 from snaps.config.image import ImageConfig
32 from snaps.config.keypair import KeypairConfig
33 from snaps.config.network import NetworkConfig, PortConfig, SubnetConfig
34 from snaps.config.router import RouterConfig
35 from snaps.config.security_group import (
36     Direction, Protocol, SecurityGroupConfig, SecurityGroupRuleConfig)
37 from snaps.config.vm_inst import FloatingIpConfig, VmInstanceConfig
38
39 from snaps.openstack.create_flavor import OpenStackFlavor
40 from snaps.openstack.create_image import OpenStackImage
41 from snaps.openstack.create_instance import OpenStackVmInstance
42 from snaps.openstack.create_keypairs import OpenStackKeypair
43 from snaps.openstack.create_network import OpenStackNetwork
44 from snaps.openstack.create_security_group import OpenStackSecurityGroup
45 from snaps.openstack.create_router import OpenStackRouter
46
47 import snaps.openstack.utils.glance_utils as glance_utils
48 from snaps.openstack.utils import keystone_utils
49
50
51 __author__ = "Shuya Nakama <shuya.nakama@okinawaopenlabs.org>"
52
53
54 class CloudifyVrouter(vrouter_base.VrouterOnBoardingBase):
55     # pylint: disable=too-many-instance-attributes
56     """vrouter testcase deployed with Cloudify Orchestrator."""
57
58     __logger = logging.getLogger(__name__)
59     name = __name__
60
61     def __init__(self, **kwargs):
62         if "case_name" not in kwargs:
63             kwargs["case_name"] = "vyos_vrouter"
64         super(CloudifyVrouter, self).__init__(**kwargs)
65
66         # Retrieve the configuration
67         try:
68             self.config = getattr(
69                 CONST, 'vnf_{}_config'.format(self.case_name))
70         except Exception:
71             raise Exception("VNF config file not found")
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=functest_utils.get_parameter_from_yaml(
80                 "orchestrator.requirements", config_file),
81         )
82         self.details['orchestrator'] = dict(
83             name=functest_utils.get_parameter_from_yaml(
84                 "orchestrator.name", config_file),
85             version=functest_utils.get_parameter_from_yaml(
86                 "orchestrator.version", config_file),
87             status='ERROR',
88             result=''
89         )
90         self.__logger.debug("Orchestrator configuration %s", self.orchestrator)
91         self.__logger.debug("name = %s", self.name)
92         self.vnf = dict(
93             descriptor=functest_utils.get_parameter_from_yaml(
94                 "vnf.descriptor", config_file),
95             inputs=functest_utils.get_parameter_from_yaml(
96                 "vnf.inputs", config_file),
97             requirements=functest_utils.get_parameter_from_yaml(
98                 "vnf.requirements", config_file)
99         )
100         self.details['vnf'] = dict(
101             descriptor_version=self.vnf['descriptor']['version'],
102             name=functest_utils.get_parameter_from_yaml(
103                 "vnf.name", config_file),
104             version=functest_utils.get_parameter_from_yaml(
105                 "vnf.version", config_file),
106         )
107         self.__logger.debug("VNF configuration: %s", self.vnf)
108
109         self.util = Utilvnf()
110
111         self.details['test_vnf'] = dict(
112             name=functest_utils.get_parameter_from_yaml(
113                 "vnf_test_suite.name", config_file),
114             version=functest_utils.get_parameter_from_yaml(
115                 "vnf_test_suite.version", config_file)
116         )
117         self.images = functest_utils.get_parameter_from_yaml(
118             "tenant_images", config_file)
119         self.__logger.info("Images needed for vrouter: %s", self.images)
120
121     @staticmethod
122     def run_blocking_ssh_command(ssh, cmd,
123                                  error_msg="Unable to run this command"):
124         """Command to run ssh command with the exit status."""
125         (_, stdout, stderr) = ssh.exec_command(cmd)
126         CloudifyVrouter.__logger.debug("SSH %s stdout: %s", cmd, stdout.read())
127         if stdout.channel.recv_exit_status() != 0:
128             CloudifyVrouter.__logger.error(
129                 "SSH %s stderr: %s", cmd, stderr.read())
130             raise Exception(error_msg)
131
132     def prepare(self):
133         super(CloudifyVrouter, self).prepare()
134         self.__logger.info("Additional pre-configuration steps")
135         self.util.set_credentials(self.snaps_creds)
136         self.__logger.info("Upload some OS images if it doesn't exist")
137         for image_name, image_file in self.images.iteritems():
138             self.__logger.info("image: %s, file: %s", image_name, image_file)
139             if image_file and image_name:
140                 image_creator = OpenStackImage(
141                     self.snaps_creds,
142                     ImageConfig(
143                         name=image_name, image_user='cloud',
144                         img_format='qcow2', image_file=image_file))
145                 image_creator.create()
146                 self.created_object.append(image_creator)
147
148     def deploy_orchestrator(self):
149         # pylint: disable=too-many-locals,too-many-statements
150         """
151         Deploy Cloudify Manager.
152         network, security group, fip, VM creation
153         """
154         # network creation
155         start_time = time.time()
156         self.__logger.info("Creating keypair ...")
157         kp_file = os.path.join(self.data_dir, "cloudify_vrouter.pem")
158         keypair_settings = KeypairConfig(
159             name='cloudify_vrouter_kp-{}'.format(self.uuid),
160             private_filepath=kp_file)
161         keypair_creator = OpenStackKeypair(self.snaps_creds, keypair_settings)
162         keypair_creator.create()
163         self.created_object.append(keypair_creator)
164
165         self.__logger.info("Creating full network ...")
166         subnet_settings = SubnetConfig(
167             name='cloudify_vrouter_subnet-{}'.format(self.uuid),
168             cidr='10.67.79.0/24')
169         network_settings = NetworkConfig(
170             name='cloudify_vrouter_network-{}'.format(self.uuid),
171             subnet_settings=[subnet_settings])
172         network_creator = OpenStackNetwork(self.snaps_creds, network_settings)
173         network_creator.create()
174         self.created_object.append(network_creator)
175         ext_net_name = snaps_utils.get_ext_net_name(self.snaps_creds)
176         router_creator = OpenStackRouter(
177             self.snaps_creds,
178             RouterConfig(
179                 name='cloudify_vrouter_router-{}'.format(self.uuid),
180                 external_gateway=ext_net_name,
181                 internal_subnets=[subnet_settings.name]))
182         router_creator.create()
183         self.created_object.append(router_creator)
184
185         # security group creation
186         self.__logger.info("Creating security group for cloudify manager vm")
187         sg_rules = list()
188         sg_rules.append(
189             SecurityGroupRuleConfig(
190                 sec_grp_name="sg-cloudify-manager-{}".format(self.uuid),
191                 direction=Direction.ingress,
192                 protocol=Protocol.tcp, port_range_min=1,
193                 port_range_max=65535))
194         sg_rules.append(
195             SecurityGroupRuleConfig(
196                 sec_grp_name="sg-cloudify-manager-{}".format(self.uuid),
197                 direction=Direction.ingress,
198                 protocol=Protocol.udp, port_range_min=1,
199                 port_range_max=65535))
200
201         security_group_creator = OpenStackSecurityGroup(
202             self.snaps_creds,
203             SecurityGroupConfig(
204                 name="sg-cloudify-manager-{}".format(self.uuid),
205                 rule_settings=sg_rules))
206
207         security_group_creator.create()
208         self.created_object.append(security_group_creator)
209
210         # orchestrator VM flavor
211         self.__logger.info("Get or create flavor for cloudify manager vm ...")
212
213         flavor_settings = FlavorConfig(
214             name=self.orchestrator['requirements']['flavor']['name'],
215             ram=self.orchestrator['requirements']['flavor']['ram_min'],
216             disk=50, vcpus=2)
217         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
218         flavor_creator.create()
219         self.created_object.append(flavor_creator)
220         image_settings = ImageConfig(
221             name=self.orchestrator['requirements']['os_image'],
222             image_user='centos', exists=True)
223
224         port_settings = PortConfig(
225             name='cloudify_manager_port-{}'.format(self.uuid),
226             network_name=network_settings.name)
227
228         manager_settings = VmInstanceConfig(
229             name='cloudify_manager-{}'.format(self.uuid),
230             flavor=flavor_settings.name,
231             port_settings=[port_settings],
232             security_group_names=[
233                 security_group_creator.sec_grp_settings.name],
234             floating_ip_settings=[FloatingIpConfig(
235                 name='cloudify_manager_fip-{}'.format(self.uuid),
236                 port_name=port_settings.name,
237                 router_name=router_creator.router_settings.name)])
238
239         manager_creator = OpenStackVmInstance(
240             self.snaps_creds, manager_settings, image_settings,
241             keypair_settings)
242
243         self.__logger.info("Creating cloudify manager VM")
244         manager_creator.create()
245         self.created_object.append(manager_creator)
246
247         cfy_client = CloudifyClient(
248             host=manager_creator.get_floating_ip().ip,
249             username='admin', password='admin', tenant='default_tenant')
250
251         self.orchestrator['object'] = cfy_client
252
253         self.cfy_manager_ip = manager_creator.get_floating_ip().ip
254
255         self.__logger.info("Attemps running status of the Manager")
256         cfy_status = None
257         retry = 10
258         while str(cfy_status) != 'running' and retry:
259             try:
260                 cfy_status = cfy_client.manager.get_status()['status']
261                 self.__logger.debug("The current manager status is %s",
262                                     cfy_status)
263             except Exception:  # pylint: disable=broad-except
264                 self.__logger.exception(
265                     "Cloudify Manager isn't up and running. Retrying ...")
266             retry = retry - 1
267             time.sleep(30)
268
269         if str(cfy_status) == 'running':
270             self.__logger.info("Cloudify Manager is up and running")
271         else:
272             raise Exception("Cloudify Manager isn't up and running")
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             self.run_blocking_ssh_command(ssh, cmd)
283             cmd = "sudo chmod 444 /etc/cloudify/cloudify_vrouter.pem"
284             self.run_blocking_ssh_command(ssh, cmd)
285             cmd = "sudo yum install -y gcc python-devel"
286             self.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(
304             self.data_dir, self.util.blueprint_dir)
305         if not os.path.exists(vrouter_blueprint_dir):
306             Repo.clone_from(
307                 descriptor.get('url'), vrouter_blueprint_dir,
308                 branch=descriptor.get('version'))
309
310         cfy_client.blueprints.upload(
311             vrouter_blueprint_dir + 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, vcpus=1)
319         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
320         flavor = flavor_creator.create()
321         self.created_object.append(flavor_creator)
322
323         # set image name
324         glance = glance_utils.glance_client(self.snaps_creds)
325         image = glance_utils.get_image(glance, "vyos1.1.7")
326
327         self.vnf['inputs'].update(dict(target_vnf_image_id=image.id))
328         self.vnf['inputs'].update(dict(reference_vnf_image_id=image.id))
329         self.vnf['inputs'].update(dict(target_vnf_flavor_id=flavor.id))
330         self.vnf['inputs'].update(dict(reference_vnf_flavor_id=flavor.id))
331         self.vnf['inputs'].update(dict(
332             keystone_username=self.snaps_creds.username))
333         self.vnf['inputs'].update(dict(
334             keystone_password=self.snaps_creds.password))
335         self.vnf['inputs'].update(dict(
336             keystone_tenant_name=self.snaps_creds.project_name))
337         self.vnf['inputs'].update(dict(
338             region=self.snaps_creds.region_name))
339         self.vnf['inputs'].update(dict(
340             keystone_url=keystone_utils.get_endpoint(
341                 self.snaps_creds, 'identity')))
342
343         self.__logger.info("Create VNF Instance")
344         cfy_client.deployments.create(
345             descriptor.get('name'), descriptor.get('name'),
346             self.vnf.get('inputs'))
347
348         wait_for_execution(
349             cfy_client, get_execution_id(cfy_client, descriptor.get('name')),
350             self.__logger, 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 = {"snaps_creds": self.snaps_creds,
372                        "username": self.snaps_creds.username,
373                        "password": self.snaps_creds.password,
374                        "auth_url": self.snaps_creds.auth_url,
375                        "tenant_name": self.snaps_creds.project_name}
376
377         self.util_info = {"credentials": credentials,
378                           "cfy": cfy_client,
379                           "vnf_data_dir": self.util.vnf_data_dir}
380
381         start_time = time.time()
382
383         result, test_result_data = super(CloudifyVrouter, self).test_vnf()
384
385         duration = time.time() - start_time
386
387         if result:
388             self.details['test_vnf'].update(
389                 status='PASS', result='OK', full_result=test_result_data,
390                 duration=duration)
391         else:
392             self.details['test_vnf'].update(
393                 status='FAIL', result='NG', full_result=test_result_data,
394                 duration=duration)
395
396         return True
397
398     def clean(self):
399         try:
400             cfy_client = self.orchestrator['object']
401             dep_name = self.vnf['descriptor'].get('name')
402             # kill existing execution
403             self.__logger.info('Deleting the current deployment')
404             exec_list = cfy_client.executions.list(dep_name)
405             for execution in exec_list:
406                 if execution['status'] == "started":
407                     try:
408                         cfy_client.executions.cancel(
409                             execution['id'], force=True)
410                     except Exception:  # pylint: disable=broad-except
411                         self.__logger.warn("Can't cancel the current exec")
412
413             execution = cfy_client.executions.start(
414                 dep_name, 'uninstall', parameters=dict(ignore_failure=True))
415
416             wait_for_execution(cfy_client, execution, self.__logger)
417             cfy_client.deployments.delete(self.vnf['descriptor'].get('name'))
418             cfy_client.blueprints.delete(self.vnf['descriptor'].get('name'))
419         except Exception:  # pylint: disable=broad-except
420             self.__logger.warn("Some issue during the undeployment ..")
421             self.__logger.warn("Tenant clean continue ..")
422         super(CloudifyVrouter, self).clean()
423
424     def get_vnf_info_list(self, target_vnf_name):
425         return self.util.get_vnf_info_list(
426             self.cfy_manager_ip, self.deployment_name, target_vnf_name)
427
428
429 def wait_for_execution(client, execution, logger, timeout=7200, ):
430     """Wait for a workflow execution on Cloudify Manager."""
431     # if execution already ended - return without waiting
432     if execution.status in Execution.END_STATES:
433         return execution
434
435     if timeout is not None:
436         deadline = time.time() + timeout
437
438     # Poll for execution status and execution logs, until execution ends
439     # and we receive an event of type in WORKFLOW_END_TYPES
440     offset = 0
441     batch_size = 50
442     event_list = []
443     execution_ended = False
444     while True:
445         event_list = client.events.list(
446             execution_id=execution.id, _offset=offset, _size=batch_size,
447             include_logs=False, sort='@timestamp').items
448
449         offset = offset + len(event_list)
450         for event in event_list:
451             logger.debug(event.get('message'))
452
453         if timeout is not None:
454             if time.time() > deadline:
455                 raise RuntimeError(
456                     'execution of operation {0} for deployment {1} '
457                     'timed out'.format(execution.workflow_id,
458                                        execution.deployment_id))
459             else:
460                 # update the remaining timeout
461                 timeout = deadline - time.time()
462
463         if not execution_ended:
464             execution = client.executions.get(execution.id)
465             execution_ended = execution.status in Execution.END_STATES
466
467         if execution_ended:
468             break
469
470         time.sleep(5)
471
472     return execution
473
474
475 def get_execution_id(client, deployment_id):
476     """
477     Get the execution id of a env preparation.
478     network, security group, fip, VM creation
479     """
480     executions = client.executions.list(deployment_id=deployment_id)
481     for execution in executions:
482         if execution.workflow_id == 'create_deployment_environment':
483             return execution
484     raise RuntimeError('Failed to get create_deployment_environment '
485                        'workflow execution.'
486                        'Available executions: {0}'.format(executions))