Merge "Fix UserConfig objects"
[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         self.__logger.info("Upload some OS images if it doesn't exist")
138         for image_name, image_file in self.images.iteritems():
139             self.__logger.info("image: %s, file: %s", image_name, image_file)
140             if image_file and image_name:
141                 image_creator = OpenStackImage(
142                     self.snaps_creds,
143                     ImageConfig(
144                         name=image_name, image_user='cloud',
145                         img_format='qcow2', image_file=image_file))
146                 image_creator.create()
147                 self.created_object.append(image_creator)
148
149     def deploy_orchestrator(self):
150         # pylint: disable=too-many-locals,too-many-statements
151         """
152         Deploy Cloudify Manager.
153         network, security group, fip, VM creation
154         """
155         # network creation
156         start_time = time.time()
157         self.__logger.info("Creating keypair ...")
158         kp_file = os.path.join(self.data_dir, "cloudify_vrouter.pem")
159         keypair_settings = KeypairConfig(
160             name='cloudify_vrouter_kp-{}'.format(self.uuid),
161             private_filepath=kp_file)
162         keypair_creator = OpenStackKeypair(self.snaps_creds, keypair_settings)
163         keypair_creator.create()
164         self.created_object.append(keypair_creator)
165
166         self.__logger.info("Creating full network ...")
167         subnet_settings = SubnetConfig(
168             name='cloudify_vrouter_subnet-{}'.format(self.uuid),
169             cidr='10.67.79.0/24',
170             dns_nameservers=[env.get('NAMESERVER')])
171         network_settings = NetworkConfig(
172             name='cloudify_vrouter_network-{}'.format(self.uuid),
173             subnet_settings=[subnet_settings])
174         network_creator = OpenStackNetwork(self.snaps_creds, network_settings)
175         network_creator.create()
176         self.created_object.append(network_creator)
177         ext_net_name = snaps_utils.get_ext_net_name(self.snaps_creds)
178         router_creator = OpenStackRouter(
179             self.snaps_creds,
180             RouterConfig(
181                 name='cloudify_vrouter_router-{}'.format(self.uuid),
182                 external_gateway=ext_net_name,
183                 internal_subnets=[subnet_settings.name]))
184         router_creator.create()
185         self.created_object.append(router_creator)
186
187         # security group creation
188         self.__logger.info("Creating security group for cloudify manager vm")
189         sg_rules = list()
190         sg_rules.append(
191             SecurityGroupRuleConfig(
192                 sec_grp_name="sg-cloudify-manager-{}".format(self.uuid),
193                 direction=Direction.ingress,
194                 protocol=Protocol.tcp, port_range_min=1,
195                 port_range_max=65535))
196         sg_rules.append(
197             SecurityGroupRuleConfig(
198                 sec_grp_name="sg-cloudify-manager-{}".format(self.uuid),
199                 direction=Direction.ingress,
200                 protocol=Protocol.udp, port_range_min=1,
201                 port_range_max=65535))
202
203         security_group_creator = OpenStackSecurityGroup(
204             self.snaps_creds,
205             SecurityGroupConfig(
206                 name="sg-cloudify-manager-{}".format(self.uuid),
207                 rule_settings=sg_rules))
208
209         security_group_creator.create()
210         self.created_object.append(security_group_creator)
211
212         # orchestrator VM flavor
213         self.__logger.info("Get or create flavor for cloudify manager vm ...")
214
215         flavor_settings = FlavorConfig(
216             name=self.orchestrator['requirements']['flavor']['name'],
217             ram=self.orchestrator['requirements']['flavor']['ram_min'],
218             disk=50, vcpus=2)
219         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
220         flavor_creator.create()
221         self.created_object.append(flavor_creator)
222         image_settings = ImageConfig(
223             name=self.orchestrator['requirements']['os_image'],
224             image_user='centos', exists=True)
225
226         port_settings = PortConfig(
227             name='cloudify_manager_port-{}'.format(self.uuid),
228             network_name=network_settings.name)
229
230         manager_settings = VmInstanceConfig(
231             name='cloudify_manager-{}'.format(self.uuid),
232             flavor=flavor_settings.name,
233             port_settings=[port_settings],
234             security_group_names=[
235                 security_group_creator.sec_grp_settings.name],
236             floating_ip_settings=[FloatingIpConfig(
237                 name='cloudify_manager_fip-{}'.format(self.uuid),
238                 port_name=port_settings.name,
239                 router_name=router_creator.router_settings.name)])
240
241         manager_creator = OpenStackVmInstance(
242             self.snaps_creds, manager_settings, image_settings,
243             keypair_settings)
244
245         self.__logger.info("Creating cloudify manager VM")
246         manager_creator.create()
247         self.created_object.append(manager_creator)
248
249         cfy_client = CloudifyClient(
250             host=manager_creator.get_floating_ip().ip,
251             username='admin', password='admin', tenant='default_tenant')
252
253         self.orchestrator['object'] = cfy_client
254
255         self.cfy_manager_ip = manager_creator.get_floating_ip().ip
256
257         self.__logger.info("Attemps running status of the Manager")
258         cfy_status = None
259         retry = 10
260         while str(cfy_status) != 'running' and retry:
261             try:
262                 cfy_status = cfy_client.manager.get_status()['status']
263                 self.__logger.info(
264                     "The current manager status is %s", cfy_status)
265             except Exception:  # pylint: disable=broad-except
266                 self.__logger.info(
267                     "Cloudify Manager isn't up and running. Retrying ...")
268             retry = retry - 1
269             time.sleep(30)
270
271         if str(cfy_status) == 'running':
272             self.__logger.info("Cloudify Manager is up and running")
273         else:
274             raise Exception("Cloudify Manager isn't up and running")
275
276         duration = time.time() - start_time
277
278         self.__logger.info("Put private keypair in manager")
279         if manager_creator.vm_ssh_active(block=True):
280             ssh = manager_creator.ssh_client()
281             scp = SCPClient(ssh.get_transport(), socket_timeout=15.0)
282             scp.put(kp_file, '~/')
283             cmd = "sudo cp ~/cloudify_vrouter.pem /etc/cloudify/"
284             self.run_blocking_ssh_command(ssh, cmd)
285             cmd = "sudo chmod 444 /etc/cloudify/cloudify_vrouter.pem"
286             self.run_blocking_ssh_command(ssh, cmd)
287             cmd = "sudo yum install -y gcc python-devel"
288             self.run_blocking_ssh_command(
289                 ssh, cmd, "Unable to install packages on manager")
290
291         self.details['orchestrator'].update(status='PASS', duration=duration)
292
293         self.vnf['inputs'].update(dict(external_network_name=ext_net_name))
294
295         return True
296
297     def deploy_vnf(self):
298         start_time = time.time()
299
300         self.__logger.info("Upload VNFD")
301         cfy_client = self.orchestrator['object']
302         descriptor = self.vnf['descriptor']
303         self.deployment_name = descriptor.get('name')
304
305         cfy_client.blueprints.upload(
306             descriptor.get('file_name'), descriptor.get('name'))
307
308         self.__logger.info("Get or create flavor for vrouter")
309         flavor_settings = FlavorConfig(
310             name=self.vnf['requirements']['flavor']['name'],
311             ram=self.vnf['requirements']['flavor']['ram_min'],
312             disk=25, vcpus=1)
313         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
314         flavor = flavor_creator.create()
315         self.created_object.append(flavor_creator)
316
317         # set image name
318         glance = glance_utils.glance_client(self.snaps_creds)
319         image = glance_utils.get_image(glance, "vyos1.1.7")
320
321         user_creator = OpenStackUser(
322             self.snaps_creds,
323             UserConfig(
324                 name='cloudify_network_bug-{}'.format(self.uuid),
325                 password=str(uuid.uuid4()),
326                 project_name=self.tenant_name,
327                 domain_name=self.snaps_creds.user_domain_name,
328                 roles={'_member_': self.tenant_name}))
329         user_creator.create()
330         self.created_object.append(user_creator)
331         snaps_creds = user_creator.get_os_creds(self.snaps_creds.project_name)
332         self.__logger.debug("snaps creds: %s", snaps_creds)
333
334         self.vnf['inputs'].update(dict(target_vnf_image_id=image.id))
335         self.vnf['inputs'].update(dict(reference_vnf_image_id=image.id))
336         self.vnf['inputs'].update(dict(target_vnf_flavor_id=flavor.id))
337         self.vnf['inputs'].update(dict(reference_vnf_flavor_id=flavor.id))
338         self.vnf['inputs'].update(dict(
339             keystone_username=snaps_creds.username))
340         self.vnf['inputs'].update(dict(
341             keystone_password=snaps_creds.password))
342         self.vnf['inputs'].update(dict(
343             keystone_tenant_name=snaps_creds.project_name))
344         self.vnf['inputs'].update(dict(
345             keystone_user_domain_name=snaps_creds.user_domain_name))
346         self.vnf['inputs'].update(dict(
347             keystone_project_domain_name=snaps_creds.project_domain_name))
348         self.vnf['inputs'].update(dict(
349             region=snaps_creds.region_name))
350         self.vnf['inputs'].update(dict(
351             keystone_url=keystone_utils.get_endpoint(
352                 snaps_creds, 'identity')))
353
354         self.__logger.info("Create VNF Instance")
355         cfy_client.deployments.create(
356             descriptor.get('name'), descriptor.get('name'),
357             self.vnf.get('inputs'))
358
359         wait_for_execution(
360             cfy_client, get_execution_id(cfy_client, descriptor.get('name')),
361             self.__logger, timeout=7200)
362
363         self.__logger.info("Start the VNF Instance deployment")
364         execution = cfy_client.executions.start(descriptor.get('name'),
365                                                 'install')
366         # Show execution log
367         execution = wait_for_execution(cfy_client, execution, self.__logger)
368
369         duration = time.time() - start_time
370
371         self.__logger.info(execution)
372         if execution.status == 'terminated':
373             self.details['vnf'].update(status='PASS', duration=duration)
374             result = True
375         else:
376             self.details['vnf'].update(status='FAIL', duration=duration)
377             result = False
378         return result
379
380     def test_vnf(self):
381         cfy_client = self.orchestrator['object']
382         credentials = {"snaps_creds": self.snaps_creds}
383
384         self.util_info = {"credentials": credentials,
385                           "cfy": cfy_client,
386                           "vnf_data_dir": self.util.vnf_data_dir}
387
388         start_time = time.time()
389
390         result, test_result_data = super(CloudifyVrouter, self).test_vnf()
391
392         duration = time.time() - start_time
393
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
403         return True
404
405     def clean(self):
406         try:
407             cfy_client = self.orchestrator['object']
408             dep_name = self.vnf['descriptor'].get('name')
409             # kill existing execution
410             self.__logger.info('Deleting the current deployment')
411             exec_list = cfy_client.executions.list(dep_name)
412             for execution in exec_list:
413                 if execution['status'] == "started":
414                     try:
415                         cfy_client.executions.cancel(
416                             execution['id'], force=True)
417                     except Exception:  # pylint: disable=broad-except
418                         self.__logger.warn("Can't cancel the current exec")
419
420             execution = cfy_client.executions.start(
421                 dep_name, 'uninstall', parameters=dict(ignore_failure=True))
422
423             wait_for_execution(cfy_client, execution, self.__logger)
424             cfy_client.deployments.delete(self.vnf['descriptor'].get('name'))
425             cfy_client.blueprints.delete(self.vnf['descriptor'].get('name'))
426         except Exception:  # pylint: disable=broad-except
427             self.__logger.exception("Some issue during the undeployment ..")
428
429         super(CloudifyVrouter, self).clean()
430
431     def get_vnf_info_list(self, target_vnf_name):
432         return self.util.get_vnf_info_list(
433             self.cfy_manager_ip, self.deployment_name, target_vnf_name)
434
435
436 def wait_for_execution(client, execution, logger, timeout=7200, ):
437     """Wait for a workflow execution on Cloudify Manager."""
438     # if execution already ended - return without waiting
439     if execution.status in Execution.END_STATES:
440         return execution
441
442     if timeout is not None:
443         deadline = time.time() + timeout
444
445     # Poll for execution status and execution logs, until execution ends
446     # and we receive an event of type in WORKFLOW_END_TYPES
447     offset = 0
448     batch_size = 50
449     event_list = []
450     execution_ended = False
451     while True:
452         event_list = client.events.list(
453             execution_id=execution.id, _offset=offset, _size=batch_size,
454             include_logs=False, sort='@timestamp').items
455
456         offset = offset + len(event_list)
457         for event in event_list:
458             logger.debug(event.get('message'))
459
460         if timeout is not None:
461             if time.time() > deadline:
462                 raise RuntimeError(
463                     'execution of operation {0} for deployment {1} '
464                     'timed out'.format(execution.workflow_id,
465                                        execution.deployment_id))
466             else:
467                 # update the remaining timeout
468                 timeout = deadline - time.time()
469
470         if not execution_ended:
471             execution = client.executions.get(execution.id)
472             execution_ended = execution.status in Execution.END_STATES
473
474         if execution_ended:
475             break
476
477         time.sleep(5)
478
479     return execution
480
481
482 def get_execution_id(client, deployment_id):
483     """
484     Get the execution id of a env preparation.
485     network, security group, fip, VM creation
486     """
487     executions = client.executions.list(deployment_id=deployment_id)
488     for execution in executions:
489         if execution.workflow_id == 'create_deployment_environment':
490             return execution
491     raise RuntimeError('Failed to get create_deployment_environment '
492                        'workflow execution.'
493                        'Available executions: {0}'.format(executions))