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