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