Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (14.8 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 mock import patch
37
from snf_django.utils.testing import mocked_quotaholder
38
from time import time
39
from synnefo import settings
40

    
41

    
42
@patch("synnefo.logic.rapi_pool.GanetiRapiClient")
43
class ServerReconciliationTest(TestCase):
44
    @patch("synnefo.logic.rapi_pool.GanetiRapiClient")
45
    def setUp(self, mrapi):
46
        self.backend = mfactory.BackendFactory()
47
        log = logging.getLogger()
48
        options = {"fix_unsynced": True,
49
                   "fix_stale": True,
50
                   "fix_orphans": True,
51
                   "fix_unsynced_nics": True,
52
                   "fix_unsynced_flavors": True}
53
        self.reconciler = reconciliation.BackendReconciler(self.backend,
54
                                                           options=options,
55
                                                           logger=log)
56

    
57
    def test_building_vm(self, mrapi):
58
        vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
59
                                             backendjobid=1,
60
                                             operstate="BUILD")
61
        for status in ["queued", "waiting", "running"]:
62
            mrapi().GetJobs.return_value = [{"id": "1", "status": status}]
63
            with mocked_quotaholder():
64
                self.reconciler.reconcile()
65
            vm1 = VirtualMachine.objects.get(id=vm1.id)
66
            self.assertFalse(vm1.deleted)
67
            self.assertEqual(vm1.operstate, "BUILD")
68

    
69
        mrapi().GetJobs.return_value = [{"id": "1", "status": "error"}]
70
        with mocked_quotaholder():
71
            self.reconciler.reconcile()
72
        vm1 = VirtualMachine.objects.get(id=vm1.id)
73
        self.assertFalse(vm1.deleted)
74
        self.assertEqual(vm1.operstate, "ERROR")
75

    
76
        for status in ["success", "canceled"]:
77
            vm1.operstate = "BUILD"
78
            vm1.deleted = False
79
            vm1.save()
80
            mrapi().GetJobs.return_value = [{"id": "1", "status": status}]
81
            with mocked_quotaholder():
82
                self.reconciler.reconcile()
83
            vm1 = VirtualMachine.objects.get(id=vm1.id)
84
            self.assertFalse(vm1.deleted)
85
            self.assertEqual(vm1.operstate, "ERROR")
86

    
87
    def test_stale_server(self, mrapi):
88
        mrapi.GetInstances = []
89
        vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
90
                                             deleted=False,
91
                                             operstate="ERROR")
92

    
93
        with mocked_quotaholder():
94
            self.reconciler.reconcile()
95
        vm1 = VirtualMachine.objects.get(id=vm1.id)
96
        self.assertFalse(vm1.deleted)
97
        vm2 = mfactory.VirtualMachineFactory(backend=self.backend,
98
                                             deleted=False,
99
                                             action="DESTROY",
100
                                             operstate="ERROR")
101
        with mocked_quotaholder():
102
            self.reconciler.reconcile()
103
        vm2 = VirtualMachine.objects.get(id=vm2.id)
104
        self.assertTrue(vm2.deleted)
105
        vm3 = mfactory.VirtualMachineFactory(backend=self.backend,
106
                                             deleted=False,
107
                                             action="DESTROY",
108
                                             operstate="ACTIVE")
109
        with mocked_quotaholder():
110
            self.reconciler.reconcile()
111
        vm3 = VirtualMachine.objects.get(id=vm3.id)
112
        self.assertTrue(vm3.deleted)
113

    
114
    def test_orphan_server(self, mrapi):
115
        cmrapi = self.reconciler.client
116
        mrapi().GetInstances.return_value =\
117
            [{"name": "%s22" % settings.BACKEND_PREFIX_ID,
118
             "beparams": {"maxmem": 1024,
119
                          "minmem": 1024,
120
                          "vcpus": 4},
121
             "oper_state": True,
122
             "mtime": time(),
123
             "disk.sizes": [],
124
             "nic.ips": [],
125
             "nic.names": [],
126
             "nic.macs": [],
127
             "nic.networks.names": [],
128
             "tags": []}]
129
        self.reconciler.reconcile()
130
        cmrapi.DeleteInstance\
131
              .assert_called_once_with("%s22" % settings.BACKEND_PREFIX_ID)
132

    
133
    def test_unsynced_operstate(self, mrapi):
134
        vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
135
                                             deleted=False,
136
                                             operstate="STOPPED")
137
        mrapi().GetInstances.return_value =\
138
            [{"name": vm1.backend_vm_id,
139
             "beparams": {"maxmem": 1024,
140
                          "minmem": 1024,
141
                          "vcpus": 4},
142
             "oper_state": True,
143
             "mtime": time(),
144
             "disk.sizes": [],
145
             "nic.ips": [],
146
             "nic.names": [],
147
             "nic.macs": [],
148
             "nic.networks.names": [],
149
             "tags": []}]
150
        with mocked_quotaholder():
151
            self.reconciler.reconcile()
152
        vm1 = VirtualMachine.objects.get(id=vm1.id)
153
        self.assertEqual(vm1.operstate, "STARTED")
154

    
155
    def test_unsynced_flavor(self, mrapi):
156
        flavor1 = mfactory.FlavorFactory(cpu=2, ram=1024, disk=1,
157
                                         disk_template="drbd")
158
        flavor2 = mfactory.FlavorFactory(cpu=4, ram=2048, disk=1,
159
                                         disk_template="drbd")
160
        vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
161
                                             deleted=False,
162
                                             flavor=flavor1,
163
                                             operstate="STARTED")
164
        mrapi().GetInstances.return_value =\
165
            [{"name": vm1.backend_vm_id,
166
             "beparams": {"maxmem": 2048,
167
                          "minmem": 2048,
168
                          "vcpus": 4},
169
             "oper_state": True,
170
             "mtime": time(),
171
             "disk.sizes": [],
172
             "nic.ips": [],
173
             "nic.names": [],
174
             "nic.macs": [],
175
             "nic.networks.names": [],
176
             "tags": []}]
177
        with mocked_quotaholder():
178
            self.reconciler.reconcile()
179
        vm1 = VirtualMachine.objects.get(id=vm1.id)
180
        self.assertEqual(vm1.flavor, flavor2)
181
        self.assertEqual(vm1.operstate, "STARTED")
182

    
183
    def test_unsynced_nics(self, mrapi):
184
        network1 = mfactory.NetworkFactory(subnet="10.0.0.0/24")
185
        network2 = mfactory.NetworkFactory(subnet="192.168.2.0/24")
186
        vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
187
                                             deleted=False,
188
                                             operstate="STOPPED")
189
        nic = mfactory.NetworkInterfaceFactory(machine=vm1, network=network1,
190
                                               ipv4="10.0.0.0")
191
        mrapi().GetInstances.return_value =\
192
            [{"name": vm1.backend_vm_id,
193
             "beparams": {"maxmem": 2048,
194
                          "minmem": 2048,
195
                          "vcpus": 4},
196
             "oper_state": True,
197
             "mtime": time(),
198
             "disk.sizes": [],
199
             "nic.names": [nic.backend_uuid],
200
             "nic.ips": ["192.168.2.1"],
201
             "nic.macs": ["aa:00:bb:cc:dd:ee"],
202
             "nic.networks.names": [network2.backend_id],
203
             "tags": []}]
204
        with mocked_quotaholder():
205
            self.reconciler.reconcile()
206
        vm1 = VirtualMachine.objects.get(id=vm1.id)
207
        self.assertEqual(vm1.operstate, "STARTED")
208
        nic = vm1.nics.all()[0]
209
        self.assertEqual(nic.network, network2)
210
        self.assertEqual(nic.ipv4, "192.168.2.1")
211
        self.assertEqual(nic.mac, "aa:00:bb:cc:dd:ee")
212

    
213

    
214
@patch("synnefo.logic.rapi_pool.GanetiRapiClient")
215
class NetworkReconciliationTest(TestCase):
216
    def setUp(self):
217
        self.backend = mfactory.BackendFactory()
218
        log = logging.getLogger()
219
        self.reconciler = reconciliation.NetworkReconciler(
220
            logger=log,
221
            fix=True)
222

    
223
    def test_parted_network(self, mrapi):
224
        net1 = mfactory.NetworkFactory(subnet="192.168.0.0/30", public=False)
225
        mrapi().GetNetworks.return_value = []
226
        # Test nothing if Ganeti returns nothing
227
        self.assertEqual(net1.backend_networks.count(), 0)
228
        self.reconciler.reconcile_networks()
229
        self.assertEqual(net1.backend_networks.count(), 0)
230

    
231
        # Test creation if exists in Ganeti
232
        self.assertEqual(net1.backend_networks.count(), 0)
233
        mrapi().GetNetworks.return_value = [{"name": net1.backend_id,
234
                                             "group_list": [["default",
235
                                                             "bridged",
236
                                                             "prv0"]],
237
                                             "network": net1.subnet,
238
                                             "map": "....",
239
                                             "external_reservations": ""}]
240
        self.reconciler.reconcile_networks()
241
        self.assertTrue(net1.backend_networks
242
                            .filter(backend=self.backend).exists())
243
        # ..but not if it is destroying
244
        net1.backend_networks.all().delete()
245
        net1.action = "DESTROY"
246
        net1.save()
247
        self.reconciler.reconcile_networks()
248
        self.assertFalse(net1.backend_networks
249
                             .filter(backend=self.backend).exists())
250
        # or network is public!
251
        net1.action = "CREATE"
252
        net1.public = True
253
        net1.save()
254
        self.reconciler.reconcile_networks()
255
        self.assertFalse(net1.backend_networks
256
                             .filter(backend=self.backend).exists())
257
        # Test creation if network is a floating IP pool
258
        net2 = mfactory.NetworkFactory(subnet="192.168.0.0/30",
259
                                       floating_ip_pool=True)
260
        mrapi().GetNetworks.return_value = []
261
        self.assertEqual(net2.backend_networks.count(), 0)
262
        self.reconciler.reconcile_networks()
263
        self.assertTrue(net2.backend_networks
264
                            .filter(backend=self.backend).exists())
265

    
266
    def test_stale_network(self, mrapi):
267
        # Test that stale network will be deleted from DB, if network action is
268
        # destroy
269
        net1 = mfactory.NetworkFactory(subnet="192.168.0.0/30", public=False,
270
                                       flavor="IP_LESS_ROUTED",
271
                                       action="DESTROY", deleted=False)
272
        bn1 = mfactory.BackendNetworkFactory(network=net1,
273
                                             backend=self.backend,
274
                                             operstate="ACTIVE")
275
        mrapi().GetNetworks.return_value = []
276
        self.assertFalse(net1.deleted)
277
        with mocked_quotaholder():
278
            self.reconciler.reconcile_networks()
279
        bn1 = BackendNetwork.objects.get(id=bn1.id)
280
        net1 = Network.objects.get(id=net1.id)
281
        self.assertEqual(bn1.operstate, "DELETED")
282
        self.assertTrue(net1.deleted)
283
        # But not if action is not DESTROY
284
        net2 = mfactory.NetworkFactory(subnet="192.168.0.0/30", public=False,
285
                                       action="CREATE")
286
        mfactory.BackendNetworkFactory(network=net2, backend=self.backend)
287
        self.assertFalse(net2.deleted)
288
        self.reconciler.reconcile_networks()
289
        self.assertFalse(net2.deleted)
290

    
291
    def test_missing_network(self, mrapi):
292
        net2 = mfactory.NetworkFactory(subnet="192.168.0.0/30", public=False,
293
                                       action="CREATE")
294
        mfactory.BackendNetworkFactory(network=net2, backend=self.backend)
295
        mrapi().GetNetworks.return_value = []
296
        self.reconciler.reconcile_networks()
297
        self.assertEqual(len(mrapi().CreateNetwork.mock_calls), 1)
298

    
299
    #def test_hanging_networks(self, mrapi):
300
    #    pass
301

    
302
    def test_unsynced_networks(self, mrapi):
303
        net = mfactory.NetworkFactory(subnet="192.168.0.0/30", public=False,
304
                                      state="PENDING",
305
                                      action="CREATE", deleted=False)
306
        bn = mfactory.BackendNetworkFactory(network=net, backend=self.backend,
307
                                            operstate="PENDING")
308
        mrapi().GetNetworks.return_value = [{"name": net.backend_id,
309
                                             "group_list": [],
310
                                             "network": net.subnet,
311
                                             "map": "....",
312
                                             "external_reservations": ""}]
313
        self.assertEqual(bn.operstate, "PENDING")
314
        self.reconciler.reconcile_networks()
315
        bn = BackendNetwork.objects.get(id=bn.id)
316
        self.assertEqual(bn.operstate, "ACTIVE")
317

    
318
    def test_orphan_networks(self, mrapi):
319
        net = mfactory.NetworkFactory(subnet="192.168.0.0/30", public=False,
320
                                      action="CREATE", deleted=True)
321
        mrapi().GetNetworks.return_value = [{"name": net.backend_id,
322
                                             "group_list": [],
323
                                             "network": net.subnet,
324
                                             "map": "....",
325
                                             "external_reservations": ""}]
326
        self.reconciler.reconcile_networks()
327
        mrapi().DeleteNetwork.assert_called_once_with(net.backend_id, [])