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)
64 def publish_image(self, name=None):
67 It allows publishing multiple images for the child testcases. It forces
68 the same configuration for all subtestcases.
72 Raises: expection on error
75 extra_properties = self.extra_properties.copy()
76 extra_properties.update(
77 getattr(config.CONF, '{}_extra_properties'.format(
79 image = self.cloud.create_image(
80 name if name else '{}-img_{}'.format(self.case_name, self.guid),
82 config.CONF, '{}_image'.format(self.case_name),
84 meta=extra_properties,
86 config.CONF, '{}_image_format'.format(self.case_name),
89 config.CONF, '{}_visibility'.format(self.case_name),
92 self.__logger.debug("image: %s", image)
95 def publish_image_alt(self, name=None):
96 """Publish alternative image
98 It allows publishing multiple images for the child testcases. It forces
99 the same configuration for all subtestcases.
103 Raises: expection on error
106 extra_alt_properties = self.extra_alt_properties.copy()
107 extra_alt_properties.update(
108 getattr(config.CONF, '{}_extra_alt_properties'.format(
109 self.case_name), {}))
110 image = self.cloud.create_image(
111 name if name else '{}-img_alt_{}'.format(
112 self.case_name, self.guid),
114 config.CONF, '{}_image_alt'.format(self.case_name),
116 meta=extra_alt_properties,
118 config.CONF, '{}_image_alt_format'.format(self.case_name),
121 config.CONF, '{}_visibility'.format(self.case_name),
124 self.__logger.debug("image: %s", image)
127 def create_flavor(self, name=None):
130 It allows creating multiple flavors for the child testcases. It forces
131 the same configuration for all subtestcases.
135 Raises: expection on error
137 assert self.orig_cloud
138 flavor = self.orig_cloud.create_flavor(
139 name if name else '{}-flavor_{}'.format(self.case_name, self.guid),
140 getattr(config.CONF, '{}_flavor_ram'.format(self.case_name),
142 getattr(config.CONF, '{}_flavor_vcpus'.format(self.case_name),
144 getattr(config.CONF, '{}_flavor_disk'.format(self.case_name),
146 self.__logger.debug("flavor: %s", flavor)
147 flavor_extra_specs = self.flavor_extra_specs.copy()
148 flavor_extra_specs.update(
150 '{}_flavor_extra_specs'.format(self.case_name), {}))
151 self.orig_cloud.set_flavor_specs(flavor.id, flavor_extra_specs)
154 def create_flavor_alt(self, name=None):
157 It allows creating multiple alt flavors for the child testcases. It
158 forces the same configuration for all subtestcases.
162 Raises: expection on error
164 assert self.orig_cloud
165 flavor = self.orig_cloud.create_flavor(
166 name if name else '{}-flavor_alt_{}'.format(
167 self.case_name, self.guid),
168 getattr(config.CONF, '{}_flavor_alt_ram'.format(self.case_name),
169 self.flavor_alt_ram),
170 getattr(config.CONF, '{}_flavor_alt_vcpus'.format(self.case_name),
171 self.flavor_alt_vcpus),
172 getattr(config.CONF, '{}_flavor_alt_disk'.format(self.case_name),
173 self.flavor_alt_disk))
174 self.__logger.debug("flavor: %s", flavor)
175 flavor_alt_extra_specs = self.flavor_alt_extra_specs.copy()
176 flavor_alt_extra_specs.update(
178 '{}_flavor_alt_extra_specs'.format(self.case_name), {}))
179 self.orig_cloud.set_flavor_specs(
180 flavor.id, flavor_alt_extra_specs)
183 def boot_vm(self, name=None, **kwargs):
184 """Boot the virtual machine
186 It allows booting multiple machines for the child testcases. It forces
187 the same configuration for all subtestcases.
191 Raises: expection on error
194 vm1 = self.cloud.create_server(
195 name if name else '{}-vm_{}'.format(self.case_name, self.guid),
196 image=self.image.id, flavor=self.flavor.id,
197 auto_ip=False, network=self.network.id,
198 timeout=self.create_server_timeout, wait=True, **kwargs)
199 self.__logger.debug("vm: %s", vm1)
202 def check_regex_in_console(self, name, regex=' login: ', loop=1):
203 """Wait for specific message in console
205 Returns: True or False on errors
208 for iloop in range(loop):
209 console = self.cloud.get_server_console(name)
210 self.__logger.debug("console: \n%s", console)
211 if re.search(regex, console):
212 self.__logger.debug("regex found: ''%s' in console", regex)
216 "try %s: cannot find regex '%s' in console",
219 self.__logger.error("cannot find regex '%s' in console", regex)
222 def run(self, **kwargs):
225 Here are the main actions:
231 - TestCase.EX_RUN_ERROR on error
233 status = testcase.TestCase.EX_RUN_ERROR
236 assert super(VmReady1, self).run(
237 **kwargs) == testcase.TestCase.EX_OK
238 self.image = self.publish_image()
239 self.flavor = self.create_flavor()
241 status = testcase.TestCase.EX_OK
242 except Exception: # pylint: disable=broad-except
243 self.__logger.exception('Cannot run %s', self.case_name)
246 self.stop_time = time.time()
251 assert self.orig_cloud
253 super(VmReady1, self).clean()
255 self.cloud.delete_image(self.image.id)
257 self.orig_cloud.delete_flavor(self.flavor.id)
258 except Exception: # pylint: disable=broad-except
259 self.__logger.exception("Cannot clean all resources")
262 class VmReady2(VmReady1):
263 """Deploy a single VM reachable via ssh (scenario2)
265 It creates new user/project before creating and configuring all tenant
266 network resources, flavors, images, etc. required by advanced testcases.
268 It ensures that all testcases inheriting from SingleVm2 could work
269 without specific configurations (or at least read the same config data).
272 __logger = logging.getLogger(__name__)
274 def __init__(self, **kwargs):
275 if "case_name" not in kwargs:
276 kwargs["case_name"] = 'vmready2'
277 super(VmReady2, self).__init__(**kwargs)
279 assert self.orig_cloud
280 self.project = tenantnetwork.NewProject(
281 self.orig_cloud, self.case_name, self.guid)
282 self.project.create()
283 self.cloud = self.project.cloud
284 except Exception: # pylint: disable=broad-except
285 self.__logger.exception("Cannot create user or project")
291 super(VmReady2, self).clean()
294 except Exception: # pylint: disable=broad-except
295 self.__logger.exception("Cannot clean all resources")
298 class SingleVm1(VmReady1):
299 """Deploy a single VM reachable via ssh (scenario1)
301 It inherits from TenantNetwork1 which creates all network resources and
302 completes it by booting a VM attached to that network.
304 It ensures that all testcases inheriting from SingleVm1 could work
305 without specific configurations (or at least read the same config data).
307 # pylint: disable=too-many-instance-attributes
309 __logger = logging.getLogger(__name__)
311 ssh_connect_timeout = 1
312 ssh_connect_loops = 6
313 create_floating_ip_timeout = 120
315 def __init__(self, **kwargs):
316 if "case_name" not in kwargs:
317 kwargs["case_name"] = 'singlevm1'
318 super(SingleVm1, self).__init__(**kwargs)
324 (_, self.key_filename) = tempfile.mkstemp()
327 """Create the security group and the keypair
329 It can be overriden to set other rules according to the services
332 Raises: Exception on error
335 self.keypair = self.cloud.create_keypair(
336 '{}-kp_{}'.format(self.case_name, self.guid))
337 self.__logger.debug("keypair: %s", self.keypair)
338 self.__logger.debug("private_key: %s", self.keypair.private_key)
339 with open(self.key_filename, 'w') as private_key_file:
340 private_key_file.write(self.keypair.private_key)
341 self.sec = self.cloud.create_security_group(
342 '{}-sg_{}'.format(self.case_name, self.guid),
343 'created by OPNFV Functest ({})'.format(self.case_name))
344 self.cloud.create_security_group_rule(
345 self.sec.id, port_range_min='22', port_range_max='22',
346 protocol='tcp', direction='ingress')
347 self.cloud.create_security_group_rule(
348 self.sec.id, protocol='icmp', direction='ingress')
350 def connect(self, vm1):
351 """Connect to a virtual machine via ssh
353 It first adds a floating ip to the virtual machine and then establishes
361 fip = self.cloud.create_floating_ip(
362 network=self.ext_net.id, server=vm1, wait=True,
363 timeout=self.create_floating_ip_timeout)
364 self.__logger.debug("floating_ip: %s", fip)
365 ssh = paramiko.SSHClient()
366 ssh.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
367 for loop in range(self.ssh_connect_loops):
369 p_console = self.cloud.get_server_console(vm1)
370 self.__logger.debug("vm console: \n%s", p_console)
372 fip.floating_ip_address,
375 '{}_image_user'.format(self.case_name), self.username),
376 key_filename=self.key_filename,
379 '{}_vm_ssh_connect_timeout'.format(self.case_name),
380 self.ssh_connect_timeout))
382 except Exception as exc: # pylint: disable=broad-except
384 "try %s: cannot connect to %s: %s", loop + 1,
385 fip.floating_ip_address, exc)
389 "cannot connect to %s", fip.floating_ip_address)
394 """Say hello world via ssh
396 It can be overriden to execute any command.
398 Returns: echo exit codes
400 (_, stdout, stderr) = self.ssh.exec_command('echo Hello World')
401 self.__logger.debug("output:\n%s", stdout.read())
402 self.__logger.debug("error:\n%s", stderr.read())
403 return stdout.channel.recv_exit_status()
405 def run(self, **kwargs):
408 Here are the main actions:
411 - create the security group
412 - execute the right command over ssh
416 - TestCase.EX_RUN_ERROR on error
418 status = testcase.TestCase.EX_RUN_ERROR
421 assert super(SingleVm1, self).run(
422 **kwargs) == testcase.TestCase.EX_OK
425 self.sshvm = self.boot_vm(
426 key_name=self.keypair.id, security_groups=[self.sec.id])
427 (self.fip, self.ssh) = self.connect(self.sshvm)
428 if not self.execute():
430 status = testcase.TestCase.EX_OK
431 except Exception: # pylint: disable=broad-except
432 self.__logger.exception('Cannot run %s', self.case_name)
434 self.stop_time = time.time()
439 assert self.orig_cloud
442 self.cloud.delete_floating_ip(self.fip.id)
444 self.cloud.delete_server(self.sshvm, wait=True)
446 self.cloud.delete_security_group(self.sec.id)
448 self.cloud.delete_keypair(self.keypair.name)
449 super(SingleVm1, self).clean()
450 except Exception: # pylint: disable=broad-except
451 self.__logger.exception("Cannot clean all resources")
454 class SingleVm2(SingleVm1):
455 """Deploy a single VM reachable via ssh (scenario2)
457 It creates new user/project before creating and configuring all tenant
458 network resources and vms required by advanced testcases.
460 It ensures that all testcases inheriting from SingleVm2 could work
461 without specific configurations (or at least read the same config data).
464 __logger = logging.getLogger(__name__)
466 def __init__(self, **kwargs):
467 if "case_name" not in kwargs:
468 kwargs["case_name"] = 'singlevm2'
469 super(SingleVm2, self).__init__(**kwargs)
471 assert self.orig_cloud
472 self.project = tenantnetwork.NewProject(
473 self.orig_cloud, self.case_name, self.guid)
474 self.project.create()
475 self.cloud = self.project.cloud
476 except Exception: # pylint: disable=broad-except
477 self.__logger.exception("Cannot create user or project")
483 super(SingleVm2, self).clean()
486 except Exception: # pylint: disable=broad-except
487 self.__logger.exception("Cannot clean all resources")