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()
189 self.cloud.delete_image(self.image.id)
190 self.orig_cloud.delete_flavor(self.flavor.id)
191 except Exception: # pylint: disable=broad-except
192 self.__logger.exception("Cannot clean all ressources")
195 class VmReady2(VmReady1):
196 """Deploy a single VM reachable via ssh (scenario2)
198 It creates new user/project before creating and configuring all tenant
199 network ressources, flavors, images, etc. required by advanced testcases.
201 It ensures that all testcases inheriting from SingleVm2 could work
202 without specific configurations (or at least read the same config data).
205 __logger = logging.getLogger(__name__)
207 def __init__(self, **kwargs):
208 if "case_name" not in kwargs:
209 kwargs["case_name"] = 'vmready2'
210 super(VmReady2, self).__init__(**kwargs)
212 assert self.orig_cloud
213 self.project = tenantnetwork.NewProject(
214 self.orig_cloud, self.case_name, self.guid)
215 self.project.create()
216 self.cloud = self.project.cloud
217 except Exception: # pylint: disable=broad-except
218 self.__logger.exception("Cannot create user or project")
224 super(VmReady2, self).clean()
227 except Exception: # pylint: disable=broad-except
228 self.__logger.exception("Cannot clean all ressources")
231 class SingleVm1(VmReady1):
232 """Deploy a single VM reachable via ssh (scenario1)
234 It inherits from TenantNetwork1 which creates all network resources and
235 completes it by booting a VM attached to that network.
237 It ensures that all testcases inheriting from SingleVm1 could work
238 without specific configurations (or at least read the same config data).
240 # pylint: disable=too-many-instance-attributes
242 __logger = logging.getLogger(__name__)
244 ssh_connect_timeout = 60
245 ssh_connect_loops = 6
247 def __init__(self, **kwargs):
248 if "case_name" not in kwargs:
249 kwargs["case_name"] = 'singlevm1'
250 super(SingleVm1, self).__init__(**kwargs)
256 (_, self.key_filename) = tempfile.mkstemp()
259 """Create the security group and the keypair
261 It can be overriden to set other rules according to the services
264 Raises: Exception on error
267 self.keypair = self.cloud.create_keypair(
268 '{}-kp_{}'.format(self.case_name, self.guid))
269 self.__logger.debug("keypair: %s", self.keypair)
270 self.__logger.debug("private_key: %s", self.keypair.private_key)
271 with open(self.key_filename, 'w') as private_key_file:
272 private_key_file.write(self.keypair.private_key)
273 self.sec = self.cloud.create_security_group(
274 '{}-sg_{}'.format(self.case_name, self.guid),
275 'created by OPNFV Functest ({})'.format(self.case_name))
276 self.cloud.create_security_group_rule(
277 self.sec.id, port_range_min='22', port_range_max='22',
278 protocol='tcp', direction='ingress')
279 self.cloud.create_security_group_rule(
280 self.sec.id, protocol='icmp', direction='ingress')
282 def connect(self, vm1):
283 """Connect to a virtual machine via ssh
285 It first adds a floating ip to the virtual machine and then establishes
293 fip = self.cloud.create_floating_ip(
294 network=self.ext_net.id, server=vm1)
295 self.__logger.debug("floating_ip: %s", fip)
296 p_console = self.cloud.get_server_console(vm1)
297 self.__logger.debug("vm console: \n%s", p_console)
298 ssh = paramiko.SSHClient()
299 ssh.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
300 for loop in range(self.ssh_connect_loops):
303 fip.floating_ip_address,
306 '{}_image_user'.format(self.case_name), self.username),
307 key_filename=self.key_filename,
310 '{}_vm_ssh_connect_timeout'.format(self.case_name),
311 self.ssh_connect_timeout))
313 except Exception: # pylint: disable=broad-except
315 "try %s: cannot connect to %s", loop + 1,
316 fip.floating_ip_address)
320 "cannot connect to %s", fip.floating_ip_address)
325 """Say hello world via ssh
327 It can be overriden to execute any command.
329 Returns: echo exit codes
331 (_, stdout, _) = self.ssh.exec_command('echo Hello World')
332 self.__logger.debug("output:\n%s", stdout.read())
333 return stdout.channel.recv_exit_status()
335 def run(self, **kwargs):
338 Here are the main actions:
341 - create the security group
342 - execute the right command over ssh
346 - TestCase.EX_RUN_ERROR on error
348 status = testcase.TestCase.EX_RUN_ERROR
351 assert super(SingleVm1, self).run(
352 **kwargs) == testcase.TestCase.EX_OK
355 self.sshvm = self.boot_vm(
356 key_name=self.keypair.id, security_groups=[self.sec.id])
357 (self.fip, self.ssh) = self.connect(self.sshvm)
358 if not self.execute():
360 status = testcase.TestCase.EX_OK
361 except Exception: # pylint: disable=broad-except
362 self.__logger.exception('Cannot run %s', self.case_name)
364 self.stop_time = time.time()
369 assert self.orig_cloud
371 self.cloud.delete_floating_ip(self.fip.id)
372 self.cloud.delete_server(self.sshvm, wait=True)
373 self.cloud.delete_security_group(self.sec.id)
374 self.cloud.delete_keypair(self.keypair.name)
375 super(SingleVm1, self).clean()
376 except Exception: # pylint: disable=broad-except
377 self.__logger.exception("Cannot clean all ressources")
380 class SingleVm2(SingleVm1):
381 """Deploy a single VM reachable via ssh (scenario2)
383 It creates new user/project before creating and configuring all tenant
384 network ressources and vms required by advanced testcases.
386 It ensures that all testcases inheriting from SingleVm2 could work
387 without specific configurations (or at least read the same config data).
390 __logger = logging.getLogger(__name__)
392 def __init__(self, **kwargs):
393 if "case_name" not in kwargs:
394 kwargs["case_name"] = 'singlevm2'
395 super(SingleVm2, self).__init__(**kwargs)
397 assert self.orig_cloud
398 self.project = tenantnetwork.NewProject(
399 self.orig_cloud, self.case_name, self.guid)
400 self.project.create()
401 self.cloud = self.project.cloud
402 except Exception: # pylint: disable=broad-except
403 self.__logger.exception("Cannot create user or project")
409 super(SingleVm2, self).clean()
412 except Exception: # pylint: disable=broad-except
413 self.__logger.exception("Cannot clean all ressources")