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 super(VmReady1, self).run(**kwargs)
172 self.image = self.publish_image()
173 self.flavor = self.create_flavor()
175 status = testcase.TestCase.EX_OK
176 except Exception: # pylint: disable=broad-except
177 self.__logger.exception('Cannot run %s', self.case_name)
179 self.stop_time = time.time()
184 assert self.orig_cloud
186 super(VmReady1, self).clean()
187 self.cloud.delete_image(self.image.id)
188 self.orig_cloud.delete_flavor(self.flavor.id)
189 except Exception: # pylint: disable=broad-except
190 self.__logger.exception("Cannot clean all ressources")
193 class VmReady2(VmReady1):
194 """Deploy a single VM reachable via ssh (scenario2)
196 It creates new user/project before creating and configuring all tenant
197 network ressources, flavors, images, etc. required by advanced testcases.
199 It ensures that all testcases inheriting from SingleVm2 could work
200 without specific configurations (or at least read the same config data).
203 __logger = logging.getLogger(__name__)
205 def __init__(self, **kwargs):
206 if "case_name" not in kwargs:
207 kwargs["case_name"] = 'vmready2'
208 super(VmReady2, self).__init__(**kwargs)
210 assert self.orig_cloud
211 self.project = tenantnetwork.NewProject(
212 self.orig_cloud, self.case_name, self.guid)
213 self.project.create()
214 self.cloud = self.project.cloud
215 except Exception: # pylint: disable=broad-except
216 self.__logger.exception("Cannot create user or project")
222 super(VmReady2, self).clean()
225 except Exception: # pylint: disable=broad-except
226 self.__logger.exception("Cannot clean all ressources")
229 class SingleVm1(VmReady1):
230 """Deploy a single VM reachable via ssh (scenario1)
232 It inherits from TenantNetwork1 which creates all network resources and
233 completes it by booting a VM attached to that network.
235 It ensures that all testcases inheriting from SingleVm1 could work
236 without specific configurations (or at least read the same config data).
238 # pylint: disable=too-many-instance-attributes
240 __logger = logging.getLogger(__name__)
242 ssh_connect_timeout = 60
244 def __init__(self, **kwargs):
245 if "case_name" not in kwargs:
246 kwargs["case_name"] = 'singlevm1'
247 super(SingleVm1, self).__init__(**kwargs)
253 (_, self.key_filename) = tempfile.mkstemp()
256 """Create the security group and the keypair
258 It can be overriden to set other rules according to the services
261 Raises: Exception on error
264 self.keypair = self.cloud.create_keypair(
265 '{}-kp_{}'.format(self.case_name, self.guid))
266 self.__logger.debug("keypair: %s", self.keypair)
267 self.__logger.debug("private_key: %s", self.keypair.private_key)
268 with open(self.key_filename, 'w') as private_key_file:
269 private_key_file.write(self.keypair.private_key)
270 self.sec = self.cloud.create_security_group(
271 '{}-sg_{}'.format(self.case_name, self.guid),
272 'created by OPNFV Functest ({})'.format(self.case_name))
273 self.cloud.create_security_group_rule(
274 self.sec.id, port_range_min='22', port_range_max='22',
275 protocol='tcp', direction='ingress')
276 self.cloud.create_security_group_rule(
277 self.sec.id, protocol='icmp', direction='ingress')
279 def connect(self, vm1):
280 """Connect to a virtual machine via ssh
282 It first adds a floating ip to the virtual machine and then establishes
290 fip = self.cloud.create_floating_ip(
291 network=self.ext_net.id, server=vm1)
292 self.__logger.debug("floating_ip: %s", fip)
293 p_console = self.cloud.get_server_console(vm1)
294 self.__logger.debug("vm console: \n%s", p_console)
295 ssh = paramiko.SSHClient()
296 ssh.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
297 for loop in range(6):
300 fip.floating_ip_address,
303 '{}_image_user'.format(self.case_name), self.username),
304 key_filename=self.key_filename,
307 '{}_vm_ssh_connect_timeout'.format(self.case_name),
308 self.ssh_connect_timeout))
310 except Exception: # pylint: disable=broad-except
312 "try %s: cannot connect to %s", loop + 1,
313 fip.floating_ip_address)
317 "cannot connect to %s", fip.floating_ip_address)
322 """Say hello world via ssh
324 It can be overriden to execute any command.
326 Returns: echo exit codes
328 (_, stdout, _) = self.ssh.exec_command('echo Hello World')
329 self.__logger.debug("output:\n%s", stdout.read())
330 return stdout.channel.recv_exit_status()
332 def run(self, **kwargs):
335 Here are the main actions:
338 - create the security group
339 - execute the right command over ssh
343 - TestCase.EX_RUN_ERROR on error
345 status = testcase.TestCase.EX_RUN_ERROR
348 super(SingleVm1, self).run(**kwargs)
351 self.sshvm = self.boot_vm(
352 key_name=self.keypair.id, security_groups=[self.sec.id])
353 (self.fip, self.ssh) = self.connect(self.sshvm)
354 if not self.execute():
356 status = testcase.TestCase.EX_OK
357 except Exception: # pylint: disable=broad-except
358 self.__logger.exception('Cannot run %s', self.case_name)
360 self.stop_time = time.time()
365 assert self.orig_cloud
367 self.cloud.delete_floating_ip(self.fip.id)
368 self.cloud.delete_server(self.sshvm, wait=True)
369 self.cloud.delete_security_group(self.sec.id)
370 self.cloud.delete_keypair(self.keypair.id)
371 super(SingleVm1, self).clean()
372 except Exception: # pylint: disable=broad-except
373 self.__logger.exception("Cannot clean all ressources")
376 class SingleVm2(SingleVm1):
377 """Deploy a single VM reachable via ssh (scenario2)
379 It creates new user/project before creating and configuring all tenant
380 network ressources and vms required by advanced testcases.
382 It ensures that all testcases inheriting from SingleVm2 could work
383 without specific configurations (or at least read the same config data).
386 __logger = logging.getLogger(__name__)
388 def __init__(self, **kwargs):
389 if "case_name" not in kwargs:
390 kwargs["case_name"] = 'singlevm2'
391 super(SingleVm2, self).__init__(**kwargs)
393 assert self.orig_cloud
394 self.project = tenantnetwork.NewProject(
395 self.orig_cloud, self.case_name, self.guid)
396 self.project.create()
397 self.cloud = self.project.cloud
398 except Exception: # pylint: disable=broad-except
399 self.__logger.exception("Cannot create user or project")
405 super(SingleVm2, self).clean()
408 except Exception: # pylint: disable=broad-except
409 self.__logger.exception("Cannot clean all ressources")