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