Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (14.9 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.NetworkWithSubnetFactory(
185
            subnet__cidr="10.0.0.0/24", subnet__gateway="10.0.0.2")
186
        network2 = mfactory.NetworkWithSubnetFactory(
187
            subnet__cidr="192.168.2.0/24", subnet__gateway="192.168.2.2")
188
        vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
189
                                             deleted=False,
190
                                             operstate="STOPPED")
191
        subnet = network1.subnets.get(ipversion=4)
192
        ip = mfactory.IPv4AddressFactory(nic__machine=vm1, network=network1,
193
                                         subnet=subnet,
194
                                         address="10.0.0.3")
195
        nic = ip.nic
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.names": [nic.backend_uuid],
205
             "nic.ips": ["192.168.2.5"],
206
             "nic.macs": ["aa:00:bb:cc:dd:ee"],
207
             "nic.networks.names": [network2.backend_id],
208
             "tags": []}]
209
        with mocked_quotaholder():
210
            self.reconciler.reconcile()
211
        vm1 = VirtualMachine.objects.get(id=vm1.id)
212
        self.assertEqual(vm1.operstate, "STARTED")
213
        nic = vm1.nics.all()[0]
214
        self.assertEqual(nic.network, network2)
215
        self.assertEqual(nic.ipv4_address, "192.168.2.5")
216
        self.assertEqual(nic.mac, "aa:00:bb:cc:dd:ee")
217

    
218

    
219
@patch("synnefo.logic.rapi_pool.GanetiRapiClient")
220
class NetworkReconciliationTest(TestCase):
221
    def setUp(self):
222
        self.backend = mfactory.BackendFactory()
223
        log = logging.getLogger()
224
        self.reconciler = reconciliation.NetworkReconciler(
225
            logger=log,
226
            fix=True)
227

    
228
    def test_parted_network(self, mrapi):
229
        net1 = mfactory.NetworkWithSubnetFactory(public=False)
230
        mrapi().GetNetworks.return_value = []
231
        # Test nothing if Ganeti returns nothing
232
        self.assertEqual(net1.backend_networks.count(), 0)
233
        self.reconciler.reconcile_networks()
234
        self.assertEqual(net1.backend_networks.count(), 0)
235

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

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

    
295
    def test_missing_network(self, mrapi):
296
        net2 = mfactory.NetworkWithSubnetFactory(public=False, action="CREATE")
297
        mfactory.BackendNetworkFactory(network=net2, backend=self.backend)
298
        mrapi().GetNetworks.return_value = []
299
        self.reconciler.reconcile_networks()
300
        self.assertEqual(len(mrapi().CreateNetwork.mock_calls), 1)
301

    
302
    #def test_hanging_networks(self, mrapi):
303
    #    pass
304

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

    
320
    def test_orphan_networks(self, mrapi):
321
        net = mfactory.NetworkWithSubnetFactory(public=False, action="CREATE",
322
                                                deleted=True)
323
        mrapi().GetNetworks.return_value = [{"name": net.backend_id,
324
                                             "group_list": [],
325
                                             "network": net.subnet4,
326
                                             "map": "....",
327
                                             "external_reservations": ""}]
328
        self.reconciler.reconcile_networks()
329
        mrapi().DeleteNetwork.assert_called_once_with(net.backend_id, [])