331ada4841cddb42de199af8e83c3a965e3e5249
[functest.git] / functest / core / singlevm.py
1 #!/usr/bin/env python
2
3 # Copyright (c) 2018 Orange and others.
4 #
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
9
10 """Ease deploying a single VM reachable via ssh
11
12 It offers a simple way to create all tenant network ressources + a VM for
13 advanced testcases (e.g. deploying an orchestrator).
14 """
15
16 import logging
17 import tempfile
18 import time
19
20 import paramiko
21 from xtesting.core import testcase
22
23 from functest.core import tenantnetwork
24 from functest.utils import config
25
26
27 class VmReady1(tenantnetwork.TenantNetwork1):
28     """Deploy a single VM reachable via ssh (scenario1)
29
30     It inherits from TenantNetwork1 which creates all network resources and
31     prepares a future VM attached to that network.
32
33     It ensures that all testcases inheriting from SingleVm1 could work
34     without specific configurations (or at least read the same config data).
35     """
36     # pylint: disable=too-many-instance-attributes
37
38     __logger = logging.getLogger(__name__)
39     filename = '/home/opnfv/functest/images/cirros-0.4.0-x86_64-disk.img'
40     flavor_ram = 1024
41     flavor_vcpus = 1
42     flavor_disk = 1
43
44     def __init__(self, **kwargs):
45         if "case_name" not in kwargs:
46             kwargs["case_name"] = 'vmready1'
47         super(VmReady1, self).__init__(**kwargs)
48         self.orig_cloud = self.cloud
49         self.image = None
50         self.flavor = None
51
52     def _publish_image(self):
53         assert self.cloud
54         meta = getattr(
55             config.CONF, '{}_extra_properties'.format(self.case_name), None)
56         self.image = self.cloud.create_image(
57             '{}-img_{}'.format(self.case_name, self.guid),
58             filename=getattr(
59                 config.CONF, '{}_image'.format(self.case_name),
60                 self.filename),
61             meta=meta)
62         self.__logger.debug("image: %s", self.image)
63
64     def _create_flavor(self):
65         assert self.orig_cloud
66         self.flavor = self.orig_cloud.create_flavor(
67             '{}-flavor_{}'.format(self.case_name, self.guid),
68             getattr(config.CONF, '{}_flavor_ram'.format(self.case_name),
69                     self.flavor_ram),
70             getattr(config.CONF, '{}_flavor_vcpus'.format(self.case_name),
71                     self.flavor_vcpus),
72             getattr(config.CONF, '{}_flavor_disk'.format(self.case_name),
73                     self.flavor_disk))
74         self.__logger.debug("flavor: %s", self.flavor)
75         self.orig_cloud.set_flavor_specs(
76             self.flavor.id, getattr(config.CONF, 'flavor_extra_specs', {}))
77
78     def run(self, **kwargs):
79         """Boot the new VM
80
81         Here are the main actions:
82         - publish the image
83         - create the flavor
84
85         Returns:
86         - TestCase.EX_OK
87         - TestCase.EX_RUN_ERROR on error
88         """
89         status = testcase.TestCase.EX_RUN_ERROR
90         try:
91             assert self.cloud
92             super(VmReady1, self).run(**kwargs)
93             self._publish_image()
94             self._create_flavor()
95             self.result = 100
96             status = testcase.TestCase.EX_OK
97         except Exception:  # pylint: disable=broad-except
98             self.__logger.exception('Cannot run %s', self.case_name)
99         finally:
100             self.stop_time = time.time()
101         return status
102
103     def clean(self):
104         try:
105             assert self.orig_cloud
106             assert self.cloud
107             self.cloud.delete_image(self.image)
108             self.orig_cloud.delete_flavor(self.flavor.id)
109             self.cloud.delete_image(self.image)
110         except Exception:  # pylint: disable=broad-except
111             pass
112
113
114 class VmReady2(VmReady1):
115     """Deploy a single VM reachable via ssh (scenario2)
116
117     It creates new user/project before creating and configuring all tenant
118     network ressources, flavors, images, etc. required by advanced testcases.
119
120     It ensures that all testcases inheriting from SingleVm2 could work
121     without specific configurations (or at least read the same config data).
122     """
123
124     __logger = logging.getLogger(__name__)
125
126     def __init__(self, **kwargs):
127         if "case_name" not in kwargs:
128             kwargs["case_name"] = 'vmready2'
129         super(VmReady2, self).__init__(**kwargs)
130         try:
131             assert self.orig_cloud
132             self.project = tenantnetwork.NewProject(
133                 self.orig_cloud, self.case_name, self.guid)
134             self.project.create()
135             self.cloud = self.project.cloud
136         except Exception:  # pylint: disable=broad-except
137             self.__logger.exception("Cannot create user or project")
138             self.cloud = None
139             self.project = None
140
141     def clean(self):
142         try:
143             super(VmReady2, self).clean()
144             assert self.project
145             self.project.clean()
146         except Exception:  # pylint: disable=broad-except
147             self.__logger.exception("cannot clean all ressources")
148
149
150 class SingleVm1(VmReady1):
151     """Deploy a single VM reachable via ssh (scenario1)
152
153     It inherits from TenantNetwork1 which creates all network resources and
154     completes it by booting a VM attached to that network.
155
156     It ensures that all testcases inheriting from SingleVm1 could work
157     without specific configurations (or at least read the same config data).
158     """
159     # pylint: disable=too-many-instance-attributes
160
161     __logger = logging.getLogger(__name__)
162     username = 'cirros'
163     ssh_connect_timeout = 60
164
165     def __init__(self, **kwargs):
166         if "case_name" not in kwargs:
167             kwargs["case_name"] = 'singlevm1'
168         super(SingleVm1, self).__init__(**kwargs)
169         self.sshvm = None
170         self.sec = None
171         self.fip = None
172         self.keypair = None
173         self.ssh = paramiko.SSHClient()
174         (_, self.key_filename) = tempfile.mkstemp()
175
176     def create_sg_rules(self):
177         """Create the security group
178
179         It can be overriden to set other rules according to the services
180         running in the VM
181
182         Raises: Exception on error
183         """
184         assert self.cloud
185         self.sec = self.cloud.create_security_group(
186             '{}-sg_{}'.format(self.case_name, self.guid),
187             'created by OPNFV Functest ({})'.format(self.case_name))
188         self.cloud.create_security_group_rule(
189             self.sec.id, port_range_min='22', port_range_max='22',
190             protocol='tcp', direction='ingress')
191         self.cloud.create_security_group_rule(
192             self.sec.id, protocol='icmp', direction='ingress')
193
194     def _boot_vm(self):
195         assert self.cloud
196         self.keypair = self.cloud.create_keypair(
197             '{}-kp_{}'.format(self.case_name, self.guid))
198         self.__logger.debug("keypair: %s", self.keypair)
199         self.__logger.debug("private_key: %s", self.keypair.private_key)
200         with open(self.key_filename, 'w') as private_key_file:
201             private_key_file.write(self.keypair.private_key)
202
203         self.sshvm = self.cloud.create_server(
204             '{}-vm_{}'.format(self.case_name, self.guid),
205             image=self.image.id, flavor=self.flavor.id,
206             key_name=self.keypair.id,
207             auto_ip=False, wait=True,
208             network=self.network.id,
209             security_groups=[self.sec.id])
210         self.__logger.debug("vm: %s", self.sshvm)
211         self.fip = self.cloud.create_floating_ip(
212             network=self.ext_net.id, server=self.sshvm)
213         self.__logger.debug("floating_ip: %s", self.fip)
214         self.sshvm = self.cloud.wait_for_server(self.sshvm, auto_ip=False)
215
216     def _connect(self):
217         assert self.cloud
218         p_console = self.cloud.get_server_console(self.sshvm.id)
219         self.__logger.debug("vm console: \n%s", p_console)
220         self.ssh.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
221         for loop in range(6):
222             try:
223                 self.ssh.connect(
224                     self.sshvm.public_v4,
225                     username=getattr(
226                         config.CONF,
227                         '{}_image_user'.format(self.case_name), self.username),
228                     key_filename=self.key_filename,
229                     timeout=getattr(
230                         config.CONF,
231                         '{}_vm_ssh_connect_timeout'.format(self.case_name),
232                         self.ssh_connect_timeout))
233                 break
234             except Exception:  # pylint: disable=broad-except
235                 self.__logger.info(
236                     "try %s: cannot connect to %s", loop + 1,
237                     self.sshvm.public_v4)
238                 time.sleep(10)
239         else:
240             self.__logger.error("cannot connect to %s", self.sshvm.public_v4)
241             return False
242         return True
243
244     def execute(self):
245         """Say hello world via ssh
246
247         It can be overriden to execute any command.
248
249         Returns: echo exit codes
250         """
251         (_, stdout, _) = self.ssh.exec_command('echo Hello World')
252         self.__logger.info("output:\n%s", stdout.read())
253         return stdout.channel.recv_exit_status()
254
255     def run(self, **kwargs):
256         """Boot the new VM
257
258         Here are the main actions:
259         - add a new ssh key
260         - boot the VM
261         - create the security group
262         - execute the right command over ssh
263
264         Returns:
265         - TestCase.EX_OK
266         - TestCase.EX_RUN_ERROR on error
267         """
268         status = testcase.TestCase.EX_RUN_ERROR
269         try:
270             assert self.cloud
271             super(SingleVm1, self).run(**kwargs)
272             self.result = 0
273             self.create_sg_rules()
274             self._boot_vm()
275             assert self._connect()
276             if not self.execute():
277                 self.result = 100
278                 status = testcase.TestCase.EX_OK
279         except Exception:  # pylint: disable=broad-except
280             self.__logger.exception('Cannot run %s', self.case_name)
281         finally:
282             self.stop_time = time.time()
283         return status
284
285     def clean(self):
286         try:
287             assert self.orig_cloud
288             assert self.cloud
289             self.cloud.delete_server(self.sshvm, wait=True)
290             self.cloud.delete_security_group(self.sec.id)
291             self.cloud.delete_image(self.image)
292             self.orig_cloud.delete_flavor(self.flavor.id)
293             self.cloud.delete_keypair(self.keypair.id)
294             self.cloud.delete_floating_ip(self.fip.id)
295             self.cloud.delete_image(self.image)
296         except Exception:  # pylint: disable=broad-except
297             pass
298
299
300 class SingleVm2(SingleVm1):
301     """Deploy a single VM reachable via ssh (scenario2)
302
303     It creates new user/project before creating and configuring all tenant
304     network ressources and vms required by advanced testcases.
305
306     It ensures that all testcases inheriting from SingleVm2 could work
307     without specific configurations (or at least read the same config data).
308     """
309
310     __logger = logging.getLogger(__name__)
311
312     def __init__(self, **kwargs):
313         if "case_name" not in kwargs:
314             kwargs["case_name"] = 'singlevm2'
315         super(SingleVm2, self).__init__(**kwargs)
316         try:
317             assert self.orig_cloud
318             self.project = tenantnetwork.NewProject(
319                 self.orig_cloud, self.case_name, self.guid)
320             self.project.create()
321             self.cloud = self.project.cloud
322         except Exception:  # pylint: disable=broad-except
323             self.__logger.exception("Cannot create user or project")
324             self.cloud = None
325             self.project = None
326
327     def clean(self):
328         try:
329             super(SingleVm2, self).clean()
330             assert self.project
331             self.project.clean()
332         except Exception:  # pylint: disable=broad-except
333             self.__logger.exception("cannot clean all ressources")