Remove floating ip of second instance in cinder testcase
[functest.git] / functest / core / singlevm.py
1 #!/usr/bin/env python
2
3 # Copyright (c) 2018 Orange 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 """Ease deploying a single VM reachable via ssh
11
12 It offers a simple way to create all tenant network ressources + a VM for
13 advanced testcases (e.g. deploying an orchestrator).
14 """
15
16 import logging
17 import tempfile
18 import time
19
20 import paramiko
21 from xtesting.core import testcase
22
23 from functest.core import tenantnetwork
24 from functest.utils import config
25
26
27 class VmReady1(tenantnetwork.TenantNetwork1):
28     """Prepare a single VM (scenario1)
29
30     It inherits from TenantNetwork1 which creates all network resources and
31     prepares a future VM attached to that network.
32
33     It ensures that all testcases inheriting from SingleVm1 could work
34     without specific configurations (or at least read the same config data).
35     """
36     # pylint: disable=too-many-instance-attributes
37
38     __logger = logging.getLogger(__name__)
39     filename = '/home/opnfv/functest/images/cirros-0.4.0-x86_64-disk.img'
40     visibility = 'private'
41     extra_properties = None
42     flavor_ram = 1024
43     flavor_vcpus = 1
44     flavor_disk = 1
45
46     def __init__(self, **kwargs):
47         if "case_name" not in kwargs:
48             kwargs["case_name"] = 'vmready1'
49         super(VmReady1, self).__init__(**kwargs)
50         self.orig_cloud = self.cloud
51         self.image = None
52         self.flavor = None
53
54     def publish_image(self, name=None):
55         """Publish image
56
57         It allows publishing multiple images for the child testcases. It forces
58         the same configuration for all subtestcases.
59
60         Returns: image
61
62         Raises: expection on error
63         """
64         assert self.cloud
65         image = self.cloud.create_image(
66             name if name else '{}-img_{}'.format(self.case_name, self.guid),
67             filename=getattr(
68                 config.CONF, '{}_image'.format(self.case_name),
69                 self.filename),
70             meta=getattr(
71                 config.CONF, '{}_extra_properties'.format(self.case_name),
72                 self.extra_properties),
73             visibility=getattr(
74                 config.CONF, '{}_visibility'.format(self.case_name),
75                 self.visibility))
76         self.__logger.debug("image: %s", image)
77         return image
78
79     def create_flavor(self, name=None):
80         """Create flavor
81
82         It allows creating multiple flavors for the child testcases. It forces
83         the same configuration for all subtestcases.
84
85         Returns: flavor
86
87         Raises: expection on error
88         """
89         assert self.orig_cloud
90         flavor = self.orig_cloud.create_flavor(
91             name if name else '{}-flavor_{}'.format(self.case_name, self.guid),
92             getattr(config.CONF, '{}_flavor_ram'.format(self.case_name),
93                     self.flavor_ram),
94             getattr(config.CONF, '{}_flavor_vcpus'.format(self.case_name),
95                     self.flavor_vcpus),
96             getattr(config.CONF, '{}_flavor_disk'.format(self.case_name),
97                     self.flavor_disk))
98         self.__logger.debug("flavor: %s", flavor)
99         self.orig_cloud.set_flavor_specs(
100             flavor.id, getattr(config.CONF, 'flavor_extra_specs', {}))
101         return flavor
102
103     def boot_vm(self, name=None, **kwargs):
104         """Boot the virtual machine
105
106         It allows booting multiple machines for the child testcases. It forces
107         the same configuration for all subtestcases.
108
109         Returns: vm
110
111         Raises: expection on error
112         """
113         assert self.cloud
114         vm1 = self.cloud.create_server(
115             name if name else '{}-vm_{}'.format(self.case_name, self.guid),
116             image=self.image.id, flavor=self.flavor.id,
117             auto_ip=False, wait=True,
118             network=self.network.id,
119             **kwargs)
120         vm1 = self.cloud.wait_for_server(vm1, auto_ip=False)
121         self.__logger.debug("vm: %s", vm1)
122         return vm1
123
124     def run(self, **kwargs):
125         """Boot the new VM
126
127         Here are the main actions:
128         - publish the image
129         - create the flavor
130
131         Returns:
132         - TestCase.EX_OK
133         - TestCase.EX_RUN_ERROR on error
134         """
135         status = testcase.TestCase.EX_RUN_ERROR
136         try:
137             assert self.cloud
138             super(VmReady1, self).run(**kwargs)
139             self.image = self.publish_image()
140             self.flavor = self.create_flavor()
141             self.result = 100
142             status = testcase.TestCase.EX_OK
143         except Exception:  # pylint: disable=broad-except
144             self.__logger.exception('Cannot run %s', self.case_name)
145         finally:
146             self.stop_time = time.time()
147         return status
148
149     def clean(self):
150         try:
151             assert self.orig_cloud
152             assert self.cloud
153             super(VmReady1, self).clean()
154             self.cloud.delete_image(self.image.id)
155             self.orig_cloud.delete_flavor(self.flavor.id)
156         except Exception:  # pylint: disable=broad-except
157             self.__logger.exception("Cannot clean all ressources")
158
159
160 class VmReady2(VmReady1):
161     """Deploy a single VM reachable via ssh (scenario2)
162
163     It creates new user/project before creating and configuring all tenant
164     network ressources, flavors, images, etc. required by advanced testcases.
165
166     It ensures that all testcases inheriting from SingleVm2 could work
167     without specific configurations (or at least read the same config data).
168     """
169
170     __logger = logging.getLogger(__name__)
171
172     def __init__(self, **kwargs):
173         if "case_name" not in kwargs:
174             kwargs["case_name"] = 'vmready2'
175         super(VmReady2, self).__init__(**kwargs)
176         try:
177             assert self.orig_cloud
178             self.project = tenantnetwork.NewProject(
179                 self.orig_cloud, self.case_name, self.guid)
180             self.project.create()
181             self.cloud = self.project.cloud
182         except Exception:  # pylint: disable=broad-except
183             self.__logger.exception("Cannot create user or project")
184             self.cloud = None
185             self.project = None
186
187     def clean(self):
188         try:
189             super(VmReady2, self).clean()
190             assert self.project
191             self.project.clean()
192         except Exception:  # pylint: disable=broad-except
193             self.__logger.exception("Cannot clean all ressources")
194
195
196 class SingleVm1(VmReady1):
197     """Deploy a single VM reachable via ssh (scenario1)
198
199     It inherits from TenantNetwork1 which creates all network resources and
200     completes it by booting a VM attached to that network.
201
202     It ensures that all testcases inheriting from SingleVm1 could work
203     without specific configurations (or at least read the same config data).
204     """
205     # pylint: disable=too-many-instance-attributes
206
207     __logger = logging.getLogger(__name__)
208     username = 'cirros'
209     ssh_connect_timeout = 60
210
211     def __init__(self, **kwargs):
212         if "case_name" not in kwargs:
213             kwargs["case_name"] = 'singlevm1'
214         super(SingleVm1, self).__init__(**kwargs)
215         self.sshvm = None
216         self.sec = None
217         self.fip = None
218         self.keypair = None
219         self.ssh = None
220         (_, self.key_filename) = tempfile.mkstemp()
221
222     def prepare(self):
223         """Create the security group and the keypair
224
225         It can be overriden to set other rules according to the services
226         running in the VM
227
228         Raises: Exception on error
229         """
230         assert self.cloud
231         self.keypair = self.cloud.create_keypair(
232             '{}-kp_{}'.format(self.case_name, self.guid))
233         self.__logger.debug("keypair: %s", self.keypair)
234         self.__logger.debug("private_key: %s", self.keypair.private_key)
235         with open(self.key_filename, 'w') as private_key_file:
236             private_key_file.write(self.keypair.private_key)
237         self.sec = self.cloud.create_security_group(
238             '{}-sg_{}'.format(self.case_name, self.guid),
239             'created by OPNFV Functest ({})'.format(self.case_name))
240         self.cloud.create_security_group_rule(
241             self.sec.id, port_range_min='22', port_range_max='22',
242             protocol='tcp', direction='ingress')
243         self.cloud.create_security_group_rule(
244             self.sec.id, protocol='icmp', direction='ingress')
245
246     def connect(self, vm1):
247         """Connect to a virtual machine via ssh
248
249         It first adds a floating ip to the virtual machine and then establishes
250         the ssh connection.
251
252         Returns:
253         - (fip, ssh)
254         - None on error
255         """
256         assert vm1
257         fip = self.cloud.create_floating_ip(
258             network=self.ext_net.id, server=vm1)
259         self.__logger.debug("floating_ip: %s", fip)
260         p_console = self.cloud.get_server_console(vm1)
261         self.__logger.debug("vm console: \n%s", p_console)
262         ssh = paramiko.SSHClient()
263         ssh.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
264         for loop in range(6):
265             try:
266                 ssh.connect(
267                     fip.floating_ip_address,
268                     username=getattr(
269                         config.CONF,
270                         '{}_image_user'.format(self.case_name), self.username),
271                     key_filename=self.key_filename,
272                     timeout=getattr(
273                         config.CONF,
274                         '{}_vm_ssh_connect_timeout'.format(self.case_name),
275                         self.ssh_connect_timeout))
276                 break
277             except Exception:  # pylint: disable=broad-except
278                 self.__logger.debug(
279                     "try %s: cannot connect to %s", loop + 1,
280                     fip.floating_ip_address)
281                 time.sleep(10)
282         else:
283             self.__logger.error(
284                 "cannot connect to %s", fip.floating_ip_address)
285             return None
286         return (fip, ssh)
287
288     def execute(self):
289         """Say hello world via ssh
290
291         It can be overriden to execute any command.
292
293         Returns: echo exit codes
294         """
295         (_, stdout, _) = self.ssh.exec_command('echo Hello World')
296         self.__logger.debug("output:\n%s", stdout.read())
297         return stdout.channel.recv_exit_status()
298
299     def run(self, **kwargs):
300         """Boot the new VM
301
302         Here are the main actions:
303         - add a new ssh key
304         - boot the VM
305         - create the security group
306         - execute the right command over ssh
307
308         Returns:
309         - TestCase.EX_OK
310         - TestCase.EX_RUN_ERROR on error
311         """
312         status = testcase.TestCase.EX_RUN_ERROR
313         try:
314             assert self.cloud
315             super(SingleVm1, self).run(**kwargs)
316             self.result = 0
317             self.prepare()
318             self.sshvm = self.boot_vm(
319                 key_name=self.keypair.id, security_groups=[self.sec.id])
320             (self.fip, self.ssh) = self.connect(self.sshvm)
321             if not self.execute():
322                 self.result = 100
323                 status = testcase.TestCase.EX_OK
324         except Exception:  # pylint: disable=broad-except
325             self.__logger.exception('Cannot run %s', self.case_name)
326         finally:
327             self.stop_time = time.time()
328         return status
329
330     def clean(self):
331         try:
332             assert self.orig_cloud
333             assert self.cloud
334             self.cloud.delete_floating_ip(self.fip.id)
335             self.cloud.delete_server(self.sshvm, wait=True)
336             self.cloud.delete_security_group(self.sec.id)
337             self.cloud.delete_keypair(self.keypair.id)
338             super(SingleVm1, self).clean()
339         except Exception:  # pylint: disable=broad-except
340             self.__logger.exception("Cannot clean all ressources")
341
342
343 class SingleVm2(SingleVm1):
344     """Deploy a single VM reachable via ssh (scenario2)
345
346     It creates new user/project before creating and configuring all tenant
347     network ressources and vms required by advanced testcases.
348
349     It ensures that all testcases inheriting from SingleVm2 could work
350     without specific configurations (or at least read the same config data).
351     """
352
353     __logger = logging.getLogger(__name__)
354
355     def __init__(self, **kwargs):
356         if "case_name" not in kwargs:
357             kwargs["case_name"] = 'singlevm2'
358         super(SingleVm2, self).__init__(**kwargs)
359         try:
360             assert self.orig_cloud
361             self.project = tenantnetwork.NewProject(
362                 self.orig_cloud, self.case_name, self.guid)
363             self.project.create()
364             self.cloud = self.project.cloud
365         except Exception:  # pylint: disable=broad-except
366             self.__logger.exception("Cannot create user or project")
367             self.cloud = None
368             self.project = None
369
370     def clean(self):
371         try:
372             super(SingleVm2, self).clean()
373             assert self.project
374             self.project.clean()
375         except Exception:  # pylint: disable=broad-except
376             self.__logger.exception("Cannot clean all ressources")