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