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