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 flavor_extra_specs.update(
160 '{}_flavor_extra_specs'.format(self.case_name), {}))
161 self.orig_cloud.set_flavor_specs(flavor.id, flavor_extra_specs)
164 def create_flavor_alt(self, name=None):
167 It allows creating multiple alt flavors for the child testcases. It
168 forces the same configuration for all subtestcases.
172 Raises: expection on error
174 assert self.orig_cloud
175 flavor = self.orig_cloud.create_flavor(
176 name if name else '{}-flavor_alt_{}'.format(
177 self.case_name, self.guid),
178 getattr(config.CONF, '{}_flavor_alt_ram'.format(self.case_name),
179 self.flavor_alt_ram),
180 getattr(config.CONF, '{}_flavor_alt_vcpus'.format(self.case_name),
181 self.flavor_alt_vcpus),
182 getattr(config.CONF, '{}_flavor_alt_disk'.format(self.case_name),
183 self.flavor_alt_disk))
184 self.__logger.debug("flavor: %s", flavor)
185 flavor_alt_extra_specs = self.flavor_alt_extra_specs.copy()
186 flavor_alt_extra_specs.update(
188 '{}_flavor_alt_extra_specs'.format(self.case_name), {}))
189 self.orig_cloud.set_flavor_specs(
190 flavor.id, flavor_alt_extra_specs)
193 def boot_vm(self, name=None, **kwargs):
194 """Boot the virtual machine
196 It allows booting multiple machines for the child testcases. It forces
197 the same configuration for all subtestcases.
201 Raises: expection on error
204 vm1 = self.cloud.create_server(
205 name if name else '{}-vm_{}'.format(self.case_name, self.guid),
206 image=self.image.id, flavor=self.flavor.id,
207 auto_ip=False, network=self.network.id,
208 timeout=self.create_server_timeout, wait=True, **kwargs)
209 self.__logger.debug("vm: %s", vm1)
212 def check_regex_in_console(self, name, regex=' login: ', loop=1):
213 """Wait for specific message in console
215 Returns: True or False on errors
218 for iloop in range(loop):
219 console = self.cloud.get_server_console(name)
220 self.__logger.debug("console: \n%s", console)
221 if re.search(regex, console):
222 self.__logger.debug("regex found: ''%s' in console", regex)
226 "try %s: cannot find regex '%s' in console",
229 self.__logger.error("cannot find regex '%s' in console", regex)
232 def run(self, **kwargs):
235 Here are the main actions:
241 - TestCase.EX_RUN_ERROR on error
243 status = testcase.TestCase.EX_RUN_ERROR
246 assert super(VmReady1, self).run(
247 **kwargs) == testcase.TestCase.EX_OK
248 self.image = self.publish_image()
249 self.flavor = self.create_flavor()
251 status = testcase.TestCase.EX_OK
252 except Exception: # pylint: disable=broad-except
253 self.__logger.exception('Cannot run %s', self.case_name)
256 self.stop_time = time.time()
261 assert self.orig_cloud
263 super(VmReady1, self).clean()
265 self.cloud.delete_image(self.image.id)
267 self.orig_cloud.delete_flavor(self.flavor.id)
268 except Exception: # pylint: disable=broad-except
269 self.__logger.exception("Cannot clean all resources")
272 class VmReady2(VmReady1):
273 """Deploy a single VM reachable via ssh (scenario2)
275 It creates new user/project before creating and configuring all tenant
276 network resources, flavors, images, etc. required by advanced testcases.
278 It ensures that all testcases inheriting from SingleVm2 could work
279 without specific configurations (or at least read the same config data).
282 __logger = logging.getLogger(__name__)
284 def __init__(self, **kwargs):
285 if "case_name" not in kwargs:
286 kwargs["case_name"] = 'vmready2'
287 super(VmReady2, self).__init__(**kwargs)
289 assert self.orig_cloud
290 self.project = tenantnetwork.NewProject(
291 self.orig_cloud, self.case_name, self.guid)
292 self.project.create()
293 self.cloud = self.project.cloud
294 except Exception: # pylint: disable=broad-except
295 self.__logger.exception("Cannot create user or project")
301 super(VmReady2, self).clean()
304 except Exception: # pylint: disable=broad-except
305 self.__logger.exception("Cannot clean all resources")
308 class SingleVm1(VmReady1):
309 """Deploy a single VM reachable via ssh (scenario1)
311 It inherits from TenantNetwork1 which creates all network resources and
312 completes it by booting a VM attached to that network.
314 It ensures that all testcases inheriting from SingleVm1 could work
315 without specific configurations (or at least read the same config data).
317 # pylint: disable=too-many-instance-attributes
319 __logger = logging.getLogger(__name__)
321 ssh_connect_timeout = 1
322 ssh_connect_loops = 6
323 create_floating_ip_timeout = 120
325 def __init__(self, **kwargs):
326 if "case_name" not in kwargs:
327 kwargs["case_name"] = 'singlevm1'
328 super(SingleVm1, self).__init__(**kwargs)
334 (_, self.key_filename) = tempfile.mkstemp()
337 """Create the security group and the keypair
339 It can be overriden to set other rules according to the services
342 Raises: Exception on error
345 self.keypair = self.cloud.create_keypair(
346 '{}-kp_{}'.format(self.case_name, self.guid))
347 self.__logger.debug("keypair: %s", self.keypair)
348 self.__logger.debug("private_key: %s", self.keypair.private_key)
349 with open(self.key_filename, 'w') as private_key_file:
350 private_key_file.write(self.keypair.private_key)
351 self.sec = self.cloud.create_security_group(
352 '{}-sg_{}'.format(self.case_name, self.guid),
353 'created by OPNFV Functest ({})'.format(self.case_name))
354 self.cloud.create_security_group_rule(
355 self.sec.id, port_range_min='22', port_range_max='22',
356 protocol='tcp', direction='ingress')
357 self.cloud.create_security_group_rule(
358 self.sec.id, protocol='icmp', direction='ingress')
360 def connect(self, vm1):
361 """Connect to a virtual machine via ssh
363 It first adds a floating ip to the virtual machine and then establishes
371 fip = self.cloud.create_floating_ip(
372 network=self.ext_net.id, server=vm1, wait=True,
373 timeout=self.create_floating_ip_timeout)
374 self.__logger.debug("floating_ip: %s", fip)
375 ssh = paramiko.SSHClient()
376 ssh.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
377 for loop in range(self.ssh_connect_loops):
379 p_console = self.cloud.get_server_console(vm1)
380 self.__logger.debug("vm console: \n%s", p_console)
382 fip.floating_ip_address,
385 '{}_image_user'.format(self.case_name), self.username),
386 key_filename=self.key_filename,
389 '{}_vm_ssh_connect_timeout'.format(self.case_name),
390 self.ssh_connect_timeout))
392 except Exception as exc: # pylint: disable=broad-except
394 "try %s: cannot connect to %s: %s", loop + 1,
395 fip.floating_ip_address, exc)
399 "cannot connect to %s", fip.floating_ip_address)
404 """Say hello world via ssh
406 It can be overriden to execute any command.
408 Returns: echo exit codes
410 (_, stdout, stderr) = self.ssh.exec_command('echo Hello World')
411 self.__logger.debug("output:\n%s", stdout.read())
412 self.__logger.debug("error:\n%s", stderr.read())
413 return stdout.channel.recv_exit_status()
415 def run(self, **kwargs):
418 Here are the main actions:
421 - create the security group
422 - execute the right command over ssh
426 - TestCase.EX_RUN_ERROR on error
428 status = testcase.TestCase.EX_RUN_ERROR
431 assert super(SingleVm1, self).run(
432 **kwargs) == testcase.TestCase.EX_OK
435 self.sshvm = self.boot_vm(
436 key_name=self.keypair.id, security_groups=[self.sec.id])
437 (self.fip, self.ssh) = self.connect(self.sshvm)
438 if not self.execute():
440 status = testcase.TestCase.EX_OK
441 except Exception: # pylint: disable=broad-except
442 self.__logger.exception('Cannot run %s', self.case_name)
444 self.stop_time = time.time()
449 assert self.orig_cloud
452 self.cloud.delete_floating_ip(self.fip.id)
454 self.cloud.delete_server(self.sshvm, wait=True)
456 self.cloud.delete_security_group(self.sec.id)
458 self.cloud.delete_keypair(self.keypair.name)
459 super(SingleVm1, self).clean()
460 except Exception: # pylint: disable=broad-except
461 self.__logger.exception("Cannot clean all resources")
464 class SingleVm2(SingleVm1):
465 """Deploy a single VM reachable via ssh (scenario2)
467 It creates new user/project before creating and configuring all tenant
468 network resources and vms required by advanced testcases.
470 It ensures that all testcases inheriting from SingleVm2 could work
471 without specific configurations (or at least read the same config data).
474 __logger = logging.getLogger(__name__)
476 def __init__(self, **kwargs):
477 if "case_name" not in kwargs:
478 kwargs["case_name"] = 'singlevm2'
479 super(SingleVm2, self).__init__(**kwargs)
481 assert self.orig_cloud
482 self.project = tenantnetwork.NewProject(
483 self.orig_cloud, self.case_name, self.guid)
484 self.project.create()
485 self.cloud = self.project.cloud
486 except Exception: # pylint: disable=broad-except
487 self.__logger.exception("Cannot create user or project")
493 super(SingleVm2, self).clean()
496 except Exception: # pylint: disable=broad-except
497 self.__logger.exception("Cannot clean all resources")