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 image_format = 'qcow2'
42 image_alt_format = image_format
43 visibility = 'private'
44 extra_properties = None
52 def __init__(self, **kwargs):
53 if "case_name" not in kwargs:
54 kwargs["case_name"] = 'vmready1'
55 super(VmReady1, self).__init__(**kwargs)
56 self.orig_cloud = self.cloud
60 def publish_image(self, name=None):
63 It allows publishing multiple images for the child testcases. It forces
64 the same configuration for all subtestcases.
68 Raises: expection on error
71 image = self.cloud.create_image(
72 name if name else '{}-img_{}'.format(self.case_name, self.guid),
74 config.CONF, '{}_image'.format(self.case_name),
77 config.CONF, '{}_extra_properties'.format(self.case_name),
78 self.extra_properties),
80 config.CONF, '{}_image_format'.format(self.case_name),
83 config.CONF, '{}_visibility'.format(self.case_name),
85 self.__logger.debug("image: %s", image)
88 def publish_image_alt(self, name=None):
89 """Publish alternative image
91 It allows publishing multiple images for the child testcases. It forces
92 the same configuration for all subtestcases.
96 Raises: expection on error
99 image = self.cloud.create_image(
100 name if name else '{}-img_alt_{}'.format(
101 self.case_name, self.guid),
103 config.CONF, '{}_image_alt'.format(self.case_name),
106 config.CONF, '{}_extra_properties'.format(self.case_name),
107 self.extra_properties),
109 config.CONF, '{}_image_alt_format'.format(self.case_name),
112 config.CONF, '{}_visibility'.format(self.case_name),
114 self.__logger.debug("image: %s", image)
117 def create_flavor(self, name=None):
120 It allows creating multiple flavors for the child testcases. It forces
121 the same configuration for all subtestcases.
125 Raises: expection on error
127 assert self.orig_cloud
128 flavor = self.orig_cloud.create_flavor(
129 name if name else '{}-flavor_{}'.format(self.case_name, self.guid),
130 getattr(config.CONF, '{}_flavor_ram'.format(self.case_name),
132 getattr(config.CONF, '{}_flavor_vcpus'.format(self.case_name),
134 getattr(config.CONF, '{}_flavor_disk'.format(self.case_name),
136 self.__logger.debug("flavor: %s", flavor)
137 self.orig_cloud.set_flavor_specs(
138 flavor.id, getattr(config.CONF, 'flavor_extra_specs', {}))
141 def create_flavor_alt(self, name=None):
144 It allows creating multiple alt flavors for the child testcases. It
145 forces the same configuration for all subtestcases.
149 Raises: expection on error
151 assert self.orig_cloud
152 flavor = self.orig_cloud.create_flavor(
153 name if name else '{}-flavor_alt_{}'.format(
154 self.case_name, self.guid),
155 getattr(config.CONF, '{}_flavor_alt_ram'.format(self.case_name),
156 self.flavor_alt_ram),
157 getattr(config.CONF, '{}_flavor_alt_vcpus'.format(self.case_name),
158 self.flavor_alt_vcpus),
159 getattr(config.CONF, '{}_flavor_alt_disk'.format(self.case_name),
160 self.flavor_alt_disk))
161 self.__logger.debug("flavor: %s", flavor)
162 self.orig_cloud.set_flavor_specs(
163 flavor.id, getattr(config.CONF, 'flavor_extra_specs', {}))
166 def boot_vm(self, name=None, **kwargs):
167 """Boot the virtual machine
169 It allows booting multiple machines for the child testcases. It forces
170 the same configuration for all subtestcases.
174 Raises: expection on error
177 vm1 = self.cloud.create_server(
178 name if name else '{}-vm_{}'.format(self.case_name, self.guid),
179 image=self.image.id, flavor=self.flavor.id,
180 auto_ip=False, wait=True,
181 network=self.network.id,
183 vm1 = self.cloud.wait_for_server(vm1, auto_ip=False)
184 self.__logger.debug("vm: %s", vm1)
187 def run(self, **kwargs):
190 Here are the main actions:
196 - TestCase.EX_RUN_ERROR on error
198 status = testcase.TestCase.EX_RUN_ERROR
201 assert super(VmReady1, self).run(
202 **kwargs) == testcase.TestCase.EX_OK
203 self.image = self.publish_image()
204 self.flavor = self.create_flavor()
206 status = testcase.TestCase.EX_OK
207 except Exception: # pylint: disable=broad-except
208 self.__logger.exception('Cannot run %s', self.case_name)
211 self.stop_time = time.time()
216 assert self.orig_cloud
218 super(VmReady1, self).clean()
220 self.cloud.delete_image(self.image.id)
222 self.orig_cloud.delete_flavor(self.flavor.id)
223 except Exception: # pylint: disable=broad-except
224 self.__logger.exception("Cannot clean all ressources")
227 class VmReady2(VmReady1):
228 """Deploy a single VM reachable via ssh (scenario2)
230 It creates new user/project before creating and configuring all tenant
231 network ressources, flavors, images, etc. required by advanced testcases.
233 It ensures that all testcases inheriting from SingleVm2 could work
234 without specific configurations (or at least read the same config data).
237 __logger = logging.getLogger(__name__)
239 def __init__(self, **kwargs):
240 if "case_name" not in kwargs:
241 kwargs["case_name"] = 'vmready2'
242 super(VmReady2, self).__init__(**kwargs)
244 assert self.orig_cloud
245 self.project = tenantnetwork.NewProject(
246 self.orig_cloud, self.case_name, self.guid)
247 self.project.create()
248 self.cloud = self.project.cloud
249 except Exception: # pylint: disable=broad-except
250 self.__logger.exception("Cannot create user or project")
256 super(VmReady2, self).clean()
259 except Exception: # pylint: disable=broad-except
260 self.__logger.exception("Cannot clean all ressources")
263 class SingleVm1(VmReady1):
264 """Deploy a single VM reachable via ssh (scenario1)
266 It inherits from TenantNetwork1 which creates all network resources and
267 completes it by booting a VM attached to that network.
269 It ensures that all testcases inheriting from SingleVm1 could work
270 without specific configurations (or at least read the same config data).
272 # pylint: disable=too-many-instance-attributes
274 __logger = logging.getLogger(__name__)
276 ssh_connect_timeout = 60
277 ssh_connect_loops = 6
279 def __init__(self, **kwargs):
280 if "case_name" not in kwargs:
281 kwargs["case_name"] = 'singlevm1'
282 super(SingleVm1, self).__init__(**kwargs)
288 (_, self.key_filename) = tempfile.mkstemp()
291 """Create the security group and the keypair
293 It can be overriden to set other rules according to the services
296 Raises: Exception on error
299 self.keypair = self.cloud.create_keypair(
300 '{}-kp_{}'.format(self.case_name, self.guid))
301 self.__logger.debug("keypair: %s", self.keypair)
302 self.__logger.debug("private_key: %s", self.keypair.private_key)
303 with open(self.key_filename, 'w') as private_key_file:
304 private_key_file.write(self.keypair.private_key)
305 self.sec = self.cloud.create_security_group(
306 '{}-sg_{}'.format(self.case_name, self.guid),
307 'created by OPNFV Functest ({})'.format(self.case_name))
308 self.cloud.create_security_group_rule(
309 self.sec.id, port_range_min='22', port_range_max='22',
310 protocol='tcp', direction='ingress')
311 self.cloud.create_security_group_rule(
312 self.sec.id, protocol='icmp', direction='ingress')
314 def connect(self, vm1):
315 """Connect to a virtual machine via ssh
317 It first adds a floating ip to the virtual machine and then establishes
325 fip = self.cloud.create_floating_ip(
326 network=self.ext_net.id, server=vm1)
327 self.__logger.debug("floating_ip: %s", fip)
328 p_console = self.cloud.get_server_console(vm1)
329 self.__logger.debug("vm console: \n%s", p_console)
330 ssh = paramiko.SSHClient()
331 ssh.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
332 for loop in range(self.ssh_connect_loops):
335 fip.floating_ip_address,
338 '{}_image_user'.format(self.case_name), self.username),
339 key_filename=self.key_filename,
342 '{}_vm_ssh_connect_timeout'.format(self.case_name),
343 self.ssh_connect_timeout))
345 except Exception: # pylint: disable=broad-except
347 "try %s: cannot connect to %s", loop + 1,
348 fip.floating_ip_address)
352 "cannot connect to %s", fip.floating_ip_address)
357 """Say hello world via ssh
359 It can be overriden to execute any command.
361 Returns: echo exit codes
363 (_, stdout, _) = self.ssh.exec_command('echo Hello World')
364 self.__logger.debug("output:\n%s", stdout.read())
365 return stdout.channel.recv_exit_status()
367 def run(self, **kwargs):
370 Here are the main actions:
373 - create the security group
374 - execute the right command over ssh
378 - TestCase.EX_RUN_ERROR on error
380 status = testcase.TestCase.EX_RUN_ERROR
383 assert super(SingleVm1, self).run(
384 **kwargs) == testcase.TestCase.EX_OK
387 self.sshvm = self.boot_vm(
388 key_name=self.keypair.id, security_groups=[self.sec.id])
389 (self.fip, self.ssh) = self.connect(self.sshvm)
390 if not self.execute():
392 status = testcase.TestCase.EX_OK
393 except Exception: # pylint: disable=broad-except
394 self.__logger.exception('Cannot run %s', self.case_name)
396 self.stop_time = time.time()
401 assert self.orig_cloud
404 self.cloud.delete_floating_ip(self.fip.id)
406 self.cloud.delete_server(self.sshvm, wait=True)
408 self.cloud.delete_security_group(self.sec.id)
410 self.cloud.delete_keypair(self.keypair.name)
411 super(SingleVm1, self).clean()
412 except Exception: # pylint: disable=broad-except
413 self.__logger.exception("Cannot clean all ressources")
416 class SingleVm2(SingleVm1):
417 """Deploy a single VM reachable via ssh (scenario2)
419 It creates new user/project before creating and configuring all tenant
420 network ressources and vms required by advanced testcases.
422 It ensures that all testcases inheriting from SingleVm2 could work
423 without specific configurations (or at least read the same config data).
426 __logger = logging.getLogger(__name__)
428 def __init__(self, **kwargs):
429 if "case_name" not in kwargs:
430 kwargs["case_name"] = 'singlevm2'
431 super(SingleVm2, self).__init__(**kwargs)
433 assert self.orig_cloud
434 self.project = tenantnetwork.NewProject(
435 self.orig_cloud, self.case_name, self.guid)
436 self.project.create()
437 self.cloud = self.project.cloud
438 except Exception: # pylint: disable=broad-except
439 self.__logger.exception("Cannot create user or project")
445 super(SingleVm2, self).clean()
448 except Exception: # pylint: disable=broad-except
449 self.__logger.exception("Cannot clean all ressources")