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).
21 from xtesting.core import testcase
23 from functest.core import tenantnetwork
24 from functest.utils import config
27 class VmReady1(tenantnetwork.TenantNetwork1):
28 """Prepare a single VM (scenario1)
30 It inherits from TenantNetwork1 which creates all network resources and
31 prepares a future VM attached to that network.
33 It ensures that all testcases inheriting from SingleVm1 could work
34 without specific configurations (or at least read the same config data).
36 # pylint: disable=too-many-instance-attributes
38 __logger = logging.getLogger(__name__)
39 filename = '/home/opnfv/functest/images/cirros-0.4.0-x86_64-disk.img'
40 visibility = 'private'
41 extra_properties = None
49 image_format = 'qcow2'
51 def __init__(self, **kwargs):
52 if "case_name" not in kwargs:
53 kwargs["case_name"] = 'vmready1'
54 super(VmReady1, self).__init__(**kwargs)
55 self.orig_cloud = self.cloud
59 def publish_image(self, name=None):
62 It allows publishing multiple images for the child testcases. It forces
63 the same configuration for all subtestcases.
67 Raises: expection on error
70 image = self.cloud.create_image(
71 name if name else '{}-img_{}'.format(self.case_name, self.guid),
73 config.CONF, '{}_image'.format(self.case_name),
76 config.CONF, '{}_extra_properties'.format(self.case_name),
77 self.extra_properties),
79 config.CONF, '{}_image_format'.format(self.case_name),
82 config.CONF, '{}_visibility'.format(self.case_name),
84 self.__logger.debug("image: %s", image)
87 def create_flavor(self, name=None):
90 It allows creating multiple flavors for the child testcases. It forces
91 the same configuration for all subtestcases.
95 Raises: expection on error
97 assert self.orig_cloud
98 flavor = self.orig_cloud.create_flavor(
99 name if name else '{}-flavor_{}'.format(self.case_name, self.guid),
100 getattr(config.CONF, '{}_flavor_ram'.format(self.case_name),
102 getattr(config.CONF, '{}_flavor_vcpus'.format(self.case_name),
104 getattr(config.CONF, '{}_flavor_disk'.format(self.case_name),
106 self.__logger.debug("flavor: %s", flavor)
107 self.orig_cloud.set_flavor_specs(
108 flavor.id, getattr(config.CONF, 'flavor_extra_specs', {}))
111 def create_flavor_alt(self, name=None):
114 It allows creating multiple alt flavors for the child testcases. It
115 forces the same configuration for all subtestcases.
119 Raises: expection on error
121 assert self.orig_cloud
122 flavor = self.orig_cloud.create_flavor(
123 name if name else '{}-flavor_alt_{}'.format(
124 self.case_name, self.guid),
125 getattr(config.CONF, '{}_flavor_alt_ram'.format(self.case_name),
126 self.flavor_alt_ram),
127 getattr(config.CONF, '{}_flavor_alt_vcpus'.format(self.case_name),
128 self.flavor_alt_vcpus),
129 getattr(config.CONF, '{}_flavor_alt_disk'.format(self.case_name),
130 self.flavor_alt_disk))
131 self.__logger.debug("flavor: %s", flavor)
132 self.orig_cloud.set_flavor_specs(
133 flavor.id, getattr(config.CONF, 'flavor_extra_specs', {}))
136 def boot_vm(self, name=None, **kwargs):
137 """Boot the virtual machine
139 It allows booting multiple machines for the child testcases. It forces
140 the same configuration for all subtestcases.
144 Raises: expection on error
147 vm1 = self.cloud.create_server(
148 name if name else '{}-vm_{}'.format(self.case_name, self.guid),
149 image=self.image.id, flavor=self.flavor.id,
150 auto_ip=False, wait=True,
151 network=self.network.id,
153 vm1 = self.cloud.wait_for_server(vm1, auto_ip=False)
154 self.__logger.debug("vm: %s", vm1)
157 def run(self, **kwargs):
160 Here are the main actions:
166 - TestCase.EX_RUN_ERROR on error
168 status = testcase.TestCase.EX_RUN_ERROR
171 assert super(VmReady1, self).run(
172 **kwargs) == testcase.TestCase.EX_OK
173 self.image = self.publish_image()
174 self.flavor = self.create_flavor()
176 status = testcase.TestCase.EX_OK
177 except Exception: # pylint: disable=broad-except
178 self.__logger.exception('Cannot run %s', self.case_name)
181 self.stop_time = time.time()
186 assert self.orig_cloud
188 super(VmReady1, self).clean()
190 self.cloud.delete_image(self.image.id)
192 self.orig_cloud.delete_flavor(self.flavor.id)
193 except Exception: # pylint: disable=broad-except
194 self.__logger.exception("Cannot clean all ressources")
197 class VmReady2(VmReady1):
198 """Deploy a single VM reachable via ssh (scenario2)
200 It creates new user/project before creating and configuring all tenant
201 network ressources, flavors, images, etc. required by advanced testcases.
203 It ensures that all testcases inheriting from SingleVm2 could work
204 without specific configurations (or at least read the same config data).
207 __logger = logging.getLogger(__name__)
209 def __init__(self, **kwargs):
210 if "case_name" not in kwargs:
211 kwargs["case_name"] = 'vmready2'
212 super(VmReady2, self).__init__(**kwargs)
214 assert self.orig_cloud
215 self.project = tenantnetwork.NewProject(
216 self.orig_cloud, self.case_name, self.guid)
217 self.project.create()
218 self.cloud = self.project.cloud
219 except Exception: # pylint: disable=broad-except
220 self.__logger.exception("Cannot create user or project")
226 super(VmReady2, self).clean()
229 except Exception: # pylint: disable=broad-except
230 self.__logger.exception("Cannot clean all ressources")
233 class SingleVm1(VmReady1):
234 """Deploy a single VM reachable via ssh (scenario1)
236 It inherits from TenantNetwork1 which creates all network resources and
237 completes it by booting a VM attached to that network.
239 It ensures that all testcases inheriting from SingleVm1 could work
240 without specific configurations (or at least read the same config data).
242 # pylint: disable=too-many-instance-attributes
244 __logger = logging.getLogger(__name__)
246 ssh_connect_timeout = 60
247 ssh_connect_loops = 6
249 def __init__(self, **kwargs):
250 if "case_name" not in kwargs:
251 kwargs["case_name"] = 'singlevm1'
252 super(SingleVm1, self).__init__(**kwargs)
258 (_, self.key_filename) = tempfile.mkstemp()
261 """Create the security group and the keypair
263 It can be overriden to set other rules according to the services
266 Raises: Exception on error
269 self.keypair = self.cloud.create_keypair(
270 '{}-kp_{}'.format(self.case_name, self.guid))
271 self.__logger.debug("keypair: %s", self.keypair)
272 self.__logger.debug("private_key: %s", self.keypair.private_key)
273 with open(self.key_filename, 'w') as private_key_file:
274 private_key_file.write(self.keypair.private_key)
275 self.sec = self.cloud.create_security_group(
276 '{}-sg_{}'.format(self.case_name, self.guid),
277 'created by OPNFV Functest ({})'.format(self.case_name))
278 self.cloud.create_security_group_rule(
279 self.sec.id, port_range_min='22', port_range_max='22',
280 protocol='tcp', direction='ingress')
281 self.cloud.create_security_group_rule(
282 self.sec.id, protocol='icmp', direction='ingress')
284 def connect(self, vm1):
285 """Connect to a virtual machine via ssh
287 It first adds a floating ip to the virtual machine and then establishes
295 fip = self.cloud.create_floating_ip(
296 network=self.ext_net.id, server=vm1)
297 self.__logger.debug("floating_ip: %s", fip)
298 p_console = self.cloud.get_server_console(vm1)
299 self.__logger.debug("vm console: \n%s", p_console)
300 ssh = paramiko.SSHClient()
301 ssh.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
302 for loop in range(self.ssh_connect_loops):
305 fip.floating_ip_address,
308 '{}_image_user'.format(self.case_name), self.username),
309 key_filename=self.key_filename,
312 '{}_vm_ssh_connect_timeout'.format(self.case_name),
313 self.ssh_connect_timeout))
315 except Exception: # pylint: disable=broad-except
317 "try %s: cannot connect to %s", loop + 1,
318 fip.floating_ip_address)
322 "cannot connect to %s", fip.floating_ip_address)
327 """Say hello world via ssh
329 It can be overriden to execute any command.
331 Returns: echo exit codes
333 (_, stdout, _) = self.ssh.exec_command('echo Hello World')
334 self.__logger.debug("output:\n%s", stdout.read())
335 return stdout.channel.recv_exit_status()
337 def run(self, **kwargs):
340 Here are the main actions:
343 - create the security group
344 - execute the right command over ssh
348 - TestCase.EX_RUN_ERROR on error
350 status = testcase.TestCase.EX_RUN_ERROR
353 assert super(SingleVm1, self).run(
354 **kwargs) == testcase.TestCase.EX_OK
357 self.sshvm = self.boot_vm(
358 key_name=self.keypair.id, security_groups=[self.sec.id])
359 (self.fip, self.ssh) = self.connect(self.sshvm)
360 if not self.execute():
362 status = testcase.TestCase.EX_OK
363 except Exception: # pylint: disable=broad-except
364 self.__logger.exception('Cannot run %s', self.case_name)
366 self.stop_time = time.time()
371 assert self.orig_cloud
374 self.cloud.delete_floating_ip(self.fip.id)
376 self.cloud.delete_server(self.sshvm, wait=True)
378 self.cloud.delete_security_group(self.sec.id)
380 self.cloud.delete_keypair(self.keypair.name)
381 super(SingleVm1, self).clean()
382 except Exception: # pylint: disable=broad-except
383 self.__logger.exception("Cannot clean all ressources")
386 class SingleVm2(SingleVm1):
387 """Deploy a single VM reachable via ssh (scenario2)
389 It creates new user/project before creating and configuring all tenant
390 network ressources and vms required by advanced testcases.
392 It ensures that all testcases inheriting from SingleVm2 could work
393 without specific configurations (or at least read the same config data).
396 __logger = logging.getLogger(__name__)
398 def __init__(self, **kwargs):
399 if "case_name" not in kwargs:
400 kwargs["case_name"] = 'singlevm2'
401 super(SingleVm2, self).__init__(**kwargs)
403 assert self.orig_cloud
404 self.project = tenantnetwork.NewProject(
405 self.orig_cloud, self.case_name, self.guid)
406 self.project.create()
407 self.cloud = self.project.cloud
408 except Exception: # pylint: disable=broad-except
409 self.__logger.exception("Cannot create user or project")
415 super(SingleVm2, self).clean()
418 except Exception: # pylint: disable=broad-except
419 self.__logger.exception("Cannot clean all ressources")