19454902224fe5ce266db2f7abc90a018fee4aef
[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             api_version='v3')
266
267         self.orchestrator['object'] = cfy_client
268
269         self.cfy_manager_ip = manager_creator.get_floating_ip().ip
270
271         self.__logger.info("Attemps running status of the Manager")
272         for loop in range(10):
273             try:
274                 self.__logger.debug(
275                     "status %s", cfy_client.manager.get_status())
276                 cfy_status = cfy_client.manager.get_status()['status']
277                 self.__logger.info(
278                     "The current manager status is %s", cfy_status)
279                 if str(cfy_status) != 'running':
280                     raise Exception("Cloudify Manager isn't up and running")
281                 break
282             except Exception:  # pylint: disable=broad-except
283                 self.logger.info(
284                     "try %s: Cloudify Manager isn't up and running", loop + 1)
285                 time.sleep(30)
286         else:
287             self.logger.error("Cloudify Manager isn't up and running")
288             return False
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             self.run_blocking_ssh_command(ssh, cmd)
299             cmd = "sudo chmod 444 /etc/cloudify/cloudify_vrouter.pem"
300             self.run_blocking_ssh_command(ssh, cmd)
301             # cmd2 is badly unpinned by Cloudify
302             cmd = "sudo yum install -y gcc python-devel python-cmd2"
303             self.run_blocking_ssh_command(
304                 ssh, cmd, "Unable to install packages on manager")
305         else:
306             self.__logger.error("Cannot connect to manager")
307             return False
308
309         self.details['orchestrator'].update(status='PASS', duration=duration)
310
311         self.__logger.info("Get or create flavor for vrouter")
312         flavor_settings = FlavorConfig(
313             name="{}-{}".format(
314                 self.vnf['requirements']['flavor']['name'],
315                 self.uuid),
316             ram=self.vnf['requirements']['flavor']['ram_min'],
317             disk=25, 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(snaps_creds)
324         image = glance_utils.get_image(glance, "vyos1.1.7")
325         self.vnf['inputs'].update(dict(external_network_name=ext_net_name))
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         self.vnf['inputs'].update(dict(target_vnf_flavor_id=flavor.id))
329         self.vnf['inputs'].update(dict(reference_vnf_flavor_id=flavor.id))
330         self.vnf['inputs'].update(dict(
331             keystone_username=snaps_creds.username))
332         self.vnf['inputs'].update(dict(
333             keystone_password=snaps_creds.password))
334         self.vnf['inputs'].update(dict(
335             keystone_tenant_name=snaps_creds.project_name))
336         self.vnf['inputs'].update(dict(
337             keystone_user_domain_name=snaps_creds.user_domain_name))
338         self.vnf['inputs'].update(dict(
339             keystone_project_domain_name=snaps_creds.project_domain_name))
340         self.vnf['inputs'].update(dict(
341             region=snaps_creds.region_name))
342         self.vnf['inputs'].update(dict(
343             keystone_url=keystone_utils.get_endpoint(
344                 snaps_creds, 'identity')))
345
346         credentials = {"snaps_creds": snaps_creds}
347         self.util_info = {"credentials": credentials,
348                           "cfy": cfy_client,
349                           "vnf_data_dir": self.util.vnf_data_dir}
350
351         return True
352
353     def deploy_vnf(self):
354         start_time = time.time()
355
356         self.__logger.info("Upload VNFD")
357         cfy_client = self.orchestrator['object']
358         descriptor = self.vnf['descriptor']
359         self.deployment_name = descriptor.get('name')
360
361         cfy_client.blueprints.upload(
362             descriptor.get('file_name'), descriptor.get('name'))
363
364         self.__logger.info("Create VNF Instance")
365         cfy_client.deployments.create(
366             descriptor.get('name'), descriptor.get('name'),
367             self.vnf.get('inputs'))
368
369         wait_for_execution(
370             cfy_client, get_execution_id(cfy_client, descriptor.get('name')),
371             self.__logger, timeout=7200)
372
373         self.__logger.info("Start the VNF Instance deployment")
374         execution = cfy_client.executions.start(descriptor.get('name'),
375                                                 'install')
376         # Show execution log
377         execution = wait_for_execution(cfy_client, execution, self.__logger)
378
379         duration = time.time() - start_time
380
381         self.__logger.info(execution)
382         if execution.status == 'terminated':
383             self.details['vnf'].update(status='PASS', duration=duration)
384             result = True
385         else:
386             self.details['vnf'].update(status='FAIL', duration=duration)
387             result = False
388         return result
389
390     def test_vnf(self):
391         start_time = time.time()
392         result, test_result_data = super(CloudifyVrouter, self).test_vnf()
393         duration = time.time() - start_time
394         if result:
395             self.details['test_vnf'].update(
396                 status='PASS', result='OK', full_result=test_result_data,
397                 duration=duration)
398         else:
399             self.details['test_vnf'].update(
400                 status='FAIL', result='NG', full_result=test_result_data,
401                 duration=duration)
402         return True
403
404     def clean(self):
405         try:
406             cfy_client = self.orchestrator['object']
407             dep_name = self.vnf['descriptor'].get('name')
408             # kill existing execution
409             self.__logger.info('Deleting the current deployment')
410             exec_list = cfy_client.executions.list(dep_name)
411             for execution in exec_list:
412                 if execution['status'] == "started":
413                     try:
414                         cfy_client.executions.cancel(
415                             execution['id'], force=True)
416                     except Exception:  # pylint: disable=broad-except
417                         self.__logger.warn("Can't cancel the current exec")
418
419             execution = cfy_client.executions.start(
420                 dep_name, 'uninstall', parameters=dict(ignore_failure=True))
421
422             wait_for_execution(cfy_client, execution, self.__logger)
423             cfy_client.deployments.delete(self.vnf['descriptor'].get('name'))
424             cfy_client.blueprints.delete(self.vnf['descriptor'].get('name'))
425         except Exception:  # pylint: disable=broad-except
426             self.__logger.exception("Some issue during the undeployment ..")
427
428         super(CloudifyVrouter, self).clean()
429
430     def get_vnf_info_list(self, target_vnf_name):
431         return self.util.get_vnf_info_list(
432             self.cfy_manager_ip, self.deployment_name, target_vnf_name)
433
434
435 def wait_for_execution(client, execution, logger, timeout=7200, ):
436     """Wait for a workflow execution on Cloudify Manager."""
437     # if execution already ended - return without waiting
438     if execution.status in Execution.END_STATES:
439         return execution
440
441     if timeout is not None:
442         deadline = time.time() + timeout
443
444     # Poll for execution status and execution logs, until execution ends
445     # and we receive an event of type in WORKFLOW_END_TYPES
446     offset = 0
447     batch_size = 50
448     event_list = []
449     execution_ended = False
450     while True:
451         event_list = client.events.list(
452             execution_id=execution.id, _offset=offset, _size=batch_size,
453             include_logs=True, sort='@timestamp').items
454
455         offset = offset + len(event_list)
456         for event in event_list:
457             logger.debug(event.get('message'))
458
459         if timeout is not None:
460             if time.time() > deadline:
461                 raise RuntimeError(
462                     'execution of operation {0} for deployment {1} '
463                     'timed out'.format(execution.workflow_id,
464                                        execution.deployment_id))
465             else:
466                 # update the remaining timeout
467                 timeout = deadline - time.time()
468
469         if not execution_ended:
470             execution = client.executions.get(execution.id)
471             execution_ended = execution.status in Execution.END_STATES
472
473         if execution_ended:
474             break
475
476         time.sleep(5)
477
478     return execution
479
480
481 def get_execution_id(client, deployment_id):
482     """
483     Get the execution id of a env preparation.
484     network, security group, fip, VM creation
485     """
486     executions = client.executions.list(deployment_id=deployment_id)
487     for execution in executions:
488         if execution.workflow_id == 'create_deployment_environment':
489             return execution
490     raise RuntimeError('Failed to get create_deployment_environment '
491                        'workflow execution.'
492                        'Available executions: {0}'.format(executions))