Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (13.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 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
        with mocked_quotaholder():
93
            self.reconciler.reconcile()
94
        vm1 = VirtualMachine.objects.get(id=vm1.id)
95
        self.assertTrue(vm1.deleted)
96

    
97
    def test_orphan_server(self, mrapi):
98
        cmrapi = self.reconciler.client
99
        mrapi().GetInstances.return_value =\
100
            [{"name": "%s22" % settings.BACKEND_PREFIX_ID,
101
             "beparams": {"maxmem": 1024,
102
                          "minmem": 1024,
103
                          "vcpus": 4},
104
             "oper_state": True,
105
             "mtime": time(),
106
             "disk.sizes": [],
107
             "nic.ips": [],
108
             "nic.macs": [],
109
             "nic.networks": [],
110
             "tags": []}]
111
        self.reconciler.reconcile()
112
        cmrapi.DeleteInstance\
113
              .assert_called_once_with("%s22" % settings.BACKEND_PREFIX_ID)
114

    
115
    def test_unsynced_operstate(self, mrapi):
116
        vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
117
                                             deleted=False,
118
                                             operstate="STOPPED")
119
        mrapi().GetInstances.return_value =\
120
            [{"name": vm1.backend_vm_id,
121
             "beparams": {"maxmem": 1024,
122
                          "minmem": 1024,
123
                          "vcpus": 4},
124
             "oper_state": True,
125
             "mtime": time(),
126
             "disk.sizes": [],
127
             "nic.ips": [],
128
             "nic.macs": [],
129
             "nic.networks": [],
130
             "tags": []}]
131
        with mocked_quotaholder():
132
            self.reconciler.reconcile()
133
        vm1 = VirtualMachine.objects.get(id=vm1.id)
134
        self.assertEqual(vm1.operstate, "STARTED")
135

    
136
    def test_unsynced_flavor(self, mrapi):
137
        flavor1 = mfactory.FlavorFactory(cpu=2, ram=1024, disk=1,
138
                                         disk_template="drbd")
139
        flavor2 = mfactory.FlavorFactory(cpu=4, ram=2048, disk=1,
140
                                         disk_template="drbd")
141
        vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
142
                                             deleted=False,
143
                                             flavor=flavor1,
144
                                             operstate="STARTED")
145
        mrapi().GetInstances.return_value =\
146
            [{"name": vm1.backend_vm_id,
147
             "beparams": {"maxmem": 2048,
148
                          "minmem": 2048,
149
                          "vcpus": 4},
150
             "oper_state": True,
151
             "mtime": time(),
152
             "disk.sizes": [],
153
             "nic.ips": [],
154
             "nic.macs": [],
155
             "nic.networks": [],
156
             "tags": []}]
157
        with mocked_quotaholder():
158
            self.reconciler.reconcile()
159
        vm1 = VirtualMachine.objects.get(id=vm1.id)
160
        self.assertEqual(vm1.flavor, flavor2)
161
        self.assertEqual(vm1.operstate, "STARTED")
162

    
163
    def test_unsynced_nics(self, mrapi):
164
        network1 = mfactory.NetworkFactory(subnet="10.0.0.0/24")
165
        network2 = mfactory.NetworkFactory(subnet="192.168.2.0/24")
166
        vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
167
                                             deleted=False,
168
                                             operstate="STOPPED")
169
        mfactory.NetworkInterfaceFactory(machine=vm1, network=network1,
170
                                         ipv4="10.0.0.0")
171
        mrapi().GetInstances.return_value =\
172
            [{"name": vm1.backend_vm_id,
173
             "beparams": {"maxmem": 2048,
174
                          "minmem": 2048,
175
                          "vcpus": 4},
176
             "oper_state": True,
177
             "mtime": time(),
178
             "disk.sizes": [],
179
             "nic.ips": ["192.168.2.1"],
180
             "nic.macs": ["aa:00:bb:cc:dd:ee"],
181
             "nic.networks": [network2.backend_id],
182
             "tags": []}]
183
        with mocked_quotaholder():
184
            self.reconciler.reconcile()
185
        vm1 = VirtualMachine.objects.get(id=vm1.id)
186
        self.assertEqual(vm1.operstate, "STARTED")
187
        nic = vm1.nics.all()[0]
188
        self.assertEqual(nic.network, network2)
189
        self.assertEqual(nic.ipv4, "192.168.2.1")
190
        self.assertEqual(nic.mac, "aa:00:bb:cc:dd:ee")
191

    
192

    
193
@patch("synnefo.logic.rapi_pool.GanetiRapiClient")
194
class NetworkReconciliationTest(TestCase):
195
    def setUp(self):
196
        self.backend = mfactory.BackendFactory()
197
        log = logging.getLogger()
198
        self.reconciler = reconciliation.NetworkReconciler(
199
            logger=log,
200
            fix=True)
201

    
202
    def test_parted_network(self, mrapi):
203
        net1 = mfactory.NetworkFactory(subnet="192.168.0.0/30", public=False)
204
        mrapi().GetNetworks.return_value = []
205
        # Test nothing if Ganeti returns nothing
206
        self.assertEqual(net1.backend_networks.count(), 0)
207
        self.reconciler.reconcile_networks()
208
        self.assertEqual(net1.backend_networks.count(), 0)
209

    
210
        # Test creation if exists in Ganeti
211
        self.assertEqual(net1.backend_networks.count(), 0)
212
        mrapi().GetNetworks.return_value = [{"name": net1.backend_id,
213
                                             "group_list": ["default"],
214
                                             "network": net1.subnet,
215
                                             "map": "....",
216
                                             "external_reservations": ""}]
217
        self.reconciler.reconcile_networks()
218
        self.assertTrue(net1.backend_networks
219
                            .filter(backend=self.backend).exists())
220
        # ..but not if it is destroying
221
        net1.backend_networks.all().delete()
222
        net1.action = "DESTROY"
223
        net1.save()
224
        self.reconciler.reconcile_networks()
225
        self.assertFalse(net1.backend_networks
226
                             .filter(backend=self.backend).exists())
227
        # or network is public!
228
        net1.action = "CREATE"
229
        net1.public = True
230
        net1.save()
231
        self.reconciler.reconcile_networks()
232
        self.assertFalse(net1.backend_networks
233
                             .filter(backend=self.backend).exists())
234
        # Test creation if network is a floating IP pool
235
        net2 = mfactory.NetworkFactory(subnet="192.168.0.0/30",
236
                                       floating_ip_pool=True)
237
        mrapi().GetNetworks.return_value = []
238
        self.assertEqual(net2.backend_networks.count(), 0)
239
        self.reconciler.reconcile_networks()
240
        self.assertTrue(net2.backend_networks
241
                            .filter(backend=self.backend).exists())
242

    
243
    def test_stale_network(self, mrapi):
244
        # Test that stale network will be deleted from DB, if network action is
245
        # destroy
246
        net1 = mfactory.NetworkFactory(subnet="192.168.0.0/30", public=False,
247
                                       flavor="IP_LESS_ROUTED",
248
                                       action="DESTROY", deleted=False)
249
        bn1 = mfactory.BackendNetworkFactory(network=net1,
250
                                             backend=self.backend,
251
                                             operstate="ACTIVE")
252
        mrapi().GetNetworks.return_value = []
253
        self.assertFalse(net1.deleted)
254
        with mocked_quotaholder():
255
            self.reconciler.reconcile_networks()
256
        bn1 = BackendNetwork.objects.get(id=bn1.id)
257
        net1 = Network.objects.get(id=net1.id)
258
        self.assertEqual(bn1.operstate, "DELETED")
259
        self.assertTrue(net1.deleted)
260
        # But not if action is not DESTROY
261
        net2 = mfactory.NetworkFactory(subnet="192.168.0.0/30", public=False,
262
                                       action="CREATE")
263
        mfactory.BackendNetworkFactory(network=net2, backend=self.backend)
264
        self.assertFalse(net2.deleted)
265
        self.reconciler.reconcile_networks()
266
        self.assertFalse(net2.deleted)
267

    
268
    def test_missing_network(self, mrapi):
269
        net2 = mfactory.NetworkFactory(subnet="192.168.0.0/30", public=False,
270
                                       action="CREATE")
271
        mfactory.BackendNetworkFactory(network=net2, backend=self.backend)
272
        mrapi().GetNetworks.return_value = []
273
        self.reconciler.reconcile_networks()
274
        self.assertEqual(len(mrapi().CreateNetwork.mock_calls), 1)
275

    
276
    #def test_hanging_networks(self, mrapi):
277
    #    pass
278

    
279
    def test_unsynced_networks(self, mrapi):
280
        net = mfactory.NetworkFactory(subnet="192.168.0.0/30", public=False,
281
                                      state="PENDING",
282
                                      action="CREATE", deleted=False)
283
        bn = mfactory.BackendNetworkFactory(network=net, backend=self.backend,
284
                                            operstate="PENDING")
285
        mrapi().GetNetworks.return_value = [{"name": net.backend_id,
286
                                             "group_list": [],
287
                                             "network": net.subnet,
288
                                             "map": "....",
289
                                             "external_reservations": ""}]
290
        self.assertEqual(bn.operstate, "PENDING")
291
        self.reconciler.reconcile_networks()
292
        bn = BackendNetwork.objects.get(id=bn.id)
293
        self.assertEqual(bn.operstate, "ACTIVE")
294

    
295
    def test_orphan_networks(self, mrapi):
296
        net = mfactory.NetworkFactory(subnet="192.168.0.0/30", public=False,
297
                                      action="CREATE", deleted=True)
298
        mrapi().GetNetworks.return_value = [{"name": net.backend_id,
299
                                             "group_list": [],
300
                                             "network": net.subnet,
301
                                             "map": "....",
302
                                             "external_reservations": ""}]
303
        self.reconciler.reconcile_networks()
304
        mrapi().DeleteNetwork.assert_called_once_with(net.backend_id, [])