Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (14.5 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.macs": [],
126
             "nic.networks": [],
127
             "tags": []}]
128
        self.reconciler.reconcile()
129
        cmrapi.DeleteInstance\
130
              .assert_called_once_with("%s22" % settings.BACKEND_PREFIX_ID)
131

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

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

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

    
209

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

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

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

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

    
285
    def test_missing_network(self, mrapi):
286
        net2 = mfactory.NetworkFactory(subnet="192.168.0.0/30", public=False,
287
                                       action="CREATE")
288
        mfactory.BackendNetworkFactory(network=net2, backend=self.backend)
289
        mrapi().GetNetworks.return_value = []
290
        self.reconciler.reconcile_networks()
291
        self.assertEqual(len(mrapi().CreateNetwork.mock_calls), 1)
292

    
293
    #def test_hanging_networks(self, mrapi):
294
    #    pass
295

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

    
312
    def test_orphan_networks(self, mrapi):
313
        net = mfactory.NetworkFactory(subnet="192.168.0.0/30", public=False,
314
                                      action="CREATE", deleted=True)
315
        mrapi().GetNetworks.return_value = [{"name": net.backend_id,
316
                                             "group_list": [],
317
                                             "network": net.subnet,
318
                                             "map": "....",
319
                                             "external_reservations": ""}]
320
        self.reconciler.reconcile_networks()
321
        mrapi().DeleteNetwork.assert_called_once_with(net.backend_id, [])