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
46 def __init__(self, **kwargs):
47 if "case_name" not in kwargs:
48 kwargs["case_name"] = 'vmready1'
49 super(VmReady1, self).__init__(**kwargs)
50 self.orig_cloud = self.cloud
54 def publish_image(self, name=None):
57 It allows publishing multiple images for the child testcases. It forces
58 the same configuration for all subtestcases.
62 Raises: expection on error
65 image = self.cloud.create_image(
66 name if name else '{}-img_{}'.format(self.case_name, self.guid),
68 config.CONF, '{}_image'.format(self.case_name),
71 config.CONF, '{}_extra_properties'.format(self.case_name),
72 self.extra_properties),
74 config.CONF, '{}_visibility'.format(self.case_name),
76 self.__logger.debug("image: %s", image)
79 def create_flavor(self, name=None):
82 It allows creating multiple flavors for the child testcases. It forces
83 the same configuration for all subtestcases.
87 Raises: expection on error
89 assert self.orig_cloud
90 flavor = self.orig_cloud.create_flavor(
91 name if name else '{}-flavor_{}'.format(self.case_name, self.guid),
92 getattr(config.CONF, '{}_flavor_ram'.format(self.case_name),
94 getattr(config.CONF, '{}_flavor_vcpus'.format(self.case_name),
96 getattr(config.CONF, '{}_flavor_disk'.format(self.case_name),
98 self.__logger.debug("flavor: %s", flavor)
99 self.orig_cloud.set_flavor_specs(
100 flavor.id, getattr(config.CONF, 'flavor_extra_specs', {}))
103 def boot_vm(self, name=None, **kwargs):
104 """Boot the virtual machine
106 It allows booting multiple machines for the child testcases. It forces
107 the same configuration for all subtestcases.
111 Raises: expection on error
114 vm1 = self.cloud.create_server(
115 name if name else '{}-vm_{}'.format(self.case_name, self.guid),
116 image=self.image.id, flavor=self.flavor.id,
117 auto_ip=False, wait=True,
118 network=self.network.id,
120 vm1 = self.cloud.wait_for_server(vm1, auto_ip=False)
121 self.__logger.debug("vm: %s", vm1)
124 def run(self, **kwargs):
127 Here are the main actions:
133 - TestCase.EX_RUN_ERROR on error
135 status = testcase.TestCase.EX_RUN_ERROR
138 super(VmReady1, self).run(**kwargs)
139 self.image = self.publish_image()
140 self.flavor = self.create_flavor()
142 status = testcase.TestCase.EX_OK
143 except Exception: # pylint: disable=broad-except
144 self.__logger.exception('Cannot run %s', self.case_name)
146 self.stop_time = time.time()
151 assert self.orig_cloud
153 super(VmReady1, self).clean()
154 self.cloud.delete_image(self.image.id)
155 self.orig_cloud.delete_flavor(self.flavor.id)
156 except Exception: # pylint: disable=broad-except
157 self.__logger.exception("Cannot clean all ressources")
160 class VmReady2(VmReady1):
161 """Deploy a single VM reachable via ssh (scenario2)
163 It creates new user/project before creating and configuring all tenant
164 network ressources, flavors, images, etc. required by advanced testcases.
166 It ensures that all testcases inheriting from SingleVm2 could work
167 without specific configurations (or at least read the same config data).
170 __logger = logging.getLogger(__name__)
172 def __init__(self, **kwargs):
173 if "case_name" not in kwargs:
174 kwargs["case_name"] = 'vmready2'
175 super(VmReady2, self).__init__(**kwargs)
177 assert self.orig_cloud
178 self.project = tenantnetwork.NewProject(
179 self.orig_cloud, self.case_name, self.guid)
180 self.project.create()
181 self.cloud = self.project.cloud
182 except Exception: # pylint: disable=broad-except
183 self.__logger.exception("Cannot create user or project")
189 super(VmReady2, self).clean()
192 except Exception: # pylint: disable=broad-except
193 self.__logger.exception("Cannot clean all ressources")
196 class SingleVm1(VmReady1):
197 """Deploy a single VM reachable via ssh (scenario1)
199 It inherits from TenantNetwork1 which creates all network resources and
200 completes it by booting a VM attached to that network.
202 It ensures that all testcases inheriting from SingleVm1 could work
203 without specific configurations (or at least read the same config data).
205 # pylint: disable=too-many-instance-attributes
207 __logger = logging.getLogger(__name__)
209 ssh_connect_timeout = 60
211 def __init__(self, **kwargs):
212 if "case_name" not in kwargs:
213 kwargs["case_name"] = 'singlevm1'
214 super(SingleVm1, self).__init__(**kwargs)
220 (_, self.key_filename) = tempfile.mkstemp()
223 """Create the security group and the keypair
225 It can be overriden to set other rules according to the services
228 Raises: Exception on error
231 self.keypair = self.cloud.create_keypair(
232 '{}-kp_{}'.format(self.case_name, self.guid))
233 self.__logger.debug("keypair: %s", self.keypair)
234 self.__logger.debug("private_key: %s", self.keypair.private_key)
235 with open(self.key_filename, 'w') as private_key_file:
236 private_key_file.write(self.keypair.private_key)
237 self.sec = self.cloud.create_security_group(
238 '{}-sg_{}'.format(self.case_name, self.guid),
239 'created by OPNFV Functest ({})'.format(self.case_name))
240 self.cloud.create_security_group_rule(
241 self.sec.id, port_range_min='22', port_range_max='22',
242 protocol='tcp', direction='ingress')
243 self.cloud.create_security_group_rule(
244 self.sec.id, protocol='icmp', direction='ingress')
246 def connect(self, vm1):
247 """Connect to a virtual machine via ssh
249 It first adds a floating ip to the virtual machine and then establishes
257 fip = self.cloud.create_floating_ip(
258 network=self.ext_net.id, server=vm1)
259 self.__logger.debug("floating_ip: %s", fip)
260 p_console = self.cloud.get_server_console(vm1)
261 self.__logger.debug("vm console: \n%s", p_console)
262 ssh = paramiko.SSHClient()
263 ssh.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
264 for loop in range(6):
267 fip.floating_ip_address,
270 '{}_image_user'.format(self.case_name), self.username),
271 key_filename=self.key_filename,
274 '{}_vm_ssh_connect_timeout'.format(self.case_name),
275 self.ssh_connect_timeout))
277 except Exception: # pylint: disable=broad-except
279 "try %s: cannot connect to %s", loop + 1,
280 fip.floating_ip_address)
284 "cannot connect to %s", fip.floating_ip_address)
289 """Say hello world via ssh
291 It can be overriden to execute any command.
293 Returns: echo exit codes
295 (_, stdout, _) = self.ssh.exec_command('echo Hello World')
296 self.__logger.debug("output:\n%s", stdout.read())
297 return stdout.channel.recv_exit_status()
299 def run(self, **kwargs):
302 Here are the main actions:
305 - create the security group
306 - execute the right command over ssh
310 - TestCase.EX_RUN_ERROR on error
312 status = testcase.TestCase.EX_RUN_ERROR
315 super(SingleVm1, self).run(**kwargs)
318 self.sshvm = self.boot_vm(
319 key_name=self.keypair.id, security_groups=[self.sec.id])
320 (self.fip, self.ssh) = self.connect(self.sshvm)
321 if not self.execute():
323 status = testcase.TestCase.EX_OK
324 except Exception: # pylint: disable=broad-except
325 self.__logger.exception('Cannot run %s', self.case_name)
327 self.stop_time = time.time()
332 assert self.orig_cloud
334 self.cloud.delete_floating_ip(self.fip.id)
335 self.cloud.delete_server(self.sshvm, wait=True)
336 self.cloud.delete_security_group(self.sec.id)
337 self.cloud.delete_keypair(self.keypair.id)
338 super(SingleVm1, self).clean()
339 except Exception: # pylint: disable=broad-except
340 self.__logger.exception("Cannot clean all ressources")
343 class SingleVm2(SingleVm1):
344 """Deploy a single VM reachable via ssh (scenario2)
346 It creates new user/project before creating and configuring all tenant
347 network ressources and vms required by advanced testcases.
349 It ensures that all testcases inheriting from SingleVm2 could work
350 without specific configurations (or at least read the same config data).
353 __logger = logging.getLogger(__name__)
355 def __init__(self, **kwargs):
356 if "case_name" not in kwargs:
357 kwargs["case_name"] = 'singlevm2'
358 super(SingleVm2, self).__init__(**kwargs)
360 assert self.orig_cloud
361 self.project = tenantnetwork.NewProject(
362 self.orig_cloud, self.case_name, self.guid)
363 self.project.create()
364 self.cloud = self.project.cloud
365 except Exception: # pylint: disable=broad-except
366 self.__logger.exception("Cannot create user or project")
372 super(SingleVm2, self).clean()
375 except Exception: # pylint: disable=broad-except
376 self.__logger.exception("Cannot clean all ressources")