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
29 class VmReady1(tenantnetwork.TenantNetwork1):
30 """Prepare a single VM (scenario1)
32 It inherits from TenantNetwork1 which creates all network resources and
33 prepares a future VM attached to that network.
35 It ensures that all testcases inheriting from SingleVm1 could work
36 without specific configurations (or at least read the same config data).
38 # pylint: disable=too-many-instance-attributes
40 __logger = logging.getLogger(__name__)
41 filename = '/home/opnfv/functest/images/cirros-0.4.0-x86_64-disk.img'
42 image_format = 'qcow2'
44 filename_alt = filename
45 image_alt_format = image_format
46 extra_alt_properties = extra_properties
47 visibility = 'private'
51 flavor_extra_specs = {}
55 flavor_alt_extra_specs = flavor_extra_specs
56 create_server_timeout = 180
58 def __init__(self, **kwargs):
59 if "case_name" not in kwargs:
60 kwargs["case_name"] = 'vmready1'
61 super(VmReady1, self).__init__(**kwargs)
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 extra_properties = self.extra_properties.copy()
77 if env.get('IMAGE_PROPERTIES'):
78 extra_properties.update(
79 dict((k.strip(), v.strip()) for k, v in (
80 item.split(': ') for item in env.get(
81 'IMAGE_PROPERTIES').split(','))))
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 dict((k.strip(), v.strip()) for k, v in (
116 item.split(': ') for item in env.get(
117 'IMAGE_PROPERTIES').split(','))))
118 extra_alt_properties.update(
119 getattr(config.CONF, '{}_extra_alt_properties'.format(
120 self.case_name), {}))
121 image = self.cloud.create_image(
122 name if name else '{}-img_alt_{}'.format(
123 self.case_name, self.guid),
125 config.CONF, '{}_image_alt'.format(self.case_name),
127 meta=extra_alt_properties,
129 config.CONF, '{}_image_alt_format'.format(self.case_name),
132 config.CONF, '{}_visibility'.format(self.case_name),
135 self.__logger.debug("image: %s", image)
138 def create_flavor(self, name=None):
141 It allows creating multiple flavors for the child testcases. It forces
142 the same configuration for all subtestcases.
146 Raises: expection on error
148 assert self.orig_cloud
149 flavor = self.orig_cloud.create_flavor(
150 name if name else '{}-flavor_{}'.format(self.case_name, self.guid),
151 getattr(config.CONF, '{}_flavor_ram'.format(self.case_name),
153 getattr(config.CONF, '{}_flavor_vcpus'.format(self.case_name),
155 getattr(config.CONF, '{}_flavor_disk'.format(self.case_name),
157 self.__logger.debug("flavor: %s", flavor)
158 flavor_extra_specs = self.flavor_extra_specs.copy()
159 flavor_extra_specs.update(
161 '{}_flavor_extra_specs'.format(self.case_name), {}))
162 self.orig_cloud.set_flavor_specs(flavor.id, flavor_extra_specs)
165 def create_flavor_alt(self, name=None):
168 It allows creating multiple alt flavors for the child testcases. It
169 forces the same configuration for all subtestcases.
173 Raises: expection on error
175 assert self.orig_cloud
176 flavor = self.orig_cloud.create_flavor(
177 name if name else '{}-flavor_alt_{}'.format(
178 self.case_name, self.guid),
179 getattr(config.CONF, '{}_flavor_alt_ram'.format(self.case_name),
180 self.flavor_alt_ram),
181 getattr(config.CONF, '{}_flavor_alt_vcpus'.format(self.case_name),
182 self.flavor_alt_vcpus),
183 getattr(config.CONF, '{}_flavor_alt_disk'.format(self.case_name),
184 self.flavor_alt_disk))
185 self.__logger.debug("flavor: %s", flavor)
186 flavor_alt_extra_specs = self.flavor_alt_extra_specs.copy()
187 flavor_alt_extra_specs.update(
189 '{}_flavor_alt_extra_specs'.format(self.case_name), {}))
190 self.orig_cloud.set_flavor_specs(
191 flavor.id, flavor_alt_extra_specs)
194 def boot_vm(self, name=None, **kwargs):
195 """Boot the virtual machine
197 It allows booting multiple machines for the child testcases. It forces
198 the same configuration for all subtestcases.
202 Raises: expection on error
205 vm1 = self.cloud.create_server(
206 name if name else '{}-vm_{}'.format(self.case_name, self.guid),
207 image=self.image.id, flavor=self.flavor.id,
208 auto_ip=False, network=self.network.id,
209 timeout=self.create_server_timeout, wait=True, **kwargs)
210 self.__logger.debug("vm: %s", vm1)
213 def check_regex_in_console(self, name, regex=' login: ', loop=1):
214 """Wait for specific message in console
216 Returns: True or False on errors
219 for iloop in range(loop):
220 console = self.cloud.get_server_console(name)
221 self.__logger.debug("console: \n%s", console)
222 if re.search(regex, console):
223 self.__logger.debug("regex found: ''%s' in console", regex)
227 "try %s: cannot find regex '%s' in console",
230 self.__logger.error("cannot find regex '%s' in console", regex)
233 def run(self, **kwargs):
236 Here are the main actions:
242 - TestCase.EX_RUN_ERROR on error
244 status = testcase.TestCase.EX_RUN_ERROR
247 assert super(VmReady1, self).run(
248 **kwargs) == testcase.TestCase.EX_OK
249 self.image = self.publish_image()
250 self.flavor = self.create_flavor()
252 status = testcase.TestCase.EX_OK
253 except Exception: # pylint: disable=broad-except
254 self.__logger.exception('Cannot run %s', self.case_name)
257 self.stop_time = time.time()
262 assert self.orig_cloud
264 super(VmReady1, self).clean()
266 self.cloud.delete_image(self.image.id)
268 self.orig_cloud.delete_flavor(self.flavor.id)
269 except Exception: # pylint: disable=broad-except
270 self.__logger.exception("Cannot clean all resources")
273 class VmReady2(VmReady1):
274 """Deploy a single VM reachable via ssh (scenario2)
276 It creates new user/project before creating and configuring all tenant
277 network resources, flavors, images, etc. required by advanced testcases.
279 It ensures that all testcases inheriting from SingleVm2 could work
280 without specific configurations (or at least read the same config data).
283 __logger = logging.getLogger(__name__)
285 def __init__(self, **kwargs):
286 if "case_name" not in kwargs:
287 kwargs["case_name"] = 'vmready2'
288 super(VmReady2, self).__init__(**kwargs)
290 assert self.orig_cloud
291 self.project = tenantnetwork.NewProject(
292 self.orig_cloud, self.case_name, self.guid)
293 self.project.create()
294 self.cloud = self.project.cloud
295 except Exception: # pylint: disable=broad-except
296 self.__logger.exception("Cannot create user or project")
302 super(VmReady2, self).clean()
305 except Exception: # pylint: disable=broad-except
306 self.__logger.exception("Cannot clean all resources")
309 class SingleVm1(VmReady1):
310 """Deploy a single VM reachable via ssh (scenario1)
312 It inherits from TenantNetwork1 which creates all network resources and
313 completes it by booting a VM attached to that network.
315 It ensures that all testcases inheriting from SingleVm1 could work
316 without specific configurations (or at least read the same config data).
318 # pylint: disable=too-many-instance-attributes
320 __logger = logging.getLogger(__name__)
322 ssh_connect_timeout = 1
323 ssh_connect_loops = 6
324 create_floating_ip_timeout = 120
326 def __init__(self, **kwargs):
327 if "case_name" not in kwargs:
328 kwargs["case_name"] = 'singlevm1'
329 super(SingleVm1, self).__init__(**kwargs)
335 (_, self.key_filename) = tempfile.mkstemp()
338 """Create the security group and the keypair
340 It can be overriden to set other rules according to the services
343 Raises: Exception on error
346 self.keypair = self.cloud.create_keypair(
347 '{}-kp_{}'.format(self.case_name, self.guid))
348 self.__logger.debug("keypair: %s", self.keypair)
349 self.__logger.debug("private_key: %s", self.keypair.private_key)
350 with open(self.key_filename, 'w') as private_key_file:
351 private_key_file.write(self.keypair.private_key)
352 self.sec = self.cloud.create_security_group(
353 '{}-sg_{}'.format(self.case_name, self.guid),
354 'created by OPNFV Functest ({})'.format(self.case_name))
355 self.cloud.create_security_group_rule(
356 self.sec.id, port_range_min='22', port_range_max='22',
357 protocol='tcp', direction='ingress')
358 self.cloud.create_security_group_rule(
359 self.sec.id, protocol='icmp', direction='ingress')
361 def connect(self, vm1):
362 """Connect to a virtual machine via ssh
364 It first adds a floating ip to the virtual machine and then establishes
372 fip = self.cloud.create_floating_ip(
373 network=self.ext_net.id, server=vm1, wait=True,
374 timeout=self.create_floating_ip_timeout)
375 self.__logger.debug("floating_ip: %s", fip)
376 ssh = paramiko.SSHClient()
377 ssh.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
378 for loop in range(self.ssh_connect_loops):
380 p_console = self.cloud.get_server_console(vm1)
381 self.__logger.debug("vm console: \n%s", p_console)
383 fip.floating_ip_address,
386 '{}_image_user'.format(self.case_name), self.username),
387 key_filename=self.key_filename,
390 '{}_vm_ssh_connect_timeout'.format(self.case_name),
391 self.ssh_connect_timeout))
393 except Exception as exc: # pylint: disable=broad-except
395 "try %s: cannot connect to %s: %s", loop + 1,
396 fip.floating_ip_address, exc)
400 "cannot connect to %s", fip.floating_ip_address)
405 """Say hello world via ssh
407 It can be overriden to execute any command.
409 Returns: echo exit codes
411 (_, stdout, stderr) = self.ssh.exec_command('echo Hello World')
412 self.__logger.debug("output:\n%s", stdout.read())
413 self.__logger.debug("error:\n%s", stderr.read())
414 return stdout.channel.recv_exit_status()
416 def run(self, **kwargs):
419 Here are the main actions:
422 - create the security group
423 - execute the right command over ssh
427 - TestCase.EX_RUN_ERROR on error
429 status = testcase.TestCase.EX_RUN_ERROR
432 assert super(SingleVm1, self).run(
433 **kwargs) == testcase.TestCase.EX_OK
436 self.sshvm = self.boot_vm(
437 key_name=self.keypair.id, security_groups=[self.sec.id])
438 (self.fip, self.ssh) = self.connect(self.sshvm)
439 if not self.execute():
441 status = testcase.TestCase.EX_OK
442 except Exception: # pylint: disable=broad-except
443 self.__logger.exception('Cannot run %s', self.case_name)
445 self.stop_time = time.time()
450 assert self.orig_cloud
453 self.cloud.delete_floating_ip(self.fip.id)
455 self.cloud.delete_server(self.sshvm, wait=True)
457 self.cloud.delete_security_group(self.sec.id)
459 self.cloud.delete_keypair(self.keypair.name)
460 super(SingleVm1, self).clean()
461 except Exception: # pylint: disable=broad-except
462 self.__logger.exception("Cannot clean all resources")
465 class SingleVm2(SingleVm1):
466 """Deploy a single VM reachable via ssh (scenario2)
468 It creates new user/project before creating and configuring all tenant
469 network resources and vms required by advanced testcases.
471 It ensures that all testcases inheriting from SingleVm2 could work
472 without specific configurations (or at least read the same config data).
475 __logger = logging.getLogger(__name__)
477 def __init__(self, **kwargs):
478 if "case_name" not in kwargs:
479 kwargs["case_name"] = 'singlevm2'
480 super(SingleVm2, self).__init__(**kwargs)
482 assert self.orig_cloud
483 self.project = tenantnetwork.NewProject(
484 self.orig_cloud, self.case_name, self.guid)
485 self.project.create()
486 self.cloud = self.project.cloud
487 except Exception: # pylint: disable=broad-except
488 self.__logger.exception("Cannot create user or project")
494 super(SingleVm2, self).clean()
497 except Exception: # pylint: disable=broad-except
498 self.__logger.exception("Cannot clean all resources")