root / snf-cyclades-app / synnefo / logic / tests / reconciliation.py @ be4d8469
History | View | Annotate | Download (15.7 kB)
1 |
# vim: set fileencoding=utf-8 :
|
---|---|
2 |
# Copyright 2012 GRNET S.A. All rights reserved.
|
3 |
#
|
4 |
# Redistribution and use in source and binary forms, with or without
|
5 |
# modification, are permitted provided that the following conditions
|
6 |
# are met:
|
7 |
#
|
8 |
# 1. Redistributions of source code must retain the above copyright
|
9 |
# notice, this list of conditions and the following disclaimer.
|
10 |
#
|
11 |
# 2. Redistributions in binary form must reproduce the above copyright
|
12 |
# notice, this list of conditions and the following disclaimer in the
|
13 |
# documentation and/or other materials provided with the distribution.
|
14 |
#
|
15 |
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
16 |
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
17 |
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
18 |
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
19 |
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
20 |
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
21 |
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
22 |
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
23 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
24 |
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
25 |
# SUCH DAMAGE.
|
26 |
#
|
27 |
# The views and conclusions contained in the software and documentation are
|
28 |
# those of the authors and should not be interpreted as representing official
|
29 |
# policies, either expressed or implied, of GRNET S.A.
|
30 |
import logging |
31 |
from django.test import TestCase |
32 |
|
33 |
from synnefo.db.models import VirtualMachine, Network, BackendNetwork |
34 |
from synnefo.db import models_factory as mfactory |
35 |
from synnefo.logic import reconciliation |
36 |
from datetime import timedelta |
37 |
from mock import patch |
38 |
from snf_django.utils.testing import mocked_quotaholder |
39 |
from time import time |
40 |
from synnefo import settings |
41 |
|
42 |
|
43 |
@patch("synnefo.logic.rapi_pool.GanetiRapiClient") |
44 |
class ServerReconciliationTest(TestCase): |
45 |
@patch("synnefo.logic.rapi_pool.GanetiRapiClient") |
46 |
def setUp(self, mrapi): |
47 |
self.backend = mfactory.BackendFactory()
|
48 |
log = logging.getLogger() |
49 |
options = {"fix_unsynced": True, |
50 |
"fix_stale": True, |
51 |
"fix_orphans": True, |
52 |
"fix_unsynced_nics": True, |
53 |
"fix_unsynced_flavors": True} |
54 |
self.reconciler = reconciliation.BackendReconciler(self.backend, |
55 |
options=options, |
56 |
logger=log) |
57 |
|
58 |
def test_building_vm(self, mrapi): |
59 |
mrapi = self.reconciler.client
|
60 |
vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
|
61 |
backendjobid=None,
|
62 |
operstate="BUILD")
|
63 |
self.reconciler.reconcile()
|
64 |
# Assert not deleted
|
65 |
vm1 = VirtualMachine.objects.get(id=vm1.id) |
66 |
self.assertFalse(vm1.deleted)
|
67 |
self.assertEqual(vm1.operstate, "BUILD") |
68 |
|
69 |
vm1.created = vm1.created - timedelta(seconds=120)
|
70 |
vm1.save() |
71 |
with mocked_quotaholder():
|
72 |
self.reconciler.reconcile()
|
73 |
vm1 = VirtualMachine.objects.get(id=vm1.id) |
74 |
self.assertEqual(vm1.operstate, "ERROR") |
75 |
|
76 |
vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
|
77 |
backendjobid=1,
|
78 |
deleted=False,
|
79 |
operstate="BUILD")
|
80 |
vm1.backendtime = vm1.created - timedelta(seconds=120)
|
81 |
vm1.backendjobid = 10
|
82 |
vm1.save() |
83 |
for status in ["queued", "waiting", "running"]: |
84 |
mrapi.GetJobStatus.return_value = {"status": status}
|
85 |
with mocked_quotaholder():
|
86 |
self.reconciler.reconcile()
|
87 |
vm1 = VirtualMachine.objects.get(id=vm1.id) |
88 |
self.assertFalse(vm1.deleted)
|
89 |
self.assertEqual(vm1.operstate, "BUILD") |
90 |
|
91 |
mrapi.GetJobStatus.return_value = {"status": "error"} |
92 |
with mocked_quotaholder():
|
93 |
self.reconciler.reconcile()
|
94 |
vm1 = VirtualMachine.objects.get(id=vm1.id) |
95 |
self.assertFalse(vm1.deleted)
|
96 |
self.assertEqual(vm1.operstate, "ERROR") |
97 |
|
98 |
for status in ["success", "cancelled"]: |
99 |
vm1.deleted = False
|
100 |
vm1.save() |
101 |
mrapi.GetJobStatus.return_value = {"status": status}
|
102 |
with mocked_quotaholder():
|
103 |
self.reconciler.reconcile()
|
104 |
vm1 = VirtualMachine.objects.get(id=vm1.id) |
105 |
self.assertTrue(vm1.deleted)
|
106 |
self.assertEqual(vm1.operstate, "DESTROYED") |
107 |
|
108 |
vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
|
109 |
backendjobid=1,
|
110 |
operstate="BUILD")
|
111 |
vm1.backendtime = vm1.created - timedelta(seconds=120)
|
112 |
vm1.backendjobid = 10
|
113 |
vm1.save() |
114 |
cmrapi = self.reconciler.client
|
115 |
cmrapi.GetInstances.return_value = \ |
116 |
[{"name": vm1.backend_vm_id,
|
117 |
"beparams": {"maxmem": 1024, |
118 |
"minmem": 1024, |
119 |
"vcpus": 4}, |
120 |
"oper_state": False, |
121 |
"mtime": time(),
|
122 |
"disk.sizes": [],
|
123 |
"nic.ips": [],
|
124 |
"nic.macs": [],
|
125 |
"nic.networks": [],
|
126 |
"tags": []}]
|
127 |
mrapi.GetJobStatus.return_value = {"status": "running"} |
128 |
with mocked_quotaholder():
|
129 |
self.reconciler.reconcile()
|
130 |
vm1 = VirtualMachine.objects.get(id=vm1.id) |
131 |
self.assertEqual(vm1.operstate, "BUILD") |
132 |
mrapi.GetJobStatus.return_value = {"status": "error"} |
133 |
with mocked_quotaholder():
|
134 |
self.reconciler.reconcile()
|
135 |
vm1 = VirtualMachine.objects.get(id=vm1.id) |
136 |
self.assertEqual(vm1.operstate, "ERROR") |
137 |
|
138 |
def test_stale_server(self, mrapi): |
139 |
mrapi.GetInstances = [] |
140 |
vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
|
141 |
deleted=False,
|
142 |
operstate="ERROR")
|
143 |
with mocked_quotaholder():
|
144 |
self.reconciler.reconcile()
|
145 |
vm1 = VirtualMachine.objects.get(id=vm1.id) |
146 |
self.assertTrue(vm1.deleted)
|
147 |
|
148 |
def test_orphan_server(self, mrapi): |
149 |
cmrapi = self.reconciler.client
|
150 |
mrapi().GetInstances.return_value =\ |
151 |
[{"name": "%s22" % settings.BACKEND_PREFIX_ID, |
152 |
"beparams": {"maxmem": 1024, |
153 |
"minmem": 1024, |
154 |
"vcpus": 4}, |
155 |
"oper_state": True, |
156 |
"mtime": time(),
|
157 |
"disk.sizes": [],
|
158 |
"nic.ips": [],
|
159 |
"nic.macs": [],
|
160 |
"nic.networks": [],
|
161 |
"tags": []}]
|
162 |
self.reconciler.reconcile()
|
163 |
cmrapi.DeleteInstance\ |
164 |
.assert_called_once_with("%s22" % settings.BACKEND_PREFIX_ID)
|
165 |
|
166 |
def test_unsynced_operstate(self, mrapi): |
167 |
vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
|
168 |
deleted=False,
|
169 |
operstate="STOPPED")
|
170 |
mrapi().GetInstances.return_value =\ |
171 |
[{"name": vm1.backend_vm_id,
|
172 |
"beparams": {"maxmem": 1024, |
173 |
"minmem": 1024, |
174 |
"vcpus": 4}, |
175 |
"oper_state": True, |
176 |
"mtime": time(),
|
177 |
"disk.sizes": [],
|
178 |
"nic.ips": [],
|
179 |
"nic.macs": [],
|
180 |
"nic.networks": [],
|
181 |
"tags": []}]
|
182 |
with mocked_quotaholder():
|
183 |
self.reconciler.reconcile()
|
184 |
vm1 = VirtualMachine.objects.get(id=vm1.id) |
185 |
self.assertEqual(vm1.operstate, "STARTED") |
186 |
|
187 |
def test_unsynced_flavor(self, mrapi): |
188 |
flavor1 = mfactory.FlavorFactory(cpu=2, ram=1024, disk=1, |
189 |
disk_template="drbd")
|
190 |
flavor2 = mfactory.FlavorFactory(cpu=4, ram=2048, disk=1, |
191 |
disk_template="drbd")
|
192 |
vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
|
193 |
deleted=False,
|
194 |
flavor=flavor1, |
195 |
operstate="STARTED")
|
196 |
mrapi().GetInstances.return_value =\ |
197 |
[{"name": vm1.backend_vm_id,
|
198 |
"beparams": {"maxmem": 2048, |
199 |
"minmem": 2048, |
200 |
"vcpus": 4}, |
201 |
"oper_state": True, |
202 |
"mtime": time(),
|
203 |
"disk.sizes": [],
|
204 |
"nic.ips": [],
|
205 |
"nic.macs": [],
|
206 |
"nic.networks": [],
|
207 |
"tags": []}]
|
208 |
with mocked_quotaholder():
|
209 |
self.reconciler.reconcile()
|
210 |
vm1 = VirtualMachine.objects.get(id=vm1.id) |
211 |
self.assertEqual(vm1.flavor, flavor2)
|
212 |
self.assertEqual(vm1.operstate, "STARTED") |
213 |
|
214 |
def test_unsynced_nics(self, mrapi): |
215 |
network1 = mfactory.NetworkFactory(subnet="10.0.0.0/24")
|
216 |
network2 = mfactory.NetworkFactory(subnet="192.168.2.0/24")
|
217 |
vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
|
218 |
deleted=False,
|
219 |
operstate="STOPPED")
|
220 |
mfactory.NetworkInterfaceFactory(machine=vm1, network=network1, |
221 |
ipv4="10.0.0.0")
|
222 |
mrapi().GetInstances.return_value =\ |
223 |
[{"name": vm1.backend_vm_id,
|
224 |
"beparams": {"maxmem": 2048, |
225 |
"minmem": 2048, |
226 |
"vcpus": 4}, |
227 |
"oper_state": True, |
228 |
"mtime": time(),
|
229 |
"disk.sizes": [],
|
230 |
"nic.ips": ["192.168.2.1"], |
231 |
"nic.macs": ["aa:00:bb:cc:dd:ee"], |
232 |
"nic.networks": [network2.backend_id],
|
233 |
"tags": []}]
|
234 |
with mocked_quotaholder():
|
235 |
self.reconciler.reconcile()
|
236 |
vm1 = VirtualMachine.objects.get(id=vm1.id) |
237 |
self.assertEqual(vm1.operstate, "STARTED") |
238 |
nic = vm1.nics.all()[0]
|
239 |
self.assertEqual(nic.network, network2)
|
240 |
self.assertEqual(nic.ipv4, "192.168.2.1") |
241 |
self.assertEqual(nic.mac, "aa:00:bb:cc:dd:ee") |
242 |
|
243 |
|
244 |
@patch("synnefo.logic.rapi_pool.GanetiRapiClient") |
245 |
class NetworkReconciliationTest(TestCase): |
246 |
def setUp(self): |
247 |
self.backend = mfactory.BackendFactory()
|
248 |
log = logging.getLogger() |
249 |
self.reconciler = reconciliation.NetworkReconciler(
|
250 |
logger=log, |
251 |
fix=True)
|
252 |
|
253 |
def test_parted_network(self, mrapi): |
254 |
net1 = mfactory.NetworkFactory(subnet="192.168.0.0/30", public=False) |
255 |
mrapi().GetNetworks.return_value = [] |
256 |
# Test nothing if Ganeti returns nothing
|
257 |
self.assertEqual(net1.backend_networks.count(), 0) |
258 |
self.reconciler.reconcile_networks()
|
259 |
self.assertEqual(net1.backend_networks.count(), 0) |
260 |
|
261 |
# Test creation if exists in Ganeti
|
262 |
self.assertEqual(net1.backend_networks.count(), 0) |
263 |
mrapi().GetNetworks.return_value = [{"name": net1.backend_id,
|
264 |
"group_list": ["default"], |
265 |
"network": net1.subnet,
|
266 |
"map": "....", |
267 |
"external_reservations": ""}] |
268 |
self.reconciler.reconcile_networks()
|
269 |
self.assertTrue(net1.backend_networks
|
270 |
.filter(backend=self.backend).exists())
|
271 |
# ..but not if it is destroying
|
272 |
net1.backend_networks.all().delete() |
273 |
net1.action = "DESTROY"
|
274 |
net1.save() |
275 |
self.reconciler.reconcile_networks()
|
276 |
self.assertFalse(net1.backend_networks
|
277 |
.filter(backend=self.backend).exists())
|
278 |
# or network is public!
|
279 |
net1.action = "CREATE"
|
280 |
net1.public = True
|
281 |
net1.save() |
282 |
self.reconciler.reconcile_networks()
|
283 |
self.assertFalse(net1.backend_networks
|
284 |
.filter(backend=self.backend).exists())
|
285 |
# Test creation if network is a floating IP pool
|
286 |
net2 = mfactory.NetworkFactory(subnet="192.168.0.0/30",
|
287 |
floating_ip_pool=True)
|
288 |
mrapi().GetNetworks.return_value = [] |
289 |
self.assertEqual(net2.backend_networks.count(), 0) |
290 |
self.reconciler.reconcile_networks()
|
291 |
self.assertTrue(net2.backend_networks
|
292 |
.filter(backend=self.backend).exists())
|
293 |
|
294 |
def test_stale_network(self, mrapi): |
295 |
# Test that stale network will be deleted from DB, if network action is
|
296 |
# destroy
|
297 |
net1 = mfactory.NetworkFactory(subnet="192.168.0.0/30", public=False, |
298 |
flavor="IP_LESS_ROUTED",
|
299 |
action="DESTROY", deleted=False) |
300 |
bn1 = mfactory.BackendNetworkFactory(network=net1, |
301 |
backend=self.backend,
|
302 |
operstate="ACTIVE")
|
303 |
mrapi().GetNetworks.return_value = [] |
304 |
self.assertFalse(net1.deleted)
|
305 |
with mocked_quotaholder():
|
306 |
self.reconciler.reconcile_networks()
|
307 |
bn1 = BackendNetwork.objects.get(id=bn1.id) |
308 |
net1 = Network.objects.get(id=net1.id) |
309 |
self.assertEqual(bn1.operstate, "DELETED") |
310 |
self.assertTrue(net1.deleted)
|
311 |
# But not if action is not DESTROY
|
312 |
net2 = mfactory.NetworkFactory(subnet="192.168.0.0/30", public=False, |
313 |
action="CREATE")
|
314 |
mfactory.BackendNetworkFactory(network=net2, backend=self.backend)
|
315 |
self.assertFalse(net2.deleted)
|
316 |
self.reconciler.reconcile_networks()
|
317 |
self.assertFalse(net2.deleted)
|
318 |
|
319 |
def test_missing_network(self, mrapi): |
320 |
net2 = mfactory.NetworkFactory(subnet="192.168.0.0/30", public=False, |
321 |
action="CREATE")
|
322 |
mfactory.BackendNetworkFactory(network=net2, backend=self.backend)
|
323 |
mrapi().GetNetworks.return_value = [] |
324 |
self.reconciler.reconcile_networks()
|
325 |
self.assertEqual(len(mrapi().CreateNetwork.mock_calls), 1) |
326 |
|
327 |
#def test_hanging_networks(self, mrapi):
|
328 |
# pass
|
329 |
|
330 |
def test_unsynced_networks(self, mrapi): |
331 |
net = mfactory.NetworkFactory(subnet="192.168.0.0/30", public=False, |
332 |
state="PENDING",
|
333 |
action="CREATE", deleted=False) |
334 |
bn = mfactory.BackendNetworkFactory(network=net, backend=self.backend,
|
335 |
operstate="PENDING")
|
336 |
mrapi().GetNetworks.return_value = [{"name": net.backend_id,
|
337 |
"group_list": [],
|
338 |
"network": net.subnet,
|
339 |
"map": "....", |
340 |
"external_reservations": ""}] |
341 |
self.assertEqual(bn.operstate, "PENDING") |
342 |
self.reconciler.reconcile_networks()
|
343 |
bn = BackendNetwork.objects.get(id=bn.id) |
344 |
self.assertEqual(bn.operstate, "ACTIVE") |
345 |
|
346 |
def test_orphan_networks(self, mrapi): |
347 |
net = mfactory.NetworkFactory(subnet="192.168.0.0/30", public=False, |
348 |
action="CREATE", deleted=True) |
349 |
mrapi().GetNetworks.return_value = [{"name": net.backend_id,
|
350 |
"group_list": [],
|
351 |
"network": net.subnet,
|
352 |
"map": "....", |
353 |
"external_reservations": ""}] |
354 |
self.reconciler.reconcile_networks()
|
355 |
mrapi().DeleteNetwork.assert_called_once_with(net.backend_id, []) |