Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / tests / reconciliation.py @ 6e9255ab

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, [])