3 # Copyright (c) 2018 Orange and others.
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
10 """Ease deploying a single VM reachable via ssh
12 It offers a simple way to create all tenant network ressources + a VM for
13 advanced testcases (e.g. deploying an orchestrator).
22 from xtesting.core import testcase
24 from functest.core import tenantnetwork
25 from functest.utils import config
28 class VmReady1(tenantnetwork.TenantNetwork1):
29 """Prepare a single VM (scenario1)
31 It inherits from TenantNetwork1 which creates all network resources and
32 prepares a future VM attached to that network.
34 It ensures that all testcases inheriting from SingleVm1 could work
35 without specific configurations (or at least read the same config data).
37 # pylint: disable=too-many-instance-attributes
39 __logger = logging.getLogger(__name__)
40 filename = '/home/opnfv/functest/images/cirros-0.4.0-x86_64-disk.img'
41 image_format = 'qcow2'
43 image_alt_format = image_format
44 visibility = 'private'
45 extra_properties = None
52 create_server_timeout = 60
54 def __init__(self, **kwargs):
55 if "case_name" not in kwargs:
56 kwargs["case_name"] = 'vmready1'
57 super(VmReady1, self).__init__(**kwargs)
58 self.orig_cloud = self.cloud
62 def publish_image(self, name=None):
65 It allows publishing multiple images for the child testcases. It forces
66 the same configuration for all subtestcases.
70 Raises: expection on error
73 image = self.cloud.create_image(
74 name if name else '{}-img_{}'.format(self.case_name, self.guid),
76 config.CONF, '{}_image'.format(self.case_name),
79 config.CONF, '{}_extra_properties'.format(self.case_name),
80 self.extra_properties),
82 config.CONF, '{}_image_format'.format(self.case_name),
85 config.CONF, '{}_visibility'.format(self.case_name),
87 self.__logger.debug("image: %s", image)
90 def publish_image_alt(self, name=None):
91 """Publish alternative image
93 It allows publishing multiple images for the child testcases. It forces
94 the same configuration for all subtestcases.
98 Raises: expection on error
101 image = self.cloud.create_image(
102 name if name else '{}-img_alt_{}'.format(
103 self.case_name, self.guid),
105 config.CONF, '{}_image_alt'.format(self.case_name),
108 config.CONF, '{}_extra_properties'.format(self.case_name),
109 self.extra_properties),
111 config.CONF, '{}_image_alt_format'.format(self.case_name),
114 config.CONF, '{}_visibility'.format(self.case_name),
116 self.__logger.debug("image: %s", image)
119 def create_flavor(self, name=None):
122 It allows creating multiple flavors for the child testcases. It forces
123 the same configuration for all subtestcases.
127 Raises: expection on error
129 assert self.orig_cloud
130 flavor = self.orig_cloud.create_flavor(
131 name if name else '{}-flavor_{}'.format(self.case_name, self.guid),
132 getattr(config.CONF, '{}_flavor_ram'.format(self.case_name),
134 getattr(config.CONF, '{}_flavor_vcpus'.format(self.case_name),
136 getattr(config.CONF, '{}_flavor_disk'.format(self.case_name),
138 self.__logger.debug("flavor: %s", flavor)
139 self.orig_cloud.set_flavor_specs(
140 flavor.id, getattr(config.CONF, 'flavor_extra_specs', {}))
143 def create_flavor_alt(self, name=None):
146 It allows creating multiple alt flavors for the child testcases. It
147 forces the same configuration for all subtestcases.
151 Raises: expection on error
153 assert self.orig_cloud
154 flavor = self.orig_cloud.create_flavor(
155 name if name else '{}-flavor_alt_{}'.format(
156 self.case_name, self.guid),
157 getattr(config.CONF, '{}_flavor_alt_ram'.format(self.case_name),
158 self.flavor_alt_ram),
159 getattr(config.CONF, '{}_flavor_alt_vcpus'.format(self.case_name),
160 self.flavor_alt_vcpus),
161 getattr(config.CONF, '{}_flavor_alt_disk'.format(self.case_name),
162 self.flavor_alt_disk))
163 self.__logger.debug("flavor: %s", flavor)
164 self.orig_cloud.set_flavor_specs(
165 flavor.id, getattr(config.CONF, 'flavor_extra_specs', {}))
168 def boot_vm(self, name=None, **kwargs):
169 """Boot the virtual machine
171 It allows booting multiple machines for the child testcases. It forces
172 the same configuration for all subtestcases.
176 Raises: expection on error
179 vm1 = self.cloud.create_server(
180 name if name else '{}-vm_{}'.format(self.case_name, self.guid),
181 image=self.image.id, flavor=self.flavor.id,
182 auto_ip=False, wait=True, network=self.network.id,
183 timeout=self.create_server_timeout, **kwargs)
184 vm1 = self.cloud.wait_for_server(vm1, auto_ip=False)
185 self.__logger.debug("vm: %s", vm1)
188 def check_regex_in_console(self, name, regex=' login: ', loop=1):
189 """Wait for specific message in console
191 Returns: True or False on errors
194 for iloop in range(loop):
195 console = self.cloud.get_server_console(name)
196 self.__logger.debug("console: \n%s", console)
197 if re.search(regex, console):
198 self.__logger.debug("regex found: ''%s' in console", regex)
202 "try %s: cannot find regex '%s' in console",
205 self.__logger.error("cannot find regex '%s' in console", regex)
208 def run(self, **kwargs):
211 Here are the main actions:
217 - TestCase.EX_RUN_ERROR on error
219 status = testcase.TestCase.EX_RUN_ERROR
222 assert super(VmReady1, self).run(
223 **kwargs) == testcase.TestCase.EX_OK
224 self.image = self.publish_image()
225 self.flavor = self.create_flavor()
227 status = testcase.TestCase.EX_OK
228 except Exception: # pylint: disable=broad-except
229 self.__logger.exception('Cannot run %s', self.case_name)
232 self.stop_time = time.time()
237 assert self.orig_cloud
239 super(VmReady1, self).clean()
241 self.cloud.delete_image(self.image.id)
243 self.orig_cloud.delete_flavor(self.flavor.id)
244 except Exception: # pylint: disable=broad-except
245 self.__logger.exception("Cannot clean all ressources")
248 class VmReady2(VmReady1):
249 """Deploy a single VM reachable via ssh (scenario2)
251 It creates new user/project before creating and configuring all tenant
252 network ressources, flavors, images, etc. required by advanced testcases.
254 It ensures that all testcases inheriting from SingleVm2 could work
255 without specific configurations (or at least read the same config data).
258 __logger = logging.getLogger(__name__)
260 def __init__(self, **kwargs):
261 if "case_name" not in kwargs:
262 kwargs["case_name"] = 'vmready2'
263 super(VmReady2, self).__init__(**kwargs)
265 assert self.orig_cloud
266 self.project = tenantnetwork.NewProject(
267 self.orig_cloud, self.case_name, self.guid)
268 self.project.create()
269 self.cloud = self.project.cloud
270 except Exception: # pylint: disable=broad-except
271 self.__logger.exception("Cannot create user or project")
277 super(VmReady2, self).clean()
280 except Exception: # pylint: disable=broad-except
281 self.__logger.exception("Cannot clean all ressources")
284 class SingleVm1(VmReady1):
285 """Deploy a single VM reachable via ssh (scenario1)
287 It inherits from TenantNetwork1 which creates all network resources and
288 completes it by booting a VM attached to that network.
290 It ensures that all testcases inheriting from SingleVm1 could work
291 without specific configurations (or at least read the same config data).
293 # pylint: disable=too-many-instance-attributes
295 __logger = logging.getLogger(__name__)
297 ssh_connect_timeout = 60
298 ssh_connect_loops = 6
300 def __init__(self, **kwargs):
301 if "case_name" not in kwargs:
302 kwargs["case_name"] = 'singlevm1'
303 super(SingleVm1, self).__init__(**kwargs)
309 (_, self.key_filename) = tempfile.mkstemp()
312 """Create the security group and the keypair
314 It can be overriden to set other rules according to the services
317 Raises: Exception on error
320 self.keypair = self.cloud.create_keypair(
321 '{}-kp_{}'.format(self.case_name, self.guid))
322 self.__logger.debug("keypair: %s", self.keypair)
323 self.__logger.debug("private_key: %s", self.keypair.private_key)
324 with open(self.key_filename, 'w') as private_key_file:
325 private_key_file.write(self.keypair.private_key)
326 self.sec = self.cloud.create_security_group(
327 '{}-sg_{}'.format(self.case_name, self.guid),
328 'created by OPNFV Functest ({})'.format(self.case_name))
329 self.cloud.create_security_group_rule(
330 self.sec.id, port_range_min='22', port_range_max='22',
331 protocol='tcp', direction='ingress')
332 self.cloud.create_security_group_rule(
333 self.sec.id, protocol='icmp', direction='ingress')
335 def connect(self, vm1):
336 """Connect to a virtual machine via ssh
338 It first adds a floating ip to the virtual machine and then establishes
346 fip = self.cloud.create_floating_ip(
347 network=self.ext_net.id, server=vm1)
348 self.__logger.debug("floating_ip: %s", fip)
349 p_console = self.cloud.get_server_console(vm1)
350 self.__logger.debug("vm console: \n%s", p_console)
351 ssh = paramiko.SSHClient()
352 ssh.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
353 for loop in range(self.ssh_connect_loops):
356 fip.floating_ip_address,
359 '{}_image_user'.format(self.case_name), self.username),
360 key_filename=self.key_filename,
363 '{}_vm_ssh_connect_timeout'.format(self.case_name),
364 self.ssh_connect_timeout))
366 except Exception: # pylint: disable=broad-except
368 "try %s: cannot connect to %s", loop + 1,
369 fip.floating_ip_address)
373 "cannot connect to %s", fip.floating_ip_address)
378 """Say hello world via ssh
380 It can be overriden to execute any command.
382 Returns: echo exit codes
384 (_, stdout, _) = self.ssh.exec_command('echo Hello World')
385 self.__logger.debug("output:\n%s", stdout.read())
386 return stdout.channel.recv_exit_status()
388 def run(self, **kwargs):
391 Here are the main actions:
394 - create the security group
395 - execute the right command over ssh
399 - TestCase.EX_RUN_ERROR on error
401 status = testcase.TestCase.EX_RUN_ERROR
404 assert super(SingleVm1, self).run(
405 **kwargs) == testcase.TestCase.EX_OK
408 self.sshvm = self.boot_vm(
409 key_name=self.keypair.id, security_groups=[self.sec.id])
410 (self.fip, self.ssh) = self.connect(self.sshvm)
411 if not self.execute():
413 status = testcase.TestCase.EX_OK
414 except Exception: # pylint: disable=broad-except
415 self.__logger.exception('Cannot run %s', self.case_name)
417 self.stop_time = time.time()
422 assert self.orig_cloud
425 self.cloud.delete_floating_ip(self.fip.id)
427 self.cloud.delete_server(self.sshvm, wait=True)
429 self.cloud.delete_security_group(self.sec.id)
431 self.cloud.delete_keypair(self.keypair.name)
432 super(SingleVm1, self).clean()
433 except Exception: # pylint: disable=broad-except
434 self.__logger.exception("Cannot clean all ressources")
437 class SingleVm2(SingleVm1):
438 """Deploy a single VM reachable via ssh (scenario2)
440 It creates new user/project before creating and configuring all tenant
441 network ressources and vms required by advanced testcases.
443 It ensures that all testcases inheriting from SingleVm2 could work
444 without specific configurations (or at least read the same config data).
447 __logger = logging.getLogger(__name__)
449 def __init__(self, **kwargs):
450 if "case_name" not in kwargs:
451 kwargs["case_name"] = 'singlevm2'
452 super(SingleVm2, self).__init__(**kwargs)
454 assert self.orig_cloud
455 self.project = tenantnetwork.NewProject(
456 self.orig_cloud, self.case_name, self.guid)
457 self.project.create()
458 self.cloud = self.project.cloud
459 except Exception: # pylint: disable=broad-except
460 self.__logger.exception("Cannot create user or project")
466 super(SingleVm2, self).clean()
469 except Exception: # pylint: disable=broad-except
470 self.__logger.exception("Cannot clean all ressources")