9a17268e1f6effcbce4ff9c637473c6a43b61f89
[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             print cfy_client.manager.get_status()
269             try:
270                 cfy_status = cfy_client.manager.get_status()['status']
271                 self.__logger.debug("The current manager status is %s",
272                                     cfy_status)
273             except Exception:  # pylint: disable=broad-except
274                 self.__logger.exception(
275                     "Cloudify Manager isn't up and running. Retrying ...")
276             retry = retry - 1
277             time.sleep(30)
278
279         if str(cfy_status) == 'running':
280             self.__logger.info("Cloudify Manager is up and running")
281         else:
282             raise Exception("Cloudify Manager isn't up and running")
283
284         self.__logger.info("Put OpenStack creds in manager")
285         secrets_list = cfy_client.secrets.list()
286         for k, val in cfy_creds.iteritems():
287             if not any(d.get('key', None) == k for d in secrets_list):
288                 cfy_client.secrets.create(k, val)
289             else:
290                 cfy_client.secrets.update(k, val)
291
292         duration = time.time() - start_time
293
294         self.__logger.info("Put private keypair in manager")
295         if manager_creator.vm_ssh_active(block=True):
296             ssh = manager_creator.ssh_client()
297             scp = SCPClient(ssh.get_transport(), socket_timeout=15.0)
298             scp.put(kp_file, '~/')
299             cmd = "sudo cp ~/cloudify_vrouter.pem /etc/cloudify/"
300             run_blocking_ssh_command(ssh, cmd)
301             cmd = "sudo chmod 444 /etc/cloudify/cloudify_vrouter.pem"
302             run_blocking_ssh_command(ssh, cmd)
303             cmd = "sudo yum install -y gcc python-devel"
304             run_blocking_ssh_command(
305                 ssh, cmd, "Unable to install packages on manager")
306
307         self.details['orchestrator'].update(status='PASS', duration=duration)
308
309         self.vnf['inputs'].update(dict(external_network_name=ext_net_name))
310
311         return True
312
313     def deploy_vnf(self):
314         start_time = time.time()
315
316         self.__logger.info("Upload VNFD")
317         cfy_client = self.orchestrator['object']
318         descriptor = self.vnf['descriptor']
319         self.deployment_name = descriptor.get('name')
320
321         vrouter_blueprint_dir = os.path.join(self.data_dir,
322                                              self.util.blueprint_dir)
323         if not os.path.exists(vrouter_blueprint_dir):
324             Repo.clone_from(descriptor.get('url'),
325                             vrouter_blueprint_dir,
326                             branch=descriptor.get('version'))
327
328         cfy_client.blueprints.upload(vrouter_blueprint_dir +
329                                      self.util.blueprint_file_name,
330                                      descriptor.get('name'))
331
332         self.__logger.info("Get or create flavor for vrouter")
333         flavor_settings = FlavorConfig(
334             name=self.vnf['requirements']['flavor']['name'],
335             ram=self.vnf['requirements']['flavor']['ram_min'],
336             disk=25,
337             vcpus=1)
338         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
339         flavor = flavor_creator.create()
340         self.created_object.append(flavor_creator)
341
342         # set image name
343         glance = glance_utils.glance_client(self.snaps_creds)
344         image = glance_utils.get_image(glance,
345                                        "vyos1.1.7")
346         self.vnf['inputs'].update(dict(target_vnf_image_id=image.id))
347         self.vnf['inputs'].update(dict(reference_vnf_image_id=image.id))
348
349         # set flavor id
350         self.vnf['inputs'].update(dict(target_vnf_flavor_id=flavor.id))
351         self.vnf['inputs'].update(dict(reference_vnf_flavor_id=flavor.id))
352
353         self.vnf['inputs'].update(dict(keystone_username=self.tenant_name))
354         self.vnf['inputs'].update(dict(keystone_password=self.tenant_name))
355         self.vnf['inputs'].update(dict(keystone_tenant_name=self.tenant_name))
356         self.vnf['inputs'].update(
357             dict(keystone_url=keystone_utils.get_endpoint(
358                 self.snaps_creds, 'identity')))
359
360         self.__logger.info("Create VNF Instance")
361         cfy_client.deployments.create(descriptor.get('name'),
362                                       descriptor.get('name'),
363                                       self.vnf.get('inputs'))
364
365         wait_for_execution(cfy_client,
366                            get_execution_id(
367                                cfy_client, descriptor.get('name')),
368                            self.__logger,
369                            timeout=7200)
370
371         self.__logger.info("Start the VNF Instance deployment")
372         execution = cfy_client.executions.start(descriptor.get('name'),
373                                                 'install')
374         # Show execution log
375         execution = wait_for_execution(cfy_client, execution, self.__logger)
376
377         duration = time.time() - start_time
378
379         self.__logger.info(execution)
380         if execution.status == 'terminated':
381             self.details['vnf'].update(status='PASS', duration=duration)
382             result = True
383         else:
384             self.details['vnf'].update(status='FAIL', duration=duration)
385             result = False
386         return result
387
388     def test_vnf(self):
389         cfy_client = self.orchestrator['object']
390         credentials = {"snaps_creds": self.snaps_creds,
391                        "username": self.snaps_creds.username,
392                        "password": self.snaps_creds.password,
393                        "auth_url": self.snaps_creds.auth_url,
394                        "tenant_name": self.snaps_creds.project_name}
395
396         self.util_info = {"credentials": credentials,
397                           "cfy": cfy_client,
398                           "vnf_data_dir": self.util.vnf_data_dir}
399
400         start_time = time.time()
401
402         result, test_result_data = super(CloudifyVrouter, self).test_vnf()
403
404         duration = time.time() - start_time
405
406         if result:
407             self.details['test_vnf'].update(status='PASS',
408                                             result='OK',
409                                             full_result=test_result_data,
410                                             duration=duration)
411         else:
412             self.details['test_vnf'].update(status='FAIL',
413                                             result='NG',
414                                             full_result=test_result_data,
415                                             duration=duration)
416
417         return True
418
419     def clean(self):
420         try:
421             cfy_client = self.orchestrator['object']
422             dep_name = self.vnf['descriptor'].get('name')
423             # kill existing execution
424             self.__logger.info('Deleting the current deployment')
425             exec_list = cfy_client.executions.list(dep_name)
426             for execution in exec_list:
427                 if execution['status'] == "started":
428                     try:
429                         cfy_client.executions.cancel(execution['id'],
430                                                      force=True)
431                     except Exception:  # pylint: disable=broad-except
432                         self.__logger.warn("Can't cancel the current exec")
433
434             execution = cfy_client.executions.start(
435                 dep_name,
436                 'uninstall',
437                 parameters=dict(ignore_failure=True))
438
439             wait_for_execution(cfy_client, execution, self.__logger)
440             cfy_client.deployments.delete(self.vnf['descriptor'].get('name'))
441             cfy_client.blueprints.delete(self.vnf['descriptor'].get('name'))
442         except Exception:  # pylint: disable=broad-except
443             self.__logger.warn("Some issue during the undeployment ..")
444             self.__logger.warn("Tenant clean continue ..")
445         super(CloudifyVrouter, self).clean()
446
447     def get_vnf_info_list(self, target_vnf_name):
448         return self.util.get_vnf_info_list(
449             self.cfy_manager_ip, self.deployment_name, target_vnf_name)
450
451
452 def wait_for_execution(client, execution, logger, timeout=7200, ):
453     """Wait for a workflow execution on Cloudify Manager."""
454     # if execution already ended - return without waiting
455     if execution.status in Execution.END_STATES:
456         return execution
457
458     if timeout is not None:
459         deadline = time.time() + timeout
460
461     # Poll for execution status and execution logs, until execution ends
462     # and we receive an event of type in WORKFLOW_END_TYPES
463     offset = 0
464     batch_size = 50
465     event_list = []
466     execution_ended = False
467     while True:
468         event_list = client.events.list(
469             execution_id=execution.id,
470             _offset=offset,
471             _size=batch_size,
472             include_logs=False,
473             sort='@timestamp').items
474
475         offset = offset + len(event_list)
476         for event in event_list:
477             logger.debug(event.get('message'))
478
479         if timeout is not None:
480             if time.time() > deadline:
481                 raise RuntimeError(
482                     'execution of operation {0} for deployment {1} '
483                     'timed out'.format(execution.workflow_id,
484                                        execution.deployment_id))
485             else:
486                 # update the remaining timeout
487                 timeout = deadline - time.time()
488
489         if not execution_ended:
490             execution = client.executions.get(execution.id)
491             execution_ended = execution.status in Execution.END_STATES
492
493         if execution_ended:
494             break
495
496         time.sleep(5)
497
498     return execution
499
500
501 def get_execution_id(client, deployment_id):
502     """
503     Get the execution id of a env preparation.
504     network, security group, fip, VM creation
505     """
506     executions = client.executions.list(deployment_id=deployment_id)
507     for execution in executions:
508         if execution.workflow_id == 'create_deployment_environment':
509             return execution
510     raise RuntimeError('Failed to get create_deployment_environment '
511                        'workflow execution.'
512                        'Available executions: {0}'.format(executions))
513
514
515 def run_blocking_ssh_command(ssh, cmd, error_msg="Unable to run this command"):
516     """Command to run ssh command with the exit status."""
517     (_, stdout, _) = ssh.exec_command(cmd)
518     if stdout.channel.recv_exit_status() != 0:
519         raise Exception(error_msg)