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'
42 extra_properties = None
43 filename_alt = filename
44 image_alt_format = image_format
45 extra_alt_properties = extra_properties
46 visibility = 'private'
50 flavor_extra_specs = {}
54 flavor_alt_extra_specs = flavor_extra_specs
55 create_server_timeout = 180
57 def __init__(self, **kwargs):
58 if "case_name" not in kwargs:
59 kwargs["case_name"] = 'vmready1'
60 super(VmReady1, self).__init__(**kwargs)
61 self.orig_cloud = self.cloud
65 def publish_image(self, name=None):
68 It allows publishing multiple images for the child testcases. It forces
69 the same configuration for all subtestcases.
73 Raises: expection on error
76 image = self.cloud.create_image(
77 name if name else '{}-img_{}'.format(self.case_name, self.guid),
79 config.CONF, '{}_image'.format(self.case_name),
82 config.CONF, '{}_extra_properties'.format(self.case_name),
83 self.extra_properties),
85 config.CONF, '{}_image_format'.format(self.case_name),
88 config.CONF, '{}_visibility'.format(self.case_name),
91 self.__logger.debug("image: %s", image)
94 def publish_image_alt(self, name=None):
95 """Publish alternative image
97 It allows publishing multiple images for the child testcases. It forces
98 the same configuration for all subtestcases.
102 Raises: expection on error
105 image = self.cloud.create_image(
106 name if name else '{}-img_alt_{}'.format(
107 self.case_name, self.guid),
109 config.CONF, '{}_image_alt'.format(self.case_name),
112 config.CONF, '{}_extra_alt_properties'.format(self.case_name),
113 self.extra_properties),
115 config.CONF, '{}_image_alt_format'.format(self.case_name),
118 config.CONF, '{}_visibility'.format(self.case_name),
121 self.__logger.debug("image: %s", image)
124 def create_flavor(self, name=None):
127 It allows creating multiple flavors for the child testcases. It forces
128 the same configuration for all subtestcases.
132 Raises: expection on error
134 assert self.orig_cloud
135 flavor = self.orig_cloud.create_flavor(
136 name if name else '{}-flavor_{}'.format(self.case_name, self.guid),
137 getattr(config.CONF, '{}_flavor_ram'.format(self.case_name),
139 getattr(config.CONF, '{}_flavor_vcpus'.format(self.case_name),
141 getattr(config.CONF, '{}_flavor_disk'.format(self.case_name),
143 self.__logger.debug("flavor: %s", flavor)
144 flavor_extra_specs_updated = self.flavor_extra_specs.copy()
145 flavor_extra_specs_updated.update(
147 '{}_flavor_extra_specs'.format(self.case_name), {}))
148 self.orig_cloud.set_flavor_specs(flavor.id, flavor_extra_specs_updated)
151 def create_flavor_alt(self, name=None):
154 It allows creating multiple alt flavors for the child testcases. It
155 forces the same configuration for all subtestcases.
159 Raises: expection on error
161 assert self.orig_cloud
162 flavor = self.orig_cloud.create_flavor(
163 name if name else '{}-flavor_alt_{}'.format(
164 self.case_name, self.guid),
165 getattr(config.CONF, '{}_flavor_alt_ram'.format(self.case_name),
166 self.flavor_alt_ram),
167 getattr(config.CONF, '{}_flavor_alt_vcpus'.format(self.case_name),
168 self.flavor_alt_vcpus),
169 getattr(config.CONF, '{}_flavor_alt_disk'.format(self.case_name),
170 self.flavor_alt_disk))
171 self.__logger.debug("flavor: %s", flavor)
172 flavor_alt_extra_specs_updated = self.flavor_alt_extra_specs.copy()
173 flavor_alt_extra_specs_updated.update(
175 '{}_flavor_alt_extra_specs'.format(self.case_name), {}))
176 self.orig_cloud.set_flavor_specs(
177 flavor.id, flavor_alt_extra_specs_updated)
180 def boot_vm(self, name=None, **kwargs):
181 """Boot the virtual machine
183 It allows booting multiple machines for the child testcases. It forces
184 the same configuration for all subtestcases.
188 Raises: expection on error
191 vm1 = self.cloud.create_server(
192 name if name else '{}-vm_{}'.format(self.case_name, self.guid),
193 image=self.image.id, flavor=self.flavor.id,
194 auto_ip=False, network=self.network.id,
195 timeout=self.create_server_timeout, wait=True, **kwargs)
196 self.__logger.debug("vm: %s", vm1)
199 def check_regex_in_console(self, name, regex=' login: ', loop=1):
200 """Wait for specific message in console
202 Returns: True or False on errors
205 for iloop in range(loop):
206 console = self.cloud.get_server_console(name)
207 self.__logger.debug("console: \n%s", console)
208 if re.search(regex, console):
209 self.__logger.debug("regex found: ''%s' in console", regex)
213 "try %s: cannot find regex '%s' in console",
216 self.__logger.error("cannot find regex '%s' in console", regex)
219 def run(self, **kwargs):
222 Here are the main actions:
228 - TestCase.EX_RUN_ERROR on error
230 status = testcase.TestCase.EX_RUN_ERROR
233 assert super(VmReady1, self).run(
234 **kwargs) == testcase.TestCase.EX_OK
235 self.image = self.publish_image()
236 self.flavor = self.create_flavor()
238 status = testcase.TestCase.EX_OK
239 except Exception: # pylint: disable=broad-except
240 self.__logger.exception('Cannot run %s', self.case_name)
243 self.stop_time = time.time()
248 assert self.orig_cloud
250 super(VmReady1, self).clean()
252 self.cloud.delete_image(self.image.id)
254 self.orig_cloud.delete_flavor(self.flavor.id)
255 except Exception: # pylint: disable=broad-except
256 self.__logger.exception("Cannot clean all ressources")
259 class VmReady2(VmReady1):
260 """Deploy a single VM reachable via ssh (scenario2)
262 It creates new user/project before creating and configuring all tenant
263 network ressources, flavors, images, etc. required by advanced testcases.
265 It ensures that all testcases inheriting from SingleVm2 could work
266 without specific configurations (or at least read the same config data).
269 __logger = logging.getLogger(__name__)
271 def __init__(self, **kwargs):
272 if "case_name" not in kwargs:
273 kwargs["case_name"] = 'vmready2'
274 super(VmReady2, self).__init__(**kwargs)
276 assert self.orig_cloud
277 self.project = tenantnetwork.NewProject(
278 self.orig_cloud, self.case_name, self.guid)
279 self.project.create()
280 self.cloud = self.project.cloud
281 except Exception: # pylint: disable=broad-except
282 self.__logger.exception("Cannot create user or project")
288 super(VmReady2, self).clean()
291 except Exception: # pylint: disable=broad-except
292 self.__logger.exception("Cannot clean all ressources")
295 class SingleVm1(VmReady1):
296 """Deploy a single VM reachable via ssh (scenario1)
298 It inherits from TenantNetwork1 which creates all network resources and
299 completes it by booting a VM attached to that network.
301 It ensures that all testcases inheriting from SingleVm1 could work
302 without specific configurations (or at least read the same config data).
304 # pylint: disable=too-many-instance-attributes
306 __logger = logging.getLogger(__name__)
308 ssh_connect_timeout = 1
309 ssh_connect_loops = 6
310 create_floating_ip_timeout = 120
312 def __init__(self, **kwargs):
313 if "case_name" not in kwargs:
314 kwargs["case_name"] = 'singlevm1'
315 super(SingleVm1, self).__init__(**kwargs)
321 (_, self.key_filename) = tempfile.mkstemp()
324 """Create the security group and the keypair
326 It can be overriden to set other rules according to the services
329 Raises: Exception on error
332 self.keypair = self.cloud.create_keypair(
333 '{}-kp_{}'.format(self.case_name, self.guid))
334 self.__logger.debug("keypair: %s", self.keypair)
335 self.__logger.debug("private_key: %s", self.keypair.private_key)
336 with open(self.key_filename, 'w') as private_key_file:
337 private_key_file.write(self.keypair.private_key)
338 self.sec = self.cloud.create_security_group(
339 '{}-sg_{}'.format(self.case_name, self.guid),
340 'created by OPNFV Functest ({})'.format(self.case_name))
341 self.cloud.create_security_group_rule(
342 self.sec.id, port_range_min='22', port_range_max='22',
343 protocol='tcp', direction='ingress')
344 self.cloud.create_security_group_rule(
345 self.sec.id, protocol='icmp', direction='ingress')
347 def connect(self, vm1):
348 """Connect to a virtual machine via ssh
350 It first adds a floating ip to the virtual machine and then establishes
358 fip = self.cloud.create_floating_ip(
359 network=self.ext_net.id, server=vm1, wait=True,
360 timeout=self.create_floating_ip_timeout)
361 self.__logger.debug("floating_ip: %s", fip)
362 ssh = paramiko.SSHClient()
363 ssh.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
364 for loop in range(self.ssh_connect_loops):
366 p_console = self.cloud.get_server_console(vm1)
367 self.__logger.debug("vm console: \n%s", p_console)
369 fip.floating_ip_address,
372 '{}_image_user'.format(self.case_name), self.username),
373 key_filename=self.key_filename,
376 '{}_vm_ssh_connect_timeout'.format(self.case_name),
377 self.ssh_connect_timeout))
379 except Exception as exc: # pylint: disable=broad-except
381 "try %s: cannot connect to %s: %s", loop + 1,
382 fip.floating_ip_address, exc)
386 "cannot connect to %s", fip.floating_ip_address)
391 """Say hello world via ssh
393 It can be overriden to execute any command.
395 Returns: echo exit codes
397 (_, stdout, stderr) = self.ssh.exec_command('echo Hello World')
398 self.__logger.debug("output:\n%s", stdout.read())
399 self.__logger.debug("error:\n%s", stderr.read())
400 return stdout.channel.recv_exit_status()
402 def run(self, **kwargs):
405 Here are the main actions:
408 - create the security group
409 - execute the right command over ssh
413 - TestCase.EX_RUN_ERROR on error
415 status = testcase.TestCase.EX_RUN_ERROR
418 assert super(SingleVm1, self).run(
419 **kwargs) == testcase.TestCase.EX_OK
422 self.sshvm = self.boot_vm(
423 key_name=self.keypair.id, security_groups=[self.sec.id])
424 (self.fip, self.ssh) = self.connect(self.sshvm)
425 if not self.execute():
427 status = testcase.TestCase.EX_OK
428 except Exception: # pylint: disable=broad-except
429 self.__logger.exception('Cannot run %s', self.case_name)
431 self.stop_time = time.time()
436 assert self.orig_cloud
439 self.cloud.delete_floating_ip(self.fip.id)
441 self.cloud.delete_server(self.sshvm, wait=True)
443 self.cloud.delete_security_group(self.sec.id)
445 self.cloud.delete_keypair(self.keypair.name)
446 super(SingleVm1, self).clean()
447 except Exception: # pylint: disable=broad-except
448 self.__logger.exception("Cannot clean all ressources")
451 class SingleVm2(SingleVm1):
452 """Deploy a single VM reachable via ssh (scenario2)
454 It creates new user/project before creating and configuring all tenant
455 network ressources and vms required by advanced testcases.
457 It ensures that all testcases inheriting from SingleVm2 could work
458 without specific configurations (or at least read the same config data).
461 __logger = logging.getLogger(__name__)
463 def __init__(self, **kwargs):
464 if "case_name" not in kwargs:
465 kwargs["case_name"] = 'singlevm2'
466 super(SingleVm2, self).__init__(**kwargs)
468 assert self.orig_cloud
469 self.project = tenantnetwork.NewProject(
470 self.orig_cloud, self.case_name, self.guid)
471 self.project.create()
472 self.cloud = self.project.cloud
473 except Exception: # pylint: disable=broad-except
474 self.__logger.exception("Cannot create user or project")
480 super(SingleVm2, self).clean()
483 except Exception: # pylint: disable=broad-except
484 self.__logger.exception("Cannot clean all ressources")