07033ecdd081a601b3cd801726906c6ddfce81ce
[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 = CONST.__getattribute__(
69                 'vnf_{}_config'.format(self.case_name))
70         except Exception:
71             raise Exception("VNF config file not found")
72
73         self.snaps_creds = ''
74         self.created_object = []
75
76         self.cfy_manager_ip = ''
77         self.util_info = {}
78         self.deployment_name = ''
79
80         config_file = os.path.join(self.case_dir, self.config)
81         self.orchestrator = dict(
82             requirements=functest_utils.get_parameter_from_yaml(
83                 "orchestrator.requirements", config_file),
84         )
85         self.details['orchestrator'] = dict(
86             name=functest_utils.get_parameter_from_yaml(
87                 "orchestrator.name", config_file),
88             version=functest_utils.get_parameter_from_yaml(
89                 "orchestrator.version", config_file),
90             status='ERROR',
91             result=''
92         )
93         self.__logger.debug("Orchestrator configuration %s", self.orchestrator)
94         self.__logger.debug("name = %s", self.name)
95         self.vnf = dict(
96             descriptor=functest_utils.get_parameter_from_yaml(
97                 "vnf.descriptor", config_file),
98             inputs=functest_utils.get_parameter_from_yaml(
99                 "vnf.inputs", config_file),
100             requirements=functest_utils.get_parameter_from_yaml(
101                 "vnf.requirements", config_file)
102         )
103         self.details['vnf'] = dict(
104             descriptor_version=self.vnf['descriptor']['version'],
105             name=functest_utils.get_parameter_from_yaml(
106                 "vnf.name", config_file),
107             version=functest_utils.get_parameter_from_yaml(
108                 "vnf.version", config_file),
109         )
110         self.__logger.debug("VNF configuration: %s", self.vnf)
111
112         self.util = Utilvnf()
113
114         self.details['test_vnf'] = dict(
115             name=functest_utils.get_parameter_from_yaml(
116                 "vnf_test_suite.name", config_file),
117             version=functest_utils.get_parameter_from_yaml(
118                 "vnf_test_suite.version", config_file)
119         )
120         self.images = functest_utils.get_parameter_from_yaml(
121             "tenant_images", config_file)
122         self.__logger.info("Images needed for vrouter: %s", self.images)
123
124     def prepare(self):
125         super(CloudifyVrouter, self).prepare()
126
127         self.__logger.info("Additional pre-configuration steps")
128
129         self.util.set_credentials(self.snaps_creds)
130
131         # needs some images
132         self.__logger.info("Upload some OS images if it doesn't exist")
133         for image_name, image_file in self.images.iteritems():
134             self.__logger.info("image: %s, file: %s", image_name, image_file)
135             if image_file and image_name:
136                 image_creator = OpenStackImage(
137                     self.snaps_creds,
138                     ImageConfig(
139                         name=image_name, image_user='cloud',
140                         img_format='qcow2', image_file=image_file))
141                 image_creator.create()
142                 self.created_object.append(image_creator)
143
144     def deploy_orchestrator(self):
145         # pylint: disable=too-many-locals,too-many-statements
146         """
147         Deploy Cloudify Manager.
148         network, security group, fip, VM creation
149         """
150         # network creation
151
152         start_time = time.time()
153         self.__logger.info("Creating keypair ...")
154         kp_file = os.path.join(self.data_dir, "cloudify_vrouter.pem")
155         keypair_settings = KeypairConfig(
156             name='cloudify_vrouter_kp-{}'.format(self.uuid),
157             private_filepath=kp_file)
158         keypair_creator = OpenStackKeypair(self.snaps_creds, keypair_settings)
159         keypair_creator.create()
160         self.created_object.append(keypair_creator)
161
162         self.__logger.info("Creating full network ...")
163         subnet_settings = SubnetConfig(
164             name='cloudify_vrouter_subnet-{}'.format(self.uuid),
165             cidr='10.67.79.0/24')
166         network_settings = NetworkConfig(
167             name='cloudify_vrouter_network-{}'.format(self.uuid),
168             subnet_settings=[subnet_settings])
169         network_creator = OpenStackNetwork(self.snaps_creds, network_settings)
170         network_creator.create()
171         self.created_object.append(network_creator)
172         ext_net_name = snaps_utils.get_ext_net_name(self.snaps_creds)
173         router_creator = OpenStackRouter(
174             self.snaps_creds,
175             RouterConfig(
176                 name='cloudify_vrouter_router-{}'.format(self.uuid),
177                 external_gateway=ext_net_name,
178                 internal_subnets=[subnet_settings.name]))
179         router_creator.create()
180         self.created_object.append(router_creator)
181
182         # security group creation
183         self.__logger.info("Creating security group for cloudify manager vm")
184         sg_rules = list()
185         sg_rules.append(
186             SecurityGroupRuleConfig(
187                 sec_grp_name="sg-cloudify-manager-{}".format(self.uuid),
188                 direction=Direction.ingress,
189                 protocol=Protocol.tcp, port_range_min=1,
190                 port_range_max=65535))
191         sg_rules.append(
192             SecurityGroupRuleConfig(
193                 sec_grp_name="sg-cloudify-manager-{}".format(self.uuid),
194                 direction=Direction.ingress,
195                 protocol=Protocol.udp, port_range_min=1,
196                 port_range_max=65535))
197
198         security_group_creator = OpenStackSecurityGroup(
199             self.snaps_creds,
200             SecurityGroupConfig(
201                 name="sg-cloudify-manager-{}".format(self.uuid),
202                 rule_settings=sg_rules))
203
204         security_group_creator.create()
205         self.created_object.append(security_group_creator)
206
207         # orchestrator VM flavor
208         self.__logger.info("Get or create flavor for cloudify manager vm ...")
209
210         flavor_settings = FlavorConfig(
211             name=self.orchestrator['requirements']['flavor']['name'],
212             ram=self.orchestrator['requirements']['flavor']['ram_min'],
213             disk=50,
214             vcpus=2)
215         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
216         flavor_creator.create()
217         self.created_object.append(flavor_creator)
218         image_settings = ImageConfig(
219             name=self.orchestrator['requirements']['os_image'],
220             image_user='centos',
221             exists=True)
222
223         port_settings = PortConfig(
224             name='cloudify_manager_port-{}'.format(self.uuid),
225             network_name=network_settings.name)
226
227         manager_settings = VmInstanceConfig(
228             name='cloudify_manager-{}'.format(self.uuid),
229             flavor=flavor_settings.name,
230             port_settings=[port_settings],
231             security_group_names=[
232                 security_group_creator.sec_grp_settings.name],
233             floating_ip_settings=[FloatingIpConfig(
234                 name='cloudify_manager_fip-{}'.format(self.uuid),
235                 port_name=port_settings.name,
236                 router_name=router_creator.router_settings.name)])
237
238         manager_creator = OpenStackVmInstance(
239             self.snaps_creds, manager_settings, image_settings,
240             keypair_settings)
241
242         self.__logger.info("Creating cloudify manager VM")
243         manager_creator.create()
244         self.created_object.append(manager_creator)
245
246         public_auth_url = keystone_utils.get_endpoint(
247             self.snaps_creds, 'identity')
248
249         self.__logger.info("Set creds for cloudify manager")
250         cfy_creds = dict(
251             keystone_username=self.snaps_creds.username,
252             keystone_password=self.snaps_creds.password,
253             keystone_tenant_name=self.snaps_creds.project_name,
254             keystone_url=public_auth_url)
255
256         cfy_client = CloudifyClient(
257             host=manager_creator.get_floating_ip().ip,
258             username='admin', password='admin', tenant='default_tenant')
259
260         self.orchestrator['object'] = cfy_client
261
262         self.cfy_manager_ip = manager_creator.get_floating_ip().ip
263
264         self.__logger.info("Attemps running status of the Manager")
265         cfy_status = None
266         retry = 10
267         while str(cfy_status) != 'running' and retry:
268             try:
269                 cfy_status = cfy_client.manager.get_status()['status']
270                 self.__logger.debug("The current manager status is %s",
271                                     cfy_status)
272             except Exception:  # pylint: disable=broad-except
273                 self.__logger.exception(
274                     "Cloudify Manager isn't up and running. Retrying ...")
275             retry = retry - 1
276             time.sleep(30)
277
278         if str(cfy_status) == 'running':
279             self.__logger.info("Cloudify Manager is up and running")
280         else:
281             raise Exception("Cloudify Manager isn't up and running")
282
283         self.__logger.info("Put OpenStack creds in manager")
284         secrets_list = cfy_client.secrets.list()
285         for k, val in cfy_creds.iteritems():
286             if not any(d.get('key', None) == k for d in secrets_list):
287                 cfy_client.secrets.create(k, val)
288             else:
289                 cfy_client.secrets.update(k, val)
290
291         duration = time.time() - start_time
292
293         self.__logger.info("Put private keypair in manager")
294         if manager_creator.vm_ssh_active(block=True):
295             ssh = manager_creator.ssh_client()
296             scp = SCPClient(ssh.get_transport(), socket_timeout=15.0)
297             scp.put(kp_file, '~/')
298             cmd = "sudo cp ~/cloudify_vrouter.pem /etc/cloudify/"
299             run_blocking_ssh_command(ssh, cmd)
300             cmd = "sudo chmod 444 /etc/cloudify/cloudify_vrouter.pem"
301             run_blocking_ssh_command(ssh, cmd)
302             cmd = "sudo yum install -y gcc python-devel"
303             run_blocking_ssh_command(
304                 ssh, cmd, "Unable to install packages on manager")
305
306         self.details['orchestrator'].update(status='PASS', duration=duration)
307
308         self.vnf['inputs'].update(dict(external_network_name=ext_net_name))
309
310         return True
311
312     def deploy_vnf(self):
313         start_time = time.time()
314
315         self.__logger.info("Upload VNFD")
316         cfy_client = self.orchestrator['object']
317         descriptor = self.vnf['descriptor']
318         self.deployment_name = descriptor.get('name')
319
320         vrouter_blueprint_dir = os.path.join(self.data_dir,
321                                              self.util.blueprint_dir)
322         if not os.path.exists(vrouter_blueprint_dir):
323             Repo.clone_from(descriptor.get('url'),
324                             vrouter_blueprint_dir,
325                             branch=descriptor.get('version'))
326
327         cfy_client.blueprints.upload(vrouter_blueprint_dir +
328                                      self.util.blueprint_file_name,
329                                      descriptor.get('name'))
330
331         self.__logger.info("Get or create flavor for vrouter")
332         flavor_settings = FlavorConfig(
333             name=self.vnf['requirements']['flavor']['name'],
334             ram=self.vnf['requirements']['flavor']['ram_min'],
335             disk=25,
336             vcpus=1)
337         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
338         flavor = flavor_creator.create()
339         self.created_object.append(flavor_creator)
340
341         # set image name
342         glance = glance_utils.glance_client(self.snaps_creds)
343         image = glance_utils.get_image(glance,
344                                        "vyos1.1.7")
345         self.vnf['inputs'].update(dict(target_vnf_image_id=image.id))
346         self.vnf['inputs'].update(dict(reference_vnf_image_id=image.id))
347
348         # set flavor id
349         self.vnf['inputs'].update(dict(target_vnf_flavor_id=flavor.id))
350         self.vnf['inputs'].update(dict(reference_vnf_flavor_id=flavor.id))
351
352         self.vnf['inputs'].update(dict(keystone_username=self.tenant_name))
353         self.vnf['inputs'].update(dict(keystone_password=self.tenant_name))
354         self.vnf['inputs'].update(dict(keystone_tenant_name=self.tenant_name))
355         self.vnf['inputs'].update(
356             dict(keystone_url=keystone_utils.get_endpoint(
357                 self.snaps_creds, 'identity')))
358
359         self.__logger.info("Create VNF Instance")
360         cfy_client.deployments.create(descriptor.get('name'),
361                                       descriptor.get('name'),
362                                       self.vnf.get('inputs'))
363
364         wait_for_execution(cfy_client,
365                            get_execution_id(
366                                cfy_client, descriptor.get('name')),
367                            self.__logger,
368                            timeout=7200)
369
370         self.__logger.info("Start the VNF Instance deployment")
371         execution = cfy_client.executions.start(descriptor.get('name'),
372                                                 'install')
373         # Show execution log
374         execution = wait_for_execution(cfy_client, execution, self.__logger)
375
376         duration = time.time() - start_time
377
378         self.__logger.info(execution)
379         if execution.status == 'terminated':
380             self.details['vnf'].update(status='PASS', duration=duration)
381             result = True
382         else:
383             self.details['vnf'].update(status='FAIL', duration=duration)
384             result = False
385         return result
386
387     def test_vnf(self):
388         cfy_client = self.orchestrator['object']
389         credentials = {"snaps_creds": self.snaps_creds,
390                        "username": self.snaps_creds.username,
391                        "password": self.snaps_creds.password,
392                        "auth_url": self.snaps_creds.auth_url,
393                        "tenant_name": self.snaps_creds.project_name}
394
395         self.util_info = {"credentials": credentials,
396                           "cfy": cfy_client,
397                           "vnf_data_dir": self.util.vnf_data_dir}
398
399         start_time = time.time()
400
401         result, test_result_data = super(CloudifyVrouter, self).test_vnf()
402
403         duration = time.time() - start_time
404
405         if result:
406             self.details['test_vnf'].update(status='PASS',
407                                             result='OK',
408                                             full_result=test_result_data,
409                                             duration=duration)
410         else:
411             self.details['test_vnf'].update(status='FAIL',
412                                             result='NG',
413                                             full_result=test_result_data,
414                                             duration=duration)
415
416         return True
417
418     def clean(self):
419         try:
420             cfy_client = self.orchestrator['object']
421             dep_name = self.vnf['descriptor'].get('name')
422             # kill existing execution
423             self.__logger.info('Deleting the current deployment')
424             exec_list = cfy_client.executions.list(dep_name)
425             for execution in exec_list:
426                 if execution['status'] == "started":
427                     try:
428                         cfy_client.executions.cancel(execution['id'],
429                                                      force=True)
430                     except Exception:  # pylint: disable=broad-except
431                         self.__logger.warn("Can't cancel the current exec")
432
433             execution = cfy_client.executions.start(
434                 dep_name,
435                 'uninstall',
436                 parameters=dict(ignore_failure=True))
437
438             wait_for_execution(cfy_client, execution, self.__logger)
439             cfy_client.deployments.delete(self.vnf['descriptor'].get('name'))
440             cfy_client.blueprints.delete(self.vnf['descriptor'].get('name'))
441         except Exception:  # pylint: disable=broad-except
442             self.__logger.warn("Some issue during the undeployment ..")
443             self.__logger.warn("Tenant clean continue ..")
444         super(CloudifyVrouter, self).clean()
445
446     def get_vnf_info_list(self, target_vnf_name):
447         return self.util.get_vnf_info_list(
448             self.cfy_manager_ip, self.deployment_name, target_vnf_name)
449
450
451 def wait_for_execution(client, execution, logger, timeout=7200, ):
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_execution_id(client, deployment_id):
501     """
502     Get the execution id of a env preparation.
503     network, security group, fip, VM creation
504     """
505     executions = client.executions.list(deployment_id=deployment_id)
506     for execution in executions:
507         if execution.workflow_id == 'create_deployment_environment':
508             return execution
509     raise RuntimeError('Failed to get create_deployment_environment '
510                        'workflow execution.'
511                        'Available executions: {0}'.format(executions))
512
513
514 def run_blocking_ssh_command(ssh, cmd, error_msg="Unable to run this command"):
515     """Command to run ssh command with the exit status."""
516     (_, stdout, _) = ssh.exec_command(cmd)
517     if stdout.channel.recv_exit_status() != 0:
518         raise Exception(error_msg)