[vyos_vrouter] Support https endpoints
[functest-xtesting.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.opnfv_tests.vnf.router.utilvnf import Utilvnf
24 from functest.utils.constants import CONST
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 from snaps.openstack.utils import keystone_utils
47
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)
116
117         # needs some images
118         self.__logger.info("Upload some OS images if it doesn't exist")
119         for image_name, image_file in self.images.iteritems():
120             self.__logger.info("image: %s, file: %s", image_name, image_file)
121             if image_file and image_name:
122                 image_creator = OpenStackImage(
123                     self.snaps_creds,
124                     ImageConfig(name=image_name,
125                                 image_user='cloud',
126                                 img_format='qcow2',
127                                 image_file=image_file))
128                 image_creator.create()
129                 self.created_object.append(image_creator)
130
131     def deploy_orchestrator(self):
132         """
133         Deploy Cloudify Manager.
134         network, security group, fip, VM creation
135         """
136         # network creation
137
138         start_time = time.time()
139         self.__logger.info("Creating keypair ...")
140         kp_file = os.path.join(self.data_dir, "cloudify_vrouter.pem")
141         keypair_settings = KeypairConfig(name='cloudify_vrouter_kp',
142                                          private_filepath=kp_file)
143         keypair_creator = OpenStackKeypair(self.snaps_creds, keypair_settings)
144         keypair_creator.create()
145         self.created_object.append(keypair_creator)
146
147         self.__logger.info("Creating full network ...")
148         subnet_settings = SubnetConfig(name='cloudify_vrouter_subnet',
149                                        cidr='10.67.79.0/24')
150         network_settings = NetworkConfig(name='cloudify_vrouter_network',
151                                          subnet_settings=[subnet_settings])
152         network_creator = OpenStackNetwork(self.snaps_creds, network_settings)
153         network_creator.create()
154         self.created_object.append(network_creator)
155         ext_net_name = snaps_utils.get_ext_net_name(self.snaps_creds)
156         router_creator = OpenStackRouter(
157             self.snaps_creds,
158             RouterConfig(
159                 name='cloudify_vrouter_router',
160                 external_gateway=ext_net_name,
161                 internal_subnets=[subnet_settings.name]))
162         router_creator.create()
163         self.created_object.append(router_creator)
164
165         # security group creation
166         self.__logger.info("Creating security group for cloudify manager vm")
167         sg_rules = list()
168         sg_rules.append(
169             SecurityGroupRuleConfig(sec_grp_name="sg-cloudify-manager",
170                                     direction=Direction.ingress,
171                                     protocol=Protocol.tcp, port_range_min=1,
172                                     port_range_max=65535))
173         sg_rules.append(
174             SecurityGroupRuleConfig(sec_grp_name="sg-cloudify-manager",
175                                     direction=Direction.ingress,
176                                     protocol=Protocol.udp, port_range_min=1,
177                                     port_range_max=65535))
178
179         security_group_creator = OpenStackSecurityGroup(
180             self.snaps_creds,
181             SecurityGroupConfig(
182                 name="sg-cloudify-manager",
183                 rule_settings=sg_rules))
184
185         security_group_creator.create()
186         self.created_object.append(security_group_creator)
187
188         # orchestrator VM flavor
189         self.__logger.info("Get or create flavor for cloudify manager vm ...")
190
191         flavor_settings = FlavorConfig(
192             name=self.orchestrator['requirements']['flavor']['name'],
193             ram=self.orchestrator['requirements']['flavor']['ram_min'],
194             disk=50,
195             vcpus=2)
196         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
197         flavor_creator.create()
198         self.created_object.append(flavor_creator)
199         image_settings = ImageConfig(
200             name=self.orchestrator['requirements']['os_image'],
201             image_user='centos',
202             exists=True)
203
204         port_settings = PortConfig(name='cloudify_manager_port',
205                                    network_name=network_settings.name)
206
207         manager_settings = VmInstanceConfig(
208             name='cloudify_manager',
209             flavor=flavor_settings.name,
210             port_settings=[port_settings],
211             security_group_names=[
212                 security_group_creator.sec_grp_settings.name],
213             floating_ip_settings=[FloatingIpConfig(
214                 name='cloudify_manager_fip',
215                 port_name=port_settings.name,
216                 router_name=router_creator.router_settings.name)])
217
218         manager_creator = OpenStackVmInstance(self.snaps_creds,
219                                               manager_settings,
220                                               image_settings,
221                                               keypair_settings)
222
223         self.__logger.info("Creating cloudify manager VM")
224         manager_creator.create()
225         self.created_object.append(manager_creator)
226
227         public_auth_url = keystone_utils.get_endpoint(
228             self.snaps_creds, 'identity')
229
230         self.__logger.info("Set creds for cloudify manager")
231         cfy_creds = dict(keystone_username=self.tenant_name,
232                          keystone_password=self.tenant_name,
233                          keystone_tenant_name=self.tenant_name,
234                          keystone_url=public_auth_url)
235
236         cfy_client = CloudifyClient(host=manager_creator.get_floating_ip().ip,
237                                     username='admin',
238                                     password='admin',
239                                     tenant='default_tenant')
240
241         self.orchestrator['object'] = cfy_client
242
243         self.cfy_manager_ip = manager_creator.get_floating_ip().ip
244
245         self.__logger.info("Attemps running status of the Manager")
246         cfy_status = None
247         retry = 10
248         while str(cfy_status) != 'running' and retry:
249             try:
250                 cfy_status = cfy_client.manager.get_status()['status']
251                 self.__logger.debug("The current manager status is %s",
252                                     cfy_status)
253             except Exception:  # pylint: disable=broad-except
254                 self.__logger.warning("Cloudify Manager isn't " +
255                                       "up and running. Retrying ...")
256             retry = retry - 1
257             time.sleep(30)
258
259         if str(cfy_status) == 'running':
260             self.__logger.info("Cloudify Manager is up and running")
261         else:
262             raise Exception("Cloudify Manager isn't up and running")
263
264         self.__logger.info("Put OpenStack creds in manager")
265         secrets_list = cfy_client.secrets.list()
266         for k, val in cfy_creds.iteritems():
267             if not any(d.get('key', None) == k for d in secrets_list):
268                 cfy_client.secrets.create(k, val)
269             else:
270                 cfy_client.secrets.update(k, val)
271
272         duration = time.time() - start_time
273
274         self.__logger.info("Put private keypair in manager")
275         if manager_creator.vm_ssh_active(block=True):
276             ssh = manager_creator.ssh_client()
277             scp = SCPClient(ssh.get_transport(), socket_timeout=15.0)
278             scp.put(kp_file, '~/')
279             cmd = "sudo cp ~/cloudify_vrouter.pem /etc/cloudify/"
280             run_blocking_ssh_command(ssh, cmd)
281             cmd = "sudo chmod 444 /etc/cloudify/cloudify_vrouter.pem"
282             run_blocking_ssh_command(ssh, cmd)
283             cmd = "sudo yum install -y gcc python-devel"
284             run_blocking_ssh_command(
285                 ssh, cmd, "Unable to install packages on manager")
286
287         self.details['orchestrator'].update(status='PASS', duration=duration)
288
289         self.vnf['inputs'].update(dict(external_network_name=ext_net_name))
290
291         return True
292
293     def deploy_vnf(self):
294         start_time = time.time()
295
296         self.__logger.info("Upload VNFD")
297         cfy_client = self.orchestrator['object']
298         descriptor = self.vnf['descriptor']
299         self.deployment_name = descriptor.get('name')
300
301         vrouter_blueprint_dir = os.path.join(self.data_dir,
302                                              self.util.blueprint_dir)
303         if not os.path.exists(vrouter_blueprint_dir):
304             Repo.clone_from(descriptor.get('url'),
305                             vrouter_blueprint_dir,
306                             branch=descriptor.get('version'))
307
308         cfy_client.blueprints.upload(vrouter_blueprint_dir +
309                                      self.util.blueprint_file_name,
310                                      descriptor.get('name'))
311
312         self.__logger.info("Get or create flavor for vrouter")
313         flavor_settings = FlavorConfig(
314             name=self.vnf['requirements']['flavor']['name'],
315             ram=self.vnf['requirements']['flavor']['ram_min'],
316             disk=25,
317             vcpus=1)
318         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
319         flavor = flavor_creator.create()
320         self.created_object.append(flavor_creator)
321
322         # set image name
323         glance = glance_utils.glance_client(self.snaps_creds)
324         image = glance_utils.get_image(glance,
325                                        "vyos1.1.7")
326         self.vnf['inputs'].update(dict(target_vnf_image_id=image.id))
327         self.vnf['inputs'].update(dict(reference_vnf_image_id=image.id))
328
329         # set flavor id
330         self.vnf['inputs'].update(dict(target_vnf_flavor_id=flavor.id))
331         self.vnf['inputs'].update(dict(reference_vnf_flavor_id=flavor.id))
332
333         self.vnf['inputs'].update(dict(keystone_username=self.tenant_name))
334         self.vnf['inputs'].update(dict(keystone_password=self.tenant_name))
335         self.vnf['inputs'].update(dict(keystone_tenant_name=self.tenant_name))
336         self.vnf['inputs'].update(
337             dict(keystone_url=keystone_utils.get_endpoint(
338                 self.snaps_creds, 'identity')))
339
340         self.__logger.info("Create VNF Instance")
341         cfy_client.deployments.create(descriptor.get('name'),
342                                       descriptor.get('name'),
343                                       self.vnf.get('inputs'))
344
345         wait_for_execution(cfy_client,
346                            get_execution_id(
347                                cfy_client, descriptor.get('name')),
348                            self.__logger,
349                            timeout=7200)
350
351         self.__logger.info("Start the VNF Instance deployment")
352         execution = cfy_client.executions.start(descriptor.get('name'),
353                                                 'install')
354         # Show execution log
355         execution = wait_for_execution(cfy_client, execution, self.__logger)
356
357         duration = time.time() - start_time
358
359         self.__logger.info(execution)
360         if execution.status == 'terminated':
361             self.details['vnf'].update(status='PASS', duration=duration)
362             result = True
363         else:
364             self.details['vnf'].update(status='FAIL', duration=duration)
365             result = False
366         return result
367
368     def test_vnf(self):
369         cfy_client = self.orchestrator['object']
370         credentials = {"snaps_creds": self.snaps_creds,
371                        "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)