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
29 class VmReady1(tenantnetwork.TenantNetwork1):
30 """Prepare a single VM (scenario1)
32 It inherits from TenantNetwork1 which creates all network resources and
33 prepares a future VM attached to that network.
35 It ensures that all testcases inheriting from SingleVm1 could work
36 without specific configurations (or at least read the same config data).
38 # pylint: disable=too-many-instance-attributes
40 __logger = logging.getLogger(__name__)
41 filename = '/home/opnfv/functest/images/cirros-0.4.0-x86_64-disk.img'
42 image_format = 'qcow2'
44 filename_alt = filename
45 image_alt_format = image_format
46 extra_alt_properties = extra_properties
47 visibility = 'private'
51 flavor_extra_specs = {}
55 flavor_alt_extra_specs = flavor_extra_specs
56 create_server_timeout = 180
58 def __init__(self, **kwargs):
59 if "case_name" not in kwargs:
60 kwargs["case_name"] = 'vmready1'
61 super(VmReady1, self).__init__(**kwargs)
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 if env.get('IMG_PROP'):
78 extra_properties.update(dict((k.strip(), v.strip()) for k, v in
79 (item.split(': ') for item in
80 env.get('IMG_PROP').split(','))))
81 extra_properties.update(
82 getattr(config.CONF, '{}_extra_properties'.format(
84 image = self.cloud.create_image(
85 name if name else '{}-img_{}'.format(self.case_name, self.guid),
87 config.CONF, '{}_image'.format(self.case_name),
89 meta=extra_properties,
91 config.CONF, '{}_image_format'.format(self.case_name),
94 config.CONF, '{}_visibility'.format(self.case_name),
97 self.__logger.debug("image: %s", image)
100 def publish_image_alt(self, name=None):
101 """Publish alternative image
103 It allows publishing multiple images for the child testcases. It forces
104 the same configuration for all subtestcases.
108 Raises: expection on error
111 extra_alt_properties = self.extra_alt_properties.copy()
112 if env.get('IMG_PROP'):
113 extra_alt_properties.update(dict((k.strip(), v.strip()) for k, v in
114 (item.split(': ') for item in
115 env.get('IMG_PROP').split(','))))
116 extra_alt_properties.update(
117 getattr(config.CONF, '{}_extra_alt_properties'.format(
118 self.case_name), {}))
119 image = self.cloud.create_image(
120 name if name else '{}-img_alt_{}'.format(
121 self.case_name, self.guid),
123 config.CONF, '{}_image_alt'.format(self.case_name),
125 meta=extra_alt_properties,
127 config.CONF, '{}_image_alt_format'.format(self.case_name),
130 config.CONF, '{}_visibility'.format(self.case_name),
133 self.__logger.debug("image: %s", image)
136 def create_flavor(self, name=None):
139 It allows creating multiple flavors for the child testcases. It forces
140 the same configuration for all subtestcases.
144 Raises: expection on error
146 assert self.orig_cloud
147 flavor = self.orig_cloud.create_flavor(
148 name if name else '{}-flavor_{}'.format(self.case_name, self.guid),
149 getattr(config.CONF, '{}_flavor_ram'.format(self.case_name),
151 getattr(config.CONF, '{}_flavor_vcpus'.format(self.case_name),
153 getattr(config.CONF, '{}_flavor_disk'.format(self.case_name),
155 self.__logger.debug("flavor: %s", flavor)
156 flavor_extra_specs = self.flavor_extra_specs.copy()
157 flavor_extra_specs.update(
159 '{}_flavor_extra_specs'.format(self.case_name), {}))
160 self.orig_cloud.set_flavor_specs(flavor.id, flavor_extra_specs)
163 def create_flavor_alt(self, name=None):
166 It allows creating multiple alt flavors for the child testcases. It
167 forces the same configuration for all subtestcases.
171 Raises: expection on error
173 assert self.orig_cloud
174 flavor = self.orig_cloud.create_flavor(
175 name if name else '{}-flavor_alt_{}'.format(
176 self.case_name, self.guid),
177 getattr(config.CONF, '{}_flavor_alt_ram'.format(self.case_name),
178 self.flavor_alt_ram),
179 getattr(config.CONF, '{}_flavor_alt_vcpus'.format(self.case_name),
180 self.flavor_alt_vcpus),
181 getattr(config.CONF, '{}_flavor_alt_disk'.format(self.case_name),
182 self.flavor_alt_disk))
183 self.__logger.debug("flavor: %s", flavor)
184 flavor_alt_extra_specs = self.flavor_alt_extra_specs.copy()
185 flavor_alt_extra_specs.update(
187 '{}_flavor_alt_extra_specs'.format(self.case_name), {}))
188 self.orig_cloud.set_flavor_specs(
189 flavor.id, flavor_alt_extra_specs)
192 def boot_vm(self, name=None, **kwargs):
193 """Boot the virtual machine
195 It allows booting multiple machines for the child testcases. It forces
196 the same configuration for all subtestcases.
200 Raises: expection on error
203 vm1 = self.cloud.create_server(
204 name if name else '{}-vm_{}'.format(self.case_name, self.guid),
205 image=self.image.id, flavor=self.flavor.id,
206 auto_ip=False, network=self.network.id,
207 timeout=self.create_server_timeout, wait=True, **kwargs)
208 self.__logger.debug("vm: %s", vm1)
211 def check_regex_in_console(self, name, regex=' login: ', loop=1):
212 """Wait for specific message in console
214 Returns: True or False on errors
217 for iloop in range(loop):
218 console = self.cloud.get_server_console(name)
219 self.__logger.debug("console: \n%s", console)
220 if re.search(regex, console):
221 self.__logger.debug("regex found: ''%s' in console", regex)
225 "try %s: cannot find regex '%s' in console",
228 self.__logger.error("cannot find regex '%s' in console", regex)
231 def run(self, **kwargs):
234 Here are the main actions:
240 - TestCase.EX_RUN_ERROR on error
242 status = testcase.TestCase.EX_RUN_ERROR
245 assert super(VmReady1, self).run(
246 **kwargs) == testcase.TestCase.EX_OK
247 self.image = self.publish_image()
248 self.flavor = self.create_flavor()
250 status = testcase.TestCase.EX_OK
251 except Exception: # pylint: disable=broad-except
252 self.__logger.exception('Cannot run %s', self.case_name)
255 self.stop_time = time.time()
260 assert self.orig_cloud
262 super(VmReady1, self).clean()
264 self.cloud.delete_image(self.image.id)
266 self.orig_cloud.delete_flavor(self.flavor.id)
267 except Exception: # pylint: disable=broad-except
268 self.__logger.exception("Cannot clean all resources")
271 class VmReady2(VmReady1):
272 """Deploy a single VM reachable via ssh (scenario2)
274 It creates new user/project before creating and configuring all tenant
275 network resources, flavors, images, etc. required by advanced testcases.
277 It ensures that all testcases inheriting from SingleVm2 could work
278 without specific configurations (or at least read the same config data).
281 __logger = logging.getLogger(__name__)
283 def __init__(self, **kwargs):
284 if "case_name" not in kwargs:
285 kwargs["case_name"] = 'vmready2'
286 super(VmReady2, self).__init__(**kwargs)
288 assert self.orig_cloud
289 self.project = tenantnetwork.NewProject(
290 self.orig_cloud, self.case_name, self.guid)
291 self.project.create()
292 self.cloud = self.project.cloud
293 except Exception: # pylint: disable=broad-except
294 self.__logger.exception("Cannot create user or project")
300 super(VmReady2, self).clean()
303 except Exception: # pylint: disable=broad-except
304 self.__logger.exception("Cannot clean all resources")
307 class SingleVm1(VmReady1):
308 """Deploy a single VM reachable via ssh (scenario1)
310 It inherits from TenantNetwork1 which creates all network resources and
311 completes it by booting a VM attached to that network.
313 It ensures that all testcases inheriting from SingleVm1 could work
314 without specific configurations (or at least read the same config data).
316 # pylint: disable=too-many-instance-attributes
318 __logger = logging.getLogger(__name__)
320 ssh_connect_timeout = 1
321 ssh_connect_loops = 6
322 create_floating_ip_timeout = 120
324 def __init__(self, **kwargs):
325 if "case_name" not in kwargs:
326 kwargs["case_name"] = 'singlevm1'
327 super(SingleVm1, self).__init__(**kwargs)
333 (_, self.key_filename) = tempfile.mkstemp()
336 """Create the security group and the keypair
338 It can be overriden to set other rules according to the services
341 Raises: Exception on error
344 self.keypair = self.cloud.create_keypair(
345 '{}-kp_{}'.format(self.case_name, self.guid))
346 self.__logger.debug("keypair: %s", self.keypair)
347 self.__logger.debug("private_key: %s", self.keypair.private_key)
348 with open(self.key_filename, 'w') as private_key_file:
349 private_key_file.write(self.keypair.private_key)
350 self.sec = self.cloud.create_security_group(
351 '{}-sg_{}'.format(self.case_name, self.guid),
352 'created by OPNFV Functest ({})'.format(self.case_name))
353 self.cloud.create_security_group_rule(
354 self.sec.id, port_range_min='22', port_range_max='22',
355 protocol='tcp', direction='ingress')
356 self.cloud.create_security_group_rule(
357 self.sec.id, protocol='icmp', direction='ingress')
359 def connect(self, vm1):
360 """Connect to a virtual machine via ssh
362 It first adds a floating ip to the virtual machine and then establishes
370 fip = self.cloud.create_floating_ip(
371 network=self.ext_net.id, server=vm1, wait=True,
372 timeout=self.create_floating_ip_timeout)
373 self.__logger.debug("floating_ip: %s", fip)
374 ssh = paramiko.SSHClient()
375 ssh.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
376 for loop in range(self.ssh_connect_loops):
378 p_console = self.cloud.get_server_console(vm1)
379 self.__logger.debug("vm console: \n%s", p_console)
381 fip.floating_ip_address,
384 '{}_image_user'.format(self.case_name), self.username),
385 key_filename=self.key_filename,
388 '{}_vm_ssh_connect_timeout'.format(self.case_name),
389 self.ssh_connect_timeout))
391 except Exception as exc: # pylint: disable=broad-except
393 "try %s: cannot connect to %s: %s", loop + 1,
394 fip.floating_ip_address, exc)
398 "cannot connect to %s", fip.floating_ip_address)
403 """Say hello world via ssh
405 It can be overriden to execute any command.
407 Returns: echo exit codes
409 (_, stdout, stderr) = self.ssh.exec_command('echo Hello World')
410 self.__logger.debug("output:\n%s", stdout.read())
411 self.__logger.debug("error:\n%s", stderr.read())
412 return stdout.channel.recv_exit_status()
414 def run(self, **kwargs):
417 Here are the main actions:
420 - create the security group
421 - execute the right command over ssh
425 - TestCase.EX_RUN_ERROR on error
427 status = testcase.TestCase.EX_RUN_ERROR
430 assert super(SingleVm1, self).run(
431 **kwargs) == testcase.TestCase.EX_OK
434 self.sshvm = self.boot_vm(
435 key_name=self.keypair.id, security_groups=[self.sec.id])
436 (self.fip, self.ssh) = self.connect(self.sshvm)
437 if not self.execute():
439 status = testcase.TestCase.EX_OK
440 except Exception: # pylint: disable=broad-except
441 self.__logger.exception('Cannot run %s', self.case_name)
443 self.stop_time = time.time()
448 assert self.orig_cloud
451 self.cloud.delete_floating_ip(self.fip.id)
453 self.cloud.delete_server(self.sshvm, wait=True)
455 self.cloud.delete_security_group(self.sec.id)
457 self.cloud.delete_keypair(self.keypair.name)
458 super(SingleVm1, self).clean()
459 except Exception: # pylint: disable=broad-except
460 self.__logger.exception("Cannot clean all resources")
463 class SingleVm2(SingleVm1):
464 """Deploy a single VM reachable via ssh (scenario2)
466 It creates new user/project before creating and configuring all tenant
467 network resources and vms required by advanced testcases.
469 It ensures that all testcases inheriting from SingleVm2 could work
470 without specific configurations (or at least read the same config data).
473 __logger = logging.getLogger(__name__)
475 def __init__(self, **kwargs):
476 if "case_name" not in kwargs:
477 kwargs["case_name"] = 'singlevm2'
478 super(SingleVm2, self).__init__(**kwargs)
480 assert self.orig_cloud
481 self.project = tenantnetwork.NewProject(
482 self.orig_cloud, self.case_name, self.guid)
483 self.project.create()
484 self.cloud = self.project.cloud
485 except Exception: # pylint: disable=broad-except
486 self.__logger.exception("Cannot create user or project")
492 super(SingleVm2, self).clean()
495 except Exception: # pylint: disable=broad-except
496 self.__logger.exception("Cannot clean all resources")