74c6d398d4e5daee91cf5d2fbb0ea1a0aba1a081
[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(name='cloudify_vrouter_kp',
156                                          private_filepath=kp_file)
157         keypair_creator = OpenStackKeypair(self.snaps_creds, keypair_settings)
158         keypair_creator.create()
159         self.created_object.append(keypair_creator)
160
161         self.__logger.info("Creating full network ...")
162         subnet_settings = SubnetConfig(
163             name='cloudify_vrouter_subnet-{}'.format(self.uuid),
164             cidr='10.67.79.0/24')
165         network_settings = NetworkConfig(
166             name='cloudify_vrouter_network-{}'.format(self.uuid),
167             subnet_settings=[subnet_settings])
168         network_creator = OpenStackNetwork(self.snaps_creds, network_settings)
169         network_creator.create()
170         self.created_object.append(network_creator)
171         ext_net_name = snaps_utils.get_ext_net_name(self.snaps_creds)
172         router_creator = OpenStackRouter(
173             self.snaps_creds,
174             RouterConfig(
175                 name='cloudify_vrouter_router-{}'.format(self.uuid),
176                 external_gateway=ext_net_name,
177                 internal_subnets=[subnet_settings.name]))
178         router_creator.create()
179         self.created_object.append(router_creator)
180
181         # security group creation
182         self.__logger.info("Creating security group for cloudify manager vm")
183         sg_rules = list()
184         sg_rules.append(
185             SecurityGroupRuleConfig(
186                 sec_grp_name="sg-cloudify-manager-{}".format(self.uuid),
187                 direction=Direction.ingress,
188                 protocol=Protocol.tcp, port_range_min=1,
189                 port_range_max=65535))
190         sg_rules.append(
191             SecurityGroupRuleConfig(
192                 sec_grp_name="sg-cloudify-manager-{}".format(self.uuid),
193                 direction=Direction.ingress,
194                 protocol=Protocol.udp, port_range_min=1,
195                 port_range_max=65535))
196
197         security_group_creator = OpenStackSecurityGroup(
198             self.snaps_creds,
199             SecurityGroupConfig(
200                 name="sg-cloudify-manager-{}".format(self.uuid),
201                 rule_settings=sg_rules))
202
203         security_group_creator.create()
204         self.created_object.append(security_group_creator)
205
206         # orchestrator VM flavor
207         self.__logger.info("Get or create flavor for cloudify manager vm ...")
208
209         flavor_settings = FlavorConfig(
210             name=self.orchestrator['requirements']['flavor']['name'],
211             ram=self.orchestrator['requirements']['flavor']['ram_min'],
212             disk=50,
213             vcpus=2)
214         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
215         flavor_creator.create()
216         self.created_object.append(flavor_creator)
217         image_settings = ImageConfig(
218             name=self.orchestrator['requirements']['os_image'],
219             image_user='centos',
220             exists=True)
221
222         port_settings = PortConfig(
223             name='cloudify_manager_port-{}'.format(self.uuid),
224             network_name=network_settings.name)
225
226         manager_settings = VmInstanceConfig(
227             name='cloudify_manager-{}'.format(self.uuid),
228             flavor=flavor_settings.name,
229             port_settings=[port_settings],
230             security_group_names=[
231                 security_group_creator.sec_grp_settings.name],
232             floating_ip_settings=[FloatingIpConfig(
233                 name='cloudify_manager_fip-{}'.format(self.uuid),
234                 port_name=port_settings.name,
235                 router_name=router_creator.router_settings.name)])
236
237         manager_creator = OpenStackVmInstance(
238             self.snaps_creds, manager_settings, image_settings,
239             keypair_settings)
240
241         self.__logger.info("Creating cloudify manager VM")
242         manager_creator.create()
243         self.created_object.append(manager_creator)
244
245         public_auth_url = keystone_utils.get_endpoint(
246             self.snaps_creds, 'identity')
247
248         self.__logger.info("Set creds for cloudify manager")
249         cfy_creds = dict(keystone_username=self.tenant_name,
250                          keystone_password=self.tenant_name,
251                          keystone_tenant_name=self.tenant_name,
252                          keystone_url=public_auth_url)
253
254         cfy_client = CloudifyClient(host=manager_creator.get_floating_ip().ip,
255                                     username='admin',
256                                     password='admin',
257                                     tenant='default_tenant')
258
259         self.orchestrator['object'] = cfy_client
260
261         self.cfy_manager_ip = manager_creator.get_floating_ip().ip
262
263         self.__logger.info("Attemps running status of the Manager")
264         cfy_status = None
265         retry = 10
266         while str(cfy_status) != 'running' and retry:
267             try:
268                 cfy_status = cfy_client.manager.get_status()['status']
269                 self.__logger.debug("The current manager status is %s",
270                                     cfy_status)
271             except Exception:  # pylint: disable=broad-except
272                 self.__logger.warning("Cloudify Manager isn't " +
273                                       "up and running. Retrying ...")
274             retry = retry - 1
275             time.sleep(30)
276
277         if str(cfy_status) == 'running':
278             self.__logger.info("Cloudify Manager is up and running")
279         else:
280             raise Exception("Cloudify Manager isn't up and running")
281
282         self.__logger.info("Put OpenStack creds in manager")
283         secrets_list = cfy_client.secrets.list()
284         for k, val in cfy_creds.iteritems():
285             if not any(d.get('key', None) == k for d in secrets_list):
286                 cfy_client.secrets.create(k, val)
287             else:
288                 cfy_client.secrets.update(k, val)
289
290         duration = time.time() - start_time
291
292         self.__logger.info("Put private keypair in manager")
293         if manager_creator.vm_ssh_active(block=True):
294             ssh = manager_creator.ssh_client()
295             scp = SCPClient(ssh.get_transport(), socket_timeout=15.0)
296             scp.put(kp_file, '~/')
297             cmd = "sudo cp ~/cloudify_vrouter.pem /etc/cloudify/"
298             run_blocking_ssh_command(ssh, cmd)
299             cmd = "sudo chmod 444 /etc/cloudify/cloudify_vrouter.pem"
300             run_blocking_ssh_command(ssh, cmd)
301             cmd = "sudo yum install -y gcc python-devel"
302             run_blocking_ssh_command(
303                 ssh, cmd, "Unable to install packages on manager")
304
305         self.details['orchestrator'].update(status='PASS', duration=duration)
306
307         self.vnf['inputs'].update(dict(external_network_name=ext_net_name))
308
309         return True
310
311     def deploy_vnf(self):
312         start_time = time.time()
313
314         self.__logger.info("Upload VNFD")
315         cfy_client = self.orchestrator['object']
316         descriptor = self.vnf['descriptor']
317         self.deployment_name = descriptor.get('name')
318
319         vrouter_blueprint_dir = os.path.join(self.data_dir,
320                                              self.util.blueprint_dir)
321         if not os.path.exists(vrouter_blueprint_dir):
322             Repo.clone_from(descriptor.get('url'),
323                             vrouter_blueprint_dir,
324                             branch=descriptor.get('version'))
325
326         cfy_client.blueprints.upload(vrouter_blueprint_dir +
327                                      self.util.blueprint_file_name,
328                                      descriptor.get('name'))
329
330         self.__logger.info("Get or create flavor for vrouter")
331         flavor_settings = FlavorConfig(
332             name=self.vnf['requirements']['flavor']['name'],
333             ram=self.vnf['requirements']['flavor']['ram_min'],
334             disk=25,
335             vcpus=1)
336         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
337         flavor = flavor_creator.create()
338         self.created_object.append(flavor_creator)
339
340         # set image name
341         glance = glance_utils.glance_client(self.snaps_creds)
342         image = glance_utils.get_image(glance,
343                                        "vyos1.1.7")
344         self.vnf['inputs'].update(dict(target_vnf_image_id=image.id))
345         self.vnf['inputs'].update(dict(reference_vnf_image_id=image.id))
346
347         # set flavor id
348         self.vnf['inputs'].update(dict(target_vnf_flavor_id=flavor.id))
349         self.vnf['inputs'].update(dict(reference_vnf_flavor_id=flavor.id))
350
351         self.vnf['inputs'].update(dict(keystone_username=self.tenant_name))
352         self.vnf['inputs'].update(dict(keystone_password=self.tenant_name))
353         self.vnf['inputs'].update(dict(keystone_tenant_name=self.tenant_name))
354         self.vnf['inputs'].update(
355             dict(keystone_url=keystone_utils.get_endpoint(
356                 self.snaps_creds, 'identity')))
357
358         self.__logger.info("Create VNF Instance")
359         cfy_client.deployments.create(descriptor.get('name'),
360                                       descriptor.get('name'),
361                                       self.vnf.get('inputs'))
362
363         wait_for_execution(cfy_client,
364                            get_execution_id(
365                                cfy_client, descriptor.get('name')),
366                            self.__logger,
367                            timeout=7200)
368
369         self.__logger.info("Start the VNF Instance deployment")
370         execution = cfy_client.executions.start(descriptor.get('name'),
371                                                 'install')
372         # Show execution log
373         execution = wait_for_execution(cfy_client, execution, self.__logger)
374
375         duration = time.time() - start_time
376
377         self.__logger.info(execution)
378         if execution.status == 'terminated':
379             self.details['vnf'].update(status='PASS', duration=duration)
380             result = True
381         else:
382             self.details['vnf'].update(status='FAIL', duration=duration)
383             result = False
384         return result
385
386     def test_vnf(self):
387         cfy_client = self.orchestrator['object']
388         credentials = {"snaps_creds": self.snaps_creds,
389                        "username": self.snaps_creds.username,
390                        "password": self.snaps_creds.password,
391                        "auth_url": self.snaps_creds.auth_url,
392                        "tenant_name": self.snaps_creds.project_name}
393
394         self.util_info = {"credentials": credentials,
395                           "cfy": cfy_client,
396                           "vnf_data_dir": self.util.vnf_data_dir}
397
398         start_time = time.time()
399
400         result, test_result_data = super(CloudifyVrouter, self).test_vnf()
401
402         duration = time.time() - start_time
403
404         if result:
405             self.details['test_vnf'].update(status='PASS',
406                                             result='OK',
407                                             full_result=test_result_data,
408                                             duration=duration)
409         else:
410             self.details['test_vnf'].update(status='FAIL',
411                                             result='NG',
412                                             full_result=test_result_data,
413                                             duration=duration)
414
415         return True
416
417     def clean(self):
418         try:
419             cfy_client = self.orchestrator['object']
420             dep_name = self.vnf['descriptor'].get('name')
421             # kill existing execution
422             self.__logger.info('Deleting the current deployment')
423             exec_list = cfy_client.executions.list(dep_name)
424             for execution in exec_list:
425                 if execution['status'] == "started":
426                     try:
427                         cfy_client.executions.cancel(execution['id'],
428                                                      force=True)
429                     except Exception:  # pylint: disable=broad-except
430                         self.__logger.warn("Can't cancel the current exec")
431
432             execution = cfy_client.executions.start(
433                 dep_name,
434                 'uninstall',
435                 parameters=dict(ignore_failure=True))
436
437             wait_for_execution(cfy_client, execution, self.__logger)
438             cfy_client.deployments.delete(self.vnf['descriptor'].get('name'))
439             cfy_client.blueprints.delete(self.vnf['descriptor'].get('name'))
440         except Exception:  # pylint: disable=broad-except
441             self.__logger.warn("Some issue during the undeployment ..")
442             self.__logger.warn("Tenant clean continue ..")
443         super(CloudifyVrouter, self).clean()
444
445     def get_vnf_info_list(self, target_vnf_name):
446         return self.util.get_vnf_info_list(
447             self.cfy_manager_ip, self.deployment_name, target_vnf_name)
448
449
450 def wait_for_execution(client, execution, logger, timeout=7200, ):
451     """Wait for a workflow execution on Cloudify Manager."""
452     # if execution already ended - return without waiting
453     if execution.status in Execution.END_STATES:
454         return execution
455
456     if timeout is not None:
457         deadline = time.time() + timeout
458
459     # Poll for execution status and execution logs, until execution ends
460     # and we receive an event of type in WORKFLOW_END_TYPES
461     offset = 0
462     batch_size = 50
463     event_list = []
464     execution_ended = False
465     while True:
466         event_list = client.events.list(
467             execution_id=execution.id,
468             _offset=offset,
469             _size=batch_size,
470             include_logs=False,
471             sort='@timestamp').items
472
473         offset = offset + len(event_list)
474         for event in event_list:
475             logger.debug(event.get('message'))
476
477         if timeout is not None:
478             if time.time() > deadline:
479                 raise RuntimeError(
480                     'execution of operation {0} for deployment {1} '
481                     'timed out'.format(execution.workflow_id,
482                                        execution.deployment_id))
483             else:
484                 # update the remaining timeout
485                 timeout = deadline - time.time()
486
487         if not execution_ended:
488             execution = client.executions.get(execution.id)
489             execution_ended = execution.status in Execution.END_STATES
490
491         if execution_ended:
492             break
493
494         time.sleep(5)
495
496     return execution
497
498
499 def get_execution_id(client, deployment_id):
500     """
501     Get the execution id of a env preparation.
502     network, security group, fip, VM creation
503     """
504     executions = client.executions.list(deployment_id=deployment_id)
505     for execution in executions:
506         if execution.workflow_id == 'create_deployment_environment':
507             return execution
508     raise RuntimeError('Failed to get create_deployment_environment '
509                        'workflow execution.'
510                        'Available executions: {0}'.format(executions))
511
512
513 def run_blocking_ssh_command(ssh, cmd, error_msg="Unable to run this command"):
514     """Command to run ssh command with the exit status."""
515     (_, stdout, _) = ssh.exec_command(cmd)
516     if stdout.channel.recv_exit_status() != 0:
517         raise Exception(error_msg)