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