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