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
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 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 extra_properties = self.extra_properties.copy()
77 extra_properties.update(
78 getattr(config.CONF, '{}_extra_properties'.format(
80 image = self.cloud.create_image(
81 name if name else '{}-img_{}'.format(self.case_name, self.guid),
83 config.CONF, '{}_image'.format(self.case_name),
85 meta=extra_properties,
87 config.CONF, '{}_image_format'.format(self.case_name),
90 config.CONF, '{}_visibility'.format(self.case_name),
93 self.__logger.debug("image: %s", image)
96 def publish_image_alt(self, name=None):
97 """Publish alternative image
99 It allows publishing multiple images for the child testcases. It forces
100 the same configuration for all subtestcases.
104 Raises: expection on error
107 extra_alt_properties = self.extra_alt_properties.copy()
108 extra_alt_properties.update(
109 getattr(config.CONF, '{}_extra_alt_properties'.format(
110 self.case_name), {}))
111 image = self.cloud.create_image(
112 name if name else '{}-img_alt_{}'.format(
113 self.case_name, self.guid),
115 config.CONF, '{}_image_alt'.format(self.case_name),
117 meta=extra_alt_properties,
119 config.CONF, '{}_image_alt_format'.format(self.case_name),
122 config.CONF, '{}_visibility'.format(self.case_name),
125 self.__logger.debug("image: %s", image)
128 def create_flavor(self, name=None):
131 It allows creating multiple flavors for the child testcases. It forces
132 the same configuration for all subtestcases.
136 Raises: expection on error
138 assert self.orig_cloud
139 flavor = self.orig_cloud.create_flavor(
140 name if name else '{}-flavor_{}'.format(self.case_name, self.guid),
141 getattr(config.CONF, '{}_flavor_ram'.format(self.case_name),
143 getattr(config.CONF, '{}_flavor_vcpus'.format(self.case_name),
145 getattr(config.CONF, '{}_flavor_disk'.format(self.case_name),
147 self.__logger.debug("flavor: %s", flavor)
148 flavor_extra_specs = self.flavor_extra_specs.copy()
149 flavor_extra_specs.update(
151 '{}_flavor_extra_specs'.format(self.case_name), {}))
152 self.orig_cloud.set_flavor_specs(flavor.id, flavor_extra_specs)
155 def create_flavor_alt(self, name=None):
158 It allows creating multiple alt flavors for the child testcases. It
159 forces the same configuration for all subtestcases.
163 Raises: expection on error
165 assert self.orig_cloud
166 flavor = self.orig_cloud.create_flavor(
167 name if name else '{}-flavor_alt_{}'.format(
168 self.case_name, self.guid),
169 getattr(config.CONF, '{}_flavor_alt_ram'.format(self.case_name),
170 self.flavor_alt_ram),
171 getattr(config.CONF, '{}_flavor_alt_vcpus'.format(self.case_name),
172 self.flavor_alt_vcpus),
173 getattr(config.CONF, '{}_flavor_alt_disk'.format(self.case_name),
174 self.flavor_alt_disk))
175 self.__logger.debug("flavor: %s", flavor)
176 flavor_alt_extra_specs = self.flavor_alt_extra_specs.copy()
177 flavor_alt_extra_specs.update(
179 '{}_flavor_alt_extra_specs'.format(self.case_name), {}))
180 self.orig_cloud.set_flavor_specs(
181 flavor.id, flavor_alt_extra_specs)
184 def boot_vm(self, name=None, **kwargs):
185 """Boot the virtual machine
187 It allows booting multiple machines for the child testcases. It forces
188 the same configuration for all subtestcases.
192 Raises: expection on error
195 vm1 = self.cloud.create_server(
196 name if name else '{}-vm_{}'.format(self.case_name, self.guid),
197 image=self.image.id, flavor=self.flavor.id,
198 auto_ip=False, network=self.network.id,
199 timeout=self.create_server_timeout, wait=True, **kwargs)
200 self.__logger.debug("vm: %s", vm1)
203 def check_regex_in_console(self, name, regex=' login: ', loop=1):
204 """Wait for specific message in console
206 Returns: True or False on errors
209 for iloop in range(loop):
210 console = self.cloud.get_server_console(name)
211 self.__logger.debug("console: \n%s", console)
212 if re.search(regex, console):
213 self.__logger.debug("regex found: ''%s' in console", regex)
217 "try %s: cannot find regex '%s' in console",
220 self.__logger.error("cannot find regex '%s' in console", regex)
223 def run(self, **kwargs):
226 Here are the main actions:
232 - TestCase.EX_RUN_ERROR on error
234 status = testcase.TestCase.EX_RUN_ERROR
237 assert super(VmReady1, self).run(
238 **kwargs) == testcase.TestCase.EX_OK
239 self.image = self.publish_image()
240 self.flavor = self.create_flavor()
242 status = testcase.TestCase.EX_OK
243 except Exception: # pylint: disable=broad-except
244 self.__logger.exception('Cannot run %s', self.case_name)
247 self.stop_time = time.time()
252 assert self.orig_cloud
254 super(VmReady1, self).clean()
256 self.cloud.delete_image(self.image.id)
258 self.orig_cloud.delete_flavor(self.flavor.id)
259 except Exception: # pylint: disable=broad-except
260 self.__logger.exception("Cannot clean all resources")
263 class VmReady2(VmReady1):
264 """Deploy a single VM reachable via ssh (scenario2)
266 It creates new user/project before creating and configuring all tenant
267 network resources, flavors, images, etc. required by advanced testcases.
269 It ensures that all testcases inheriting from SingleVm2 could work
270 without specific configurations (or at least read the same config data).
273 __logger = logging.getLogger(__name__)
275 def __init__(self, **kwargs):
276 if "case_name" not in kwargs:
277 kwargs["case_name"] = 'vmready2'
278 super(VmReady2, self).__init__(**kwargs)
280 assert self.orig_cloud
281 self.project = tenantnetwork.NewProject(
282 self.orig_cloud, self.case_name, self.guid)
283 self.project.create()
284 self.cloud = self.project.cloud
285 except Exception: # pylint: disable=broad-except
286 self.__logger.exception("Cannot create user or project")
292 super(VmReady2, self).clean()
295 except Exception: # pylint: disable=broad-except
296 self.__logger.exception("Cannot clean all resources")
299 class SingleVm1(VmReady1):
300 """Deploy a single VM reachable via ssh (scenario1)
302 It inherits from TenantNetwork1 which creates all network resources and
303 completes it by booting a VM attached to that network.
305 It ensures that all testcases inheriting from SingleVm1 could work
306 without specific configurations (or at least read the same config data).
308 # pylint: disable=too-many-instance-attributes
310 __logger = logging.getLogger(__name__)
312 ssh_connect_timeout = 1
313 ssh_connect_loops = 6
314 create_floating_ip_timeout = 120
316 def __init__(self, **kwargs):
317 if "case_name" not in kwargs:
318 kwargs["case_name"] = 'singlevm1'
319 super(SingleVm1, self).__init__(**kwargs)
325 (_, self.key_filename) = tempfile.mkstemp()
328 """Create the security group and the keypair
330 It can be overriden to set other rules according to the services
333 Raises: Exception on error
336 self.keypair = self.cloud.create_keypair(
337 '{}-kp_{}'.format(self.case_name, self.guid))
338 self.__logger.debug("keypair: %s", self.keypair)
339 self.__logger.debug("private_key: %s", self.keypair.private_key)
340 with open(self.key_filename, 'w') as private_key_file:
341 private_key_file.write(self.keypair.private_key)
342 self.sec = self.cloud.create_security_group(
343 '{}-sg_{}'.format(self.case_name, self.guid),
344 'created by OPNFV Functest ({})'.format(self.case_name))
345 self.cloud.create_security_group_rule(
346 self.sec.id, port_range_min='22', port_range_max='22',
347 protocol='tcp', direction='ingress')
348 self.cloud.create_security_group_rule(
349 self.sec.id, protocol='icmp', direction='ingress')
351 def connect(self, vm1):
352 """Connect to a virtual machine via ssh
354 It first adds a floating ip to the virtual machine and then establishes
362 fip = self.cloud.create_floating_ip(
363 network=self.ext_net.id, server=vm1, wait=True,
364 timeout=self.create_floating_ip_timeout)
365 self.__logger.debug("floating_ip: %s", fip)
366 ssh = paramiko.SSHClient()
367 ssh.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
368 for loop in range(self.ssh_connect_loops):
370 p_console = self.cloud.get_server_console(vm1)
371 self.__logger.debug("vm console: \n%s", p_console)
373 fip.floating_ip_address,
376 '{}_image_user'.format(self.case_name), self.username),
377 key_filename=self.key_filename,
380 '{}_vm_ssh_connect_timeout'.format(self.case_name),
381 self.ssh_connect_timeout))
383 except Exception as exc: # pylint: disable=broad-except
385 "try %s: cannot connect to %s: %s", loop + 1,
386 fip.floating_ip_address, exc)
390 "cannot connect to %s", fip.floating_ip_address)
395 """Say hello world via ssh
397 It can be overriden to execute any command.
399 Returns: echo exit codes
401 (_, stdout, stderr) = self.ssh.exec_command('echo Hello World')
402 self.__logger.debug("output:\n%s", stdout.read())
403 self.__logger.debug("error:\n%s", stderr.read())
404 return stdout.channel.recv_exit_status()
406 def run(self, **kwargs):
409 Here are the main actions:
412 - create the security group
413 - execute the right command over ssh
417 - TestCase.EX_RUN_ERROR on error
419 status = testcase.TestCase.EX_RUN_ERROR
422 assert super(SingleVm1, self).run(
423 **kwargs) == testcase.TestCase.EX_OK
426 self.sshvm = self.boot_vm(
427 key_name=self.keypair.id, security_groups=[self.sec.id])
428 (self.fip, self.ssh) = self.connect(self.sshvm)
429 if not self.execute():
431 status = testcase.TestCase.EX_OK
432 except Exception: # pylint: disable=broad-except
433 self.__logger.exception('Cannot run %s', self.case_name)
435 self.stop_time = time.time()
440 assert self.orig_cloud
443 self.cloud.delete_floating_ip(self.fip.id)
445 self.cloud.delete_server(self.sshvm, wait=True)
447 self.cloud.delete_security_group(self.sec.id)
449 self.cloud.delete_keypair(self.keypair.name)
450 super(SingleVm1, self).clean()
451 except Exception: # pylint: disable=broad-except
452 self.__logger.exception("Cannot clean all resources")
455 class SingleVm2(SingleVm1):
456 """Deploy a single VM reachable via ssh (scenario2)
458 It creates new user/project before creating and configuring all tenant
459 network resources and vms required by advanced testcases.
461 It ensures that all testcases inheriting from SingleVm2 could work
462 without specific configurations (or at least read the same config data).
465 __logger = logging.getLogger(__name__)
467 def __init__(self, **kwargs):
468 if "case_name" not in kwargs:
469 kwargs["case_name"] = 'singlevm2'
470 super(SingleVm2, self).__init__(**kwargs)
472 assert self.orig_cloud
473 self.project = tenantnetwork.NewProject(
474 self.orig_cloud, self.case_name, self.guid)
475 self.project.create()
476 self.cloud = self.project.cloud
477 except Exception: # pylint: disable=broad-except
478 self.__logger.exception("Cannot create user or project")
484 super(SingleVm2, self).clean()
487 except Exception: # pylint: disable=broad-except
488 self.__logger.exception("Cannot clean all resources")