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
52 create_server_timeout = 180
54 def __init__(self, **kwargs):
55 if "case_name" not in kwargs:
56 kwargs["case_name"] = 'vmready1'
57 super(VmReady1, self).__init__(**kwargs)
58 self.orig_cloud = self.cloud
62 def publish_image(self, name=None):
65 It allows publishing multiple images for the child testcases. It forces
66 the same configuration for all subtestcases.
70 Raises: expection on error
73 image = self.cloud.create_image(
74 name if name else '{}-img_{}'.format(self.case_name, self.guid),
76 config.CONF, '{}_image'.format(self.case_name),
79 config.CONF, '{}_extra_properties'.format(self.case_name),
80 self.extra_properties),
82 config.CONF, '{}_image_format'.format(self.case_name),
85 config.CONF, '{}_visibility'.format(self.case_name),
88 self.__logger.debug("image: %s", image)
91 def publish_image_alt(self, name=None):
92 """Publish alternative image
94 It allows publishing multiple images for the child testcases. It forces
95 the same configuration for all subtestcases.
99 Raises: expection on error
102 image = self.cloud.create_image(
103 name if name else '{}-img_alt_{}'.format(
104 self.case_name, self.guid),
106 config.CONF, '{}_image_alt'.format(self.case_name),
109 config.CONF, '{}_extra_properties'.format(self.case_name),
110 self.extra_properties),
112 config.CONF, '{}_image_alt_format'.format(self.case_name),
115 config.CONF, '{}_visibility'.format(self.case_name),
118 self.__logger.debug("image: %s", image)
121 def create_flavor(self, name=None):
124 It allows creating multiple flavors for the child testcases. It forces
125 the same configuration for all subtestcases.
129 Raises: expection on error
131 assert self.orig_cloud
132 flavor = self.orig_cloud.create_flavor(
133 name if name else '{}-flavor_{}'.format(self.case_name, self.guid),
134 getattr(config.CONF, '{}_flavor_ram'.format(self.case_name),
136 getattr(config.CONF, '{}_flavor_vcpus'.format(self.case_name),
138 getattr(config.CONF, '{}_flavor_disk'.format(self.case_name),
140 self.__logger.debug("flavor: %s", flavor)
141 self.orig_cloud.set_flavor_specs(
142 flavor.id, getattr(config.CONF, 'flavor_extra_specs', {}))
145 def create_flavor_alt(self, name=None):
148 It allows creating multiple alt flavors for the child testcases. It
149 forces the same configuration for all subtestcases.
153 Raises: expection on error
155 assert self.orig_cloud
156 flavor = self.orig_cloud.create_flavor(
157 name if name else '{}-flavor_alt_{}'.format(
158 self.case_name, self.guid),
159 getattr(config.CONF, '{}_flavor_alt_ram'.format(self.case_name),
160 self.flavor_alt_ram),
161 getattr(config.CONF, '{}_flavor_alt_vcpus'.format(self.case_name),
162 self.flavor_alt_vcpus),
163 getattr(config.CONF, '{}_flavor_alt_disk'.format(self.case_name),
164 self.flavor_alt_disk))
165 self.__logger.debug("flavor: %s", flavor)
166 self.orig_cloud.set_flavor_specs(
167 flavor.id, getattr(config.CONF, 'flavor_extra_specs', {}))
170 def boot_vm(self, name=None, **kwargs):
171 """Boot the virtual machine
173 It allows booting multiple machines for the child testcases. It forces
174 the same configuration for all subtestcases.
178 Raises: expection on error
181 vm1 = self.cloud.create_server(
182 name if name else '{}-vm_{}'.format(self.case_name, self.guid),
183 image=self.image.id, flavor=self.flavor.id,
184 auto_ip=False, network=self.network.id,
185 timeout=self.create_server_timeout, wait=True, **kwargs)
186 self.__logger.debug("vm: %s", vm1)
189 def check_regex_in_console(self, name, regex=' login: ', loop=1):
190 """Wait for specific message in console
192 Returns: True or False on errors
195 for iloop in range(loop):
196 console = self.cloud.get_server_console(name)
197 self.__logger.debug("console: \n%s", console)
198 if re.search(regex, console):
199 self.__logger.debug("regex found: ''%s' in console", regex)
203 "try %s: cannot find regex '%s' in console",
206 self.__logger.error("cannot find regex '%s' in console", regex)
209 def run(self, **kwargs):
212 Here are the main actions:
218 - TestCase.EX_RUN_ERROR on error
220 status = testcase.TestCase.EX_RUN_ERROR
223 assert super(VmReady1, self).run(
224 **kwargs) == testcase.TestCase.EX_OK
225 self.image = self.publish_image()
226 self.flavor = self.create_flavor()
228 status = testcase.TestCase.EX_OK
229 except Exception: # pylint: disable=broad-except
230 self.__logger.exception('Cannot run %s', self.case_name)
233 self.stop_time = time.time()
238 assert self.orig_cloud
240 super(VmReady1, self).clean()
242 self.cloud.delete_image(self.image.id)
244 self.orig_cloud.delete_flavor(self.flavor.id)
245 except Exception: # pylint: disable=broad-except
246 self.__logger.exception("Cannot clean all ressources")
249 class VmReady2(VmReady1):
250 """Deploy a single VM reachable via ssh (scenario2)
252 It creates new user/project before creating and configuring all tenant
253 network ressources, flavors, images, etc. required by advanced testcases.
255 It ensures that all testcases inheriting from SingleVm2 could work
256 without specific configurations (or at least read the same config data).
259 __logger = logging.getLogger(__name__)
261 def __init__(self, **kwargs):
262 if "case_name" not in kwargs:
263 kwargs["case_name"] = 'vmready2'
264 super(VmReady2, self).__init__(**kwargs)
266 assert self.orig_cloud
267 self.project = tenantnetwork.NewProject(
268 self.orig_cloud, self.case_name, self.guid)
269 self.project.create()
270 self.cloud = self.project.cloud
271 except Exception: # pylint: disable=broad-except
272 self.__logger.exception("Cannot create user or project")
278 super(VmReady2, self).clean()
281 except Exception: # pylint: disable=broad-except
282 self.__logger.exception("Cannot clean all ressources")
285 class SingleVm1(VmReady1):
286 """Deploy a single VM reachable via ssh (scenario1)
288 It inherits from TenantNetwork1 which creates all network resources and
289 completes it by booting a VM attached to that network.
291 It ensures that all testcases inheriting from SingleVm1 could work
292 without specific configurations (or at least read the same config data).
294 # pylint: disable=too-many-instance-attributes
296 __logger = logging.getLogger(__name__)
298 ssh_connect_timeout = 1
299 ssh_connect_loops = 6
300 create_floating_ip_timeout = 120
302 def __init__(self, **kwargs):
303 if "case_name" not in kwargs:
304 kwargs["case_name"] = 'singlevm1'
305 super(SingleVm1, self).__init__(**kwargs)
311 (_, self.key_filename) = tempfile.mkstemp()
314 """Create the security group and the keypair
316 It can be overriden to set other rules according to the services
319 Raises: Exception on error
322 self.keypair = self.cloud.create_keypair(
323 '{}-kp_{}'.format(self.case_name, self.guid))
324 self.__logger.debug("keypair: %s", self.keypair)
325 self.__logger.debug("private_key: %s", self.keypair.private_key)
326 with open(self.key_filename, 'w') as private_key_file:
327 private_key_file.write(self.keypair.private_key)
328 self.sec = self.cloud.create_security_group(
329 '{}-sg_{}'.format(self.case_name, self.guid),
330 'created by OPNFV Functest ({})'.format(self.case_name))
331 self.cloud.create_security_group_rule(
332 self.sec.id, port_range_min='22', port_range_max='22',
333 protocol='tcp', direction='ingress')
334 self.cloud.create_security_group_rule(
335 self.sec.id, protocol='icmp', direction='ingress')
337 def connect(self, vm1):
338 """Connect to a virtual machine via ssh
340 It first adds a floating ip to the virtual machine and then establishes
348 fip = self.cloud.create_floating_ip(
349 network=self.ext_net.id, server=vm1, wait=True,
350 timeout=self.create_floating_ip_timeout)
351 self.__logger.debug("floating_ip: %s", fip)
352 ssh = paramiko.SSHClient()
353 ssh.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
354 for loop in range(self.ssh_connect_loops):
356 p_console = self.cloud.get_server_console(vm1)
357 self.__logger.debug("vm console: \n%s", p_console)
359 fip.floating_ip_address,
362 '{}_image_user'.format(self.case_name), self.username),
363 key_filename=self.key_filename,
366 '{}_vm_ssh_connect_timeout'.format(self.case_name),
367 self.ssh_connect_timeout))
369 except Exception as exc: # pylint: disable=broad-except
371 "try %s: cannot connect to %s: %s", loop + 1,
372 fip.floating_ip_address, exc)
376 "cannot connect to %s", fip.floating_ip_address)
381 """Say hello world via ssh
383 It can be overriden to execute any command.
385 Returns: echo exit codes
387 (_, stdout, stderr) = self.ssh.exec_command('echo Hello World')
388 self.__logger.debug("output:\n%s", stdout.read())
389 self.__logger.debug("error:\n%s", stderr.read())
390 return stdout.channel.recv_exit_status()
392 def run(self, **kwargs):
395 Here are the main actions:
398 - create the security group
399 - execute the right command over ssh
403 - TestCase.EX_RUN_ERROR on error
405 status = testcase.TestCase.EX_RUN_ERROR
408 assert super(SingleVm1, self).run(
409 **kwargs) == testcase.TestCase.EX_OK
412 self.sshvm = self.boot_vm(
413 key_name=self.keypair.id, security_groups=[self.sec.id])
414 (self.fip, self.ssh) = self.connect(self.sshvm)
415 if not self.execute():
417 status = testcase.TestCase.EX_OK
418 except Exception: # pylint: disable=broad-except
419 self.__logger.exception('Cannot run %s', self.case_name)
421 self.stop_time = time.time()
426 assert self.orig_cloud
429 self.cloud.delete_floating_ip(self.fip.id)
431 self.cloud.delete_server(self.sshvm, wait=True)
433 self.cloud.delete_security_group(self.sec.id)
435 self.cloud.delete_keypair(self.keypair.name)
436 super(SingleVm1, self).clean()
437 except Exception: # pylint: disable=broad-except
438 self.__logger.exception("Cannot clean all ressources")
441 class SingleVm2(SingleVm1):
442 """Deploy a single VM reachable via ssh (scenario2)
444 It creates new user/project before creating and configuring all tenant
445 network ressources and vms required by advanced testcases.
447 It ensures that all testcases inheriting from SingleVm2 could work
448 without specific configurations (or at least read the same config data).
451 __logger = logging.getLogger(__name__)
453 def __init__(self, **kwargs):
454 if "case_name" not in kwargs:
455 kwargs["case_name"] = 'singlevm2'
456 super(SingleVm2, self).__init__(**kwargs)
458 assert self.orig_cloud
459 self.project = tenantnetwork.NewProject(
460 self.orig_cloud, self.case_name, self.guid)
461 self.project.create()
462 self.cloud = self.project.cloud
463 except Exception: # pylint: disable=broad-except
464 self.__logger.exception("Cannot create user or project")
470 super(SingleVm2, self).clean()
473 except Exception: # pylint: disable=broad-except
474 self.__logger.exception("Cannot clean all ressources")