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 resources + 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
26 from functest.utils import env
27 from functest.utils import functest_utils
30 class VmReady1(tenantnetwork.TenantNetwork1):
31 """Prepare a single VM (scenario1)
33 It inherits from TenantNetwork1 which creates all network resources and
34 prepares a future VM attached to that network.
36 It ensures that all testcases inheriting from SingleVm1 could work
37 without specific configurations (or at least read the same config data).
39 # pylint: disable=too-many-instance-attributes
41 __logger = logging.getLogger(__name__)
42 filename = '/home/opnfv/functest/images/cirros-0.4.0-x86_64-disk.img'
43 image_format = 'qcow2'
45 filename_alt = filename
46 image_alt_format = image_format
47 extra_alt_properties = extra_properties
48 visibility = 'private'
52 flavor_extra_specs = {}
56 flavor_alt_extra_specs = flavor_extra_specs
57 create_server_timeout = 180
59 def __init__(self, **kwargs):
60 if "case_name" not in kwargs:
61 kwargs["case_name"] = 'vmready1'
62 super(VmReady1, self).__init__(**kwargs)
66 def publish_image(self, name=None):
69 It allows publishing multiple images for the child testcases. It forces
70 the same configuration for all subtestcases.
74 Raises: expection on error
77 extra_properties = self.extra_properties.copy()
78 if env.get('IMAGE_PROPERTIES'):
79 extra_properties.update(
80 functest_utils.convert_ini_to_dict(
81 env.get('IMAGE_PROPERTIES')))
82 extra_properties.update(
83 getattr(config.CONF, '{}_extra_properties'.format(
85 image = self.cloud.create_image(
86 name if name else '{}-img_{}'.format(self.case_name, self.guid),
88 config.CONF, '{}_image'.format(self.case_name),
90 meta=extra_properties,
92 config.CONF, '{}_image_format'.format(self.case_name),
95 config.CONF, '{}_visibility'.format(self.case_name),
98 self.__logger.debug("image: %s", image)
101 def publish_image_alt(self, name=None):
102 """Publish alternative image
104 It allows publishing multiple images for the child testcases. It forces
105 the same configuration for all subtestcases.
109 Raises: expection on error
112 extra_alt_properties = self.extra_alt_properties.copy()
113 if env.get('IMAGE_PROPERTIES'):
114 extra_alt_properties.update(
115 functest_utils.convert_ini_to_dict(
116 env.get('IMAGE_PROPERTIES')))
117 extra_alt_properties.update(
118 getattr(config.CONF, '{}_extra_alt_properties'.format(
119 self.case_name), {}))
120 image = self.cloud.create_image(
121 name if name else '{}-img_alt_{}'.format(
122 self.case_name, self.guid),
124 config.CONF, '{}_image_alt'.format(self.case_name),
126 meta=extra_alt_properties,
128 config.CONF, '{}_image_alt_format'.format(self.case_name),
131 config.CONF, '{}_visibility'.format(self.case_name),
134 self.__logger.debug("image: %s", image)
137 def create_flavor(self, name=None):
140 It allows creating multiple flavors for the child testcases. It forces
141 the same configuration for all subtestcases.
145 Raises: expection on error
147 assert self.orig_cloud
148 flavor = self.orig_cloud.create_flavor(
149 name if name else '{}-flavor_{}'.format(self.case_name, self.guid),
150 getattr(config.CONF, '{}_flavor_ram'.format(self.case_name),
152 getattr(config.CONF, '{}_flavor_vcpus'.format(self.case_name),
154 getattr(config.CONF, '{}_flavor_disk'.format(self.case_name),
156 self.__logger.debug("flavor: %s", flavor)
157 flavor_extra_specs = self.flavor_extra_specs.copy()
158 if env.get('FLAVOR_EXTRA_SPECS'):
159 flavor_extra_specs.update(
160 functest_utils.convert_ini_to_dict(
161 env.get('FLAVOR_EXTRA_SPECS')))
162 flavor_extra_specs.update(
164 '{}_flavor_extra_specs'.format(self.case_name), {}))
165 self.orig_cloud.set_flavor_specs(flavor.id, flavor_extra_specs)
168 def create_flavor_alt(self, name=None):
171 It allows creating multiple alt flavors for the child testcases. It
172 forces the same configuration for all subtestcases.
176 Raises: expection on error
178 assert self.orig_cloud
179 flavor = self.orig_cloud.create_flavor(
180 name if name else '{}-flavor_alt_{}'.format(
181 self.case_name, self.guid),
182 getattr(config.CONF, '{}_flavor_alt_ram'.format(self.case_name),
183 self.flavor_alt_ram),
184 getattr(config.CONF, '{}_flavor_alt_vcpus'.format(self.case_name),
185 self.flavor_alt_vcpus),
186 getattr(config.CONF, '{}_flavor_alt_disk'.format(self.case_name),
187 self.flavor_alt_disk))
188 self.__logger.debug("flavor: %s", flavor)
189 flavor_alt_extra_specs = self.flavor_alt_extra_specs.copy()
190 if env.get('FLAVOR_EXTRA_SPECS'):
191 flavor_alt_extra_specs.update(
192 functest_utils.convert_ini_to_dict(
193 env.get('FLAVOR_EXTRA_SPECS')))
194 flavor_alt_extra_specs.update(
196 '{}_flavor_alt_extra_specs'.format(self.case_name), {}))
197 self.orig_cloud.set_flavor_specs(
198 flavor.id, flavor_alt_extra_specs)
201 def boot_vm(self, name=None, **kwargs):
202 """Boot the virtual machine
204 It allows booting multiple machines for the child testcases. It forces
205 the same configuration for all subtestcases.
209 Raises: expection on error
212 vm1 = self.cloud.create_server(
213 name if name else '{}-vm_{}'.format(self.case_name, self.guid),
214 image=self.image.id, flavor=self.flavor.id,
215 auto_ip=False, network=self.network.id,
216 timeout=self.create_server_timeout, wait=True, **kwargs)
217 self.__logger.debug("vm: %s", vm1)
220 def check_regex_in_console(self, name, regex=' login: ', loop=1):
221 """Wait for specific message in console
223 Returns: True or False on errors
226 for iloop in range(loop):
227 console = self.cloud.get_server_console(name)
228 self.__logger.debug("console: \n%s", console)
229 if re.search(regex, console):
231 "regex found: '%s' in console\n%s", regex, console)
235 "try %s: cannot find regex '%s' in console\n%s",
236 iloop + 1, regex, console)
238 self.__logger.error("cannot find regex '%s' in console", regex)
241 def run(self, **kwargs):
244 Here are the main actions:
250 - TestCase.EX_RUN_ERROR on error
252 status = testcase.TestCase.EX_RUN_ERROR
255 assert super(VmReady1, self).run(
256 **kwargs) == testcase.TestCase.EX_OK
257 self.image = self.publish_image()
258 self.flavor = self.create_flavor()
260 status = testcase.TestCase.EX_OK
261 except Exception: # pylint: disable=broad-except
262 self.__logger.exception('Cannot run %s', self.case_name)
265 self.stop_time = time.time()
270 assert self.orig_cloud
272 super(VmReady1, self).clean()
274 self.cloud.delete_image(self.image.id)
276 self.orig_cloud.delete_flavor(self.flavor.id)
277 except Exception: # pylint: disable=broad-except
278 self.__logger.exception("Cannot clean all resources")
281 class VmReady2(VmReady1):
282 """Deploy a single VM reachable via ssh (scenario2)
284 It creates new user/project before creating and configuring all tenant
285 network resources, flavors, images, etc. required by advanced testcases.
287 It ensures that all testcases inheriting from SingleVm2 could work
288 without specific configurations (or at least read the same config data).
291 __logger = logging.getLogger(__name__)
293 def __init__(self, **kwargs):
294 if "case_name" not in kwargs:
295 kwargs["case_name"] = 'vmready2'
296 super(VmReady2, self).__init__(**kwargs)
298 assert self.orig_cloud
299 self.project = tenantnetwork.NewProject(
300 self.orig_cloud, self.case_name, self.guid)
301 self.project.create()
302 self.cloud = self.project.cloud
303 except Exception: # pylint: disable=broad-except
304 self.__logger.exception("Cannot create user or project")
310 super(VmReady2, self).clean()
313 except Exception: # pylint: disable=broad-except
314 self.__logger.exception("Cannot clean all resources")
317 class SingleVm1(VmReady1):
318 """Deploy a single VM reachable via ssh (scenario1)
320 It inherits from TenantNetwork1 which creates all network resources and
321 completes it by booting a VM attached to that network.
323 It ensures that all testcases inheriting from SingleVm1 could work
324 without specific configurations (or at least read the same config data).
326 # pylint: disable=too-many-instance-attributes
328 __logger = logging.getLogger(__name__)
330 ssh_connect_timeout = 1
331 ssh_connect_loops = 6
332 create_floating_ip_timeout = 120
334 def __init__(self, **kwargs):
335 if "case_name" not in kwargs:
336 kwargs["case_name"] = 'singlevm1'
337 super(SingleVm1, self).__init__(**kwargs)
343 (_, self.key_filename) = tempfile.mkstemp()
346 """Create the security group and the keypair
348 It can be overriden to set other rules according to the services
351 Raises: Exception on error
354 self.keypair = self.cloud.create_keypair(
355 '{}-kp_{}'.format(self.case_name, self.guid))
356 self.__logger.debug("keypair: %s", self.keypair)
357 self.__logger.debug("private_key:\n%s", self.keypair.private_key)
358 with open(self.key_filename, 'w') as private_key_file:
359 private_key_file.write(self.keypair.private_key)
360 self.sec = self.cloud.create_security_group(
361 '{}-sg_{}'.format(self.case_name, self.guid),
362 'created by OPNFV Functest ({})'.format(self.case_name))
363 self.cloud.create_security_group_rule(
364 self.sec.id, port_range_min='22', port_range_max='22',
365 protocol='tcp', direction='ingress')
366 self.cloud.create_security_group_rule(
367 self.sec.id, protocol='icmp', direction='ingress')
369 def connect(self, vm1):
370 """Connect to a virtual machine via ssh
372 It first adds a floating ip to the virtual machine and then establishes
380 fip = self.cloud.create_floating_ip(
381 network=self.ext_net.id, server=vm1, wait=True,
382 timeout=self.create_floating_ip_timeout)
383 self.__logger.debug("floating_ip: %s", fip)
384 ssh = paramiko.SSHClient()
385 ssh.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
386 for loop in range(self.ssh_connect_loops):
388 p_console = self.cloud.get_server_console(vm1)
389 self.__logger.debug("vm console: \n%s", p_console)
391 fip.floating_ip_address,
394 '{}_image_user'.format(self.case_name), self.username),
395 key_filename=self.key_filename,
398 '{}_vm_ssh_connect_timeout'.format(self.case_name),
399 self.ssh_connect_timeout))
401 except Exception as exc: # pylint: disable=broad-except
403 "try %s: cannot connect to %s: %s", loop + 1,
404 fip.floating_ip_address, exc)
408 "cannot connect to %s", fip.floating_ip_address)
413 """Say hello world via ssh
415 It can be overriden to execute any command.
417 Returns: echo exit codes
419 (_, stdout, stderr) = self.ssh.exec_command('echo Hello World')
420 self.__logger.debug("output:\n%s", stdout.read())
421 self.__logger.debug("error:\n%s", stderr.read())
422 return stdout.channel.recv_exit_status()
424 def run(self, **kwargs):
427 Here are the main actions:
430 - create the security group
431 - execute the right command over ssh
435 - TestCase.EX_RUN_ERROR on error
437 status = testcase.TestCase.EX_RUN_ERROR
440 assert super(SingleVm1, self).run(
441 **kwargs) == testcase.TestCase.EX_OK
444 self.sshvm = self.boot_vm(
445 key_name=self.keypair.id, security_groups=[self.sec.id])
446 (self.fip, self.ssh) = self.connect(self.sshvm)
447 if not self.execute():
449 status = testcase.TestCase.EX_OK
450 except Exception: # pylint: disable=broad-except
451 self.__logger.exception('Cannot run %s', self.case_name)
453 self.stop_time = time.time()
458 assert self.orig_cloud
461 self.cloud.delete_floating_ip(self.fip.id)
463 self.cloud.delete_server(self.sshvm, wait=True)
465 self.cloud.delete_security_group(self.sec.id)
467 self.cloud.delete_keypair(self.keypair.name)
468 super(SingleVm1, self).clean()
469 except Exception: # pylint: disable=broad-except
470 self.__logger.exception("Cannot clean all resources")
473 class SingleVm2(SingleVm1):
474 """Deploy a single VM reachable via ssh (scenario2)
476 It creates new user/project before creating and configuring all tenant
477 network resources and vms required by advanced testcases.
479 It ensures that all testcases inheriting from SingleVm2 could work
480 without specific configurations (or at least read the same config data).
483 __logger = logging.getLogger(__name__)
485 def __init__(self, **kwargs):
486 if "case_name" not in kwargs:
487 kwargs["case_name"] = 'singlevm2'
488 super(SingleVm2, self).__init__(**kwargs)
490 assert self.orig_cloud
491 self.project = tenantnetwork.NewProject(
492 self.orig_cloud, self.case_name, self.guid)
493 self.project.create()
494 self.cloud = self.project.cloud
495 except Exception: # pylint: disable=broad-except
496 self.__logger.exception("Cannot create user or project")
502 super(SingleVm2, self).clean()
505 except Exception: # pylint: disable=broad-except
506 self.__logger.exception("Cannot clean all resources")