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'
43 image_alt_format = image_format
44 visibility = 'private'
45 extra_properties = None
49 flavor_extra_specs = {}
53 flavor_alt_extra_specs = {}
54 create_server_timeout = 180
56 def __init__(self, **kwargs):
57 if "case_name" not in kwargs:
58 kwargs["case_name"] = 'vmready1'
59 super(VmReady1, self).__init__(**kwargs)
60 self.orig_cloud = self.cloud
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 image = self.cloud.create_image(
76 name if name else '{}-img_{}'.format(self.case_name, self.guid),
78 config.CONF, '{}_image'.format(self.case_name),
81 config.CONF, '{}_extra_properties'.format(self.case_name),
82 self.extra_properties),
84 config.CONF, '{}_image_format'.format(self.case_name),
87 config.CONF, '{}_visibility'.format(self.case_name),
90 self.__logger.debug("image: %s", image)
93 def publish_image_alt(self, name=None):
94 """Publish alternative image
96 It allows publishing multiple images for the child testcases. It forces
97 the same configuration for all subtestcases.
101 Raises: expection on error
104 image = self.cloud.create_image(
105 name if name else '{}-img_alt_{}'.format(
106 self.case_name, self.guid),
108 config.CONF, '{}_image_alt'.format(self.case_name),
111 config.CONF, '{}_extra_properties'.format(self.case_name),
112 self.extra_properties),
114 config.CONF, '{}_image_alt_format'.format(self.case_name),
117 config.CONF, '{}_visibility'.format(self.case_name),
120 self.__logger.debug("image: %s", image)
123 def create_flavor(self, name=None):
126 It allows creating multiple flavors for the child testcases. It forces
127 the same configuration for all subtestcases.
131 Raises: expection on error
133 assert self.orig_cloud
134 flavor = self.orig_cloud.create_flavor(
135 name if name else '{}-flavor_{}'.format(self.case_name, self.guid),
136 getattr(config.CONF, '{}_flavor_ram'.format(self.case_name),
138 getattr(config.CONF, '{}_flavor_vcpus'.format(self.case_name),
140 getattr(config.CONF, '{}_flavor_disk'.format(self.case_name),
142 self.__logger.debug("flavor: %s", flavor)
143 flavor_extra_specs_updated = self.flavor_extra_specs.copy()
144 flavor_extra_specs_updated.update(
146 '{}_flavor_extra_specs'.format(self.case_name), {}))
147 self.orig_cloud.set_flavor_specs(flavor.id, flavor_extra_specs_updated)
150 def create_flavor_alt(self, name=None):
153 It allows creating multiple alt flavors for the child testcases. It
154 forces the same configuration for all subtestcases.
158 Raises: expection on error
160 assert self.orig_cloud
161 flavor = self.orig_cloud.create_flavor(
162 name if name else '{}-flavor_alt_{}'.format(
163 self.case_name, self.guid),
164 getattr(config.CONF, '{}_flavor_alt_ram'.format(self.case_name),
165 self.flavor_alt_ram),
166 getattr(config.CONF, '{}_flavor_alt_vcpus'.format(self.case_name),
167 self.flavor_alt_vcpus),
168 getattr(config.CONF, '{}_flavor_alt_disk'.format(self.case_name),
169 self.flavor_alt_disk))
170 self.__logger.debug("flavor: %s", flavor)
171 flavor_alt_extra_specs_updated = self.flavor_alt_extra_specs.copy()
172 flavor_alt_extra_specs_updated.update(
174 '{}_flavor_alt_extra_specs'.format(self.case_name), {}))
175 self.orig_cloud.set_flavor_specs(
176 flavor.id, flavor_alt_extra_specs_updated)
179 def boot_vm(self, name=None, **kwargs):
180 """Boot the virtual machine
182 It allows booting multiple machines for the child testcases. It forces
183 the same configuration for all subtestcases.
187 Raises: expection on error
190 vm1 = self.cloud.create_server(
191 name if name else '{}-vm_{}'.format(self.case_name, self.guid),
192 image=self.image.id, flavor=self.flavor.id,
193 auto_ip=False, network=self.network.id,
194 timeout=self.create_server_timeout, wait=True, **kwargs)
195 self.__logger.debug("vm: %s", vm1)
198 def check_regex_in_console(self, name, regex=' login: ', loop=1):
199 """Wait for specific message in console
201 Returns: True or False on errors
204 for iloop in range(loop):
205 console = self.cloud.get_server_console(name)
206 self.__logger.debug("console: \n%s", console)
207 if re.search(regex, console):
208 self.__logger.debug("regex found: ''%s' in console", regex)
212 "try %s: cannot find regex '%s' in console",
215 self.__logger.error("cannot find regex '%s' in console", regex)
218 def run(self, **kwargs):
221 Here are the main actions:
227 - TestCase.EX_RUN_ERROR on error
229 status = testcase.TestCase.EX_RUN_ERROR
232 assert super(VmReady1, self).run(
233 **kwargs) == testcase.TestCase.EX_OK
234 self.image = self.publish_image()
235 self.flavor = self.create_flavor()
237 status = testcase.TestCase.EX_OK
238 except Exception: # pylint: disable=broad-except
239 self.__logger.exception('Cannot run %s', self.case_name)
242 self.stop_time = time.time()
247 assert self.orig_cloud
249 super(VmReady1, self).clean()
251 self.cloud.delete_image(self.image.id)
253 self.orig_cloud.delete_flavor(self.flavor.id)
254 except Exception: # pylint: disable=broad-except
255 self.__logger.exception("Cannot clean all ressources")
258 class VmReady2(VmReady1):
259 """Deploy a single VM reachable via ssh (scenario2)
261 It creates new user/project before creating and configuring all tenant
262 network ressources, flavors, images, etc. required by advanced testcases.
264 It ensures that all testcases inheriting from SingleVm2 could work
265 without specific configurations (or at least read the same config data).
268 __logger = logging.getLogger(__name__)
270 def __init__(self, **kwargs):
271 if "case_name" not in kwargs:
272 kwargs["case_name"] = 'vmready2'
273 super(VmReady2, self).__init__(**kwargs)
275 assert self.orig_cloud
276 self.project = tenantnetwork.NewProject(
277 self.orig_cloud, self.case_name, self.guid)
278 self.project.create()
279 self.cloud = self.project.cloud
280 except Exception: # pylint: disable=broad-except
281 self.__logger.exception("Cannot create user or project")
287 super(VmReady2, self).clean()
290 except Exception: # pylint: disable=broad-except
291 self.__logger.exception("Cannot clean all ressources")
294 class SingleVm1(VmReady1):
295 """Deploy a single VM reachable via ssh (scenario1)
297 It inherits from TenantNetwork1 which creates all network resources and
298 completes it by booting a VM attached to that network.
300 It ensures that all testcases inheriting from SingleVm1 could work
301 without specific configurations (or at least read the same config data).
303 # pylint: disable=too-many-instance-attributes
305 __logger = logging.getLogger(__name__)
307 ssh_connect_timeout = 1
308 ssh_connect_loops = 6
309 create_floating_ip_timeout = 120
311 def __init__(self, **kwargs):
312 if "case_name" not in kwargs:
313 kwargs["case_name"] = 'singlevm1'
314 super(SingleVm1, self).__init__(**kwargs)
320 (_, self.key_filename) = tempfile.mkstemp()
323 """Create the security group and the keypair
325 It can be overriden to set other rules according to the services
328 Raises: Exception on error
331 self.keypair = self.cloud.create_keypair(
332 '{}-kp_{}'.format(self.case_name, self.guid))
333 self.__logger.debug("keypair: %s", self.keypair)
334 self.__logger.debug("private_key: %s", self.keypair.private_key)
335 with open(self.key_filename, 'w') as private_key_file:
336 private_key_file.write(self.keypair.private_key)
337 self.sec = self.cloud.create_security_group(
338 '{}-sg_{}'.format(self.case_name, self.guid),
339 'created by OPNFV Functest ({})'.format(self.case_name))
340 self.cloud.create_security_group_rule(
341 self.sec.id, port_range_min='22', port_range_max='22',
342 protocol='tcp', direction='ingress')
343 self.cloud.create_security_group_rule(
344 self.sec.id, protocol='icmp', direction='ingress')
346 def connect(self, vm1):
347 """Connect to a virtual machine via ssh
349 It first adds a floating ip to the virtual machine and then establishes
357 fip = self.cloud.create_floating_ip(
358 network=self.ext_net.id, server=vm1, wait=True,
359 timeout=self.create_floating_ip_timeout)
360 self.__logger.debug("floating_ip: %s", fip)
361 ssh = paramiko.SSHClient()
362 ssh.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
363 for loop in range(self.ssh_connect_loops):
365 p_console = self.cloud.get_server_console(vm1)
366 self.__logger.debug("vm console: \n%s", p_console)
368 fip.floating_ip_address,
371 '{}_image_user'.format(self.case_name), self.username),
372 key_filename=self.key_filename,
375 '{}_vm_ssh_connect_timeout'.format(self.case_name),
376 self.ssh_connect_timeout))
378 except Exception as exc: # pylint: disable=broad-except
380 "try %s: cannot connect to %s: %s", loop + 1,
381 fip.floating_ip_address, exc)
385 "cannot connect to %s", fip.floating_ip_address)
390 """Say hello world via ssh
392 It can be overriden to execute any command.
394 Returns: echo exit codes
396 (_, stdout, stderr) = self.ssh.exec_command('echo Hello World')
397 self.__logger.debug("output:\n%s", stdout.read())
398 self.__logger.debug("error:\n%s", stderr.read())
399 return stdout.channel.recv_exit_status()
401 def run(self, **kwargs):
404 Here are the main actions:
407 - create the security group
408 - execute the right command over ssh
412 - TestCase.EX_RUN_ERROR on error
414 status = testcase.TestCase.EX_RUN_ERROR
417 assert super(SingleVm1, self).run(
418 **kwargs) == testcase.TestCase.EX_OK
421 self.sshvm = self.boot_vm(
422 key_name=self.keypair.id, security_groups=[self.sec.id])
423 (self.fip, self.ssh) = self.connect(self.sshvm)
424 if not self.execute():
426 status = testcase.TestCase.EX_OK
427 except Exception: # pylint: disable=broad-except
428 self.__logger.exception('Cannot run %s', self.case_name)
430 self.stop_time = time.time()
435 assert self.orig_cloud
438 self.cloud.delete_floating_ip(self.fip.id)
440 self.cloud.delete_server(self.sshvm, wait=True)
442 self.cloud.delete_security_group(self.sec.id)
444 self.cloud.delete_keypair(self.keypair.name)
445 super(SingleVm1, self).clean()
446 except Exception: # pylint: disable=broad-except
447 self.__logger.exception("Cannot clean all ressources")
450 class SingleVm2(SingleVm1):
451 """Deploy a single VM reachable via ssh (scenario2)
453 It creates new user/project before creating and configuring all tenant
454 network ressources and vms required by advanced testcases.
456 It ensures that all testcases inheriting from SingleVm2 could work
457 without specific configurations (or at least read the same config data).
460 __logger = logging.getLogger(__name__)
462 def __init__(self, **kwargs):
463 if "case_name" not in kwargs:
464 kwargs["case_name"] = 'singlevm2'
465 super(SingleVm2, self).__init__(**kwargs)
467 assert self.orig_cloud
468 self.project = tenantnetwork.NewProject(
469 self.orig_cloud, self.case_name, self.guid)
470 self.project.create()
471 self.cloud = self.project.cloud
472 except Exception: # pylint: disable=broad-except
473 self.__logger.exception("Cannot create user or project")
479 super(SingleVm2, self).clean()
482 except Exception: # pylint: disable=broad-except
483 self.__logger.exception("Cannot clean all ressources")