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