Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / tests.py @ 52194c77

History | View | Annotate | Download (22.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

    
31
# Provides automated tests for logic module
32

    
33
from random import randint
34

    
35
from django.test import TestCase
36

    
37
from synnefo.db.models import *
38
from synnefo.db import models_factory as mfactory
39
from synnefo.logic import reconciliation
40
from synnefo.lib.utils import split_time
41
from datetime import datetime
42
from mock import patch
43
from synnefo.api.util import allocate_resource
44
from synnefo.logic.callbacks import (update_db, update_net, update_network,
45
                                    update_build_progress)
46

    
47
now = datetime.now
48
from time import time
49
import json
50

    
51
## Test Callbacks
52

    
53

    
54
@patch('synnefo.lib.amqp.AMQPClient')
55
class UpdateDBTest(TestCase):
56
    def create_msg(self, **kwargs):
57
        """Create snf-ganeti-eventd message"""
58
        msg = {'event_time': split_time(time())}
59
        msg['type'] = 'ganeti-op-status'
60
        msg['status'] = 'success'
61
        msg['jobId'] = 1
62
        msg['logmsg'] = 'Dummy Log'
63
        for key, val in kwargs.items():
64
            msg[key] = val
65
        message = {'body': json.dumps(msg)}
66
        return message
67

    
68
    def test_missing_attribute(self, client):
69
        update_db(client, json.dumps({'body': {}}))
70
        client.basic_nack.assert_called_once()
71

    
72
    def test_unhandled_exception(self, client):
73
        update_db(client, {})
74
        client.basic_reject.assert_called_once()
75

    
76
    def test_missing_instance(self, client):
77
        msg = self.create_msg(operation='OP_INSTANCE_STARTUP',
78
                              instance='foo')
79
        update_db(client, msg)
80
        client.basic_nack.assert_called_once()
81

    
82
    def test_wrong_type(self, client):
83
        msg = self.create_msg(type="WRONG_TYPE")
84
        update_db(client, msg)
85
        client.basic_ack.assert_called_once()
86

    
87
    def test_start(self, client):
88
        vm = mfactory.VirtualMachineFactory()
89
        msg = self.create_msg(operation='OP_INSTANCE_STARTUP',
90
                              instance=vm.backend_vm_id)
91
        update_db(client, msg)
92
        client.basic_ack.assert_called_once()
93
        db_vm = VirtualMachine.objects.get(id=vm.id)
94
        self.assertEqual(db_vm.operstate, 'STARTED')
95

    
96
    def test_stop(self, client):
97
        vm = mfactory.VirtualMachineFactory()
98
        msg = self.create_msg(operation='OP_INSTANCE_SHUTDOWN',
99
                              instance=vm.backend_vm_id)
100
        update_db(client, msg)
101
        client.basic_ack.assert_called_once()
102
        db_vm = VirtualMachine.objects.get(id=vm.id)
103
        self.assertEqual(db_vm.operstate, 'STOPPED')
104

    
105
    def test_reboot(self, client):
106
        vm = mfactory.VirtualMachineFactory()
107
        msg = self.create_msg(operation='OP_INSTANCE_REBOOT',
108
                              instance=vm.backend_vm_id)
109
        update_db(client, msg)
110
        client.basic_ack.assert_called_once()
111
        db_vm = VirtualMachine.objects.get(id=vm.id)
112
        self.assertEqual(db_vm.operstate, 'STARTED')
113

    
114
    def test_remove(self, client):
115
        vm = mfactory.VirtualMachineFactory()
116
        # Also create a NIC
117
        mfactory.NetworkInterfaceFactory(machine=vm)
118
        msg = self.create_msg(operation='OP_INSTANCE_REMOVE',
119
                              instance=vm.backend_vm_id)
120
        update_db(client, msg)
121
        client.basic_ack.assert_called_once()
122
        db_vm = VirtualMachine.objects.get(id=vm.id)
123
        self.assertEqual(db_vm.operstate, 'DESTROYED')
124
        self.assertTrue(db_vm.deleted)
125
        # Check that nics are deleted
126
        self.assertFalse(db_vm.nics.all())
127

    
128
    def test_create(self, client):
129
        vm = mfactory.VirtualMachineFactory()
130
        msg = self.create_msg(operation='OP_INSTANCE_CREATE',
131
                              instance=vm.backend_vm_id)
132
        update_db(client, msg)
133
        client.basic_ack.assert_called_once()
134
        db_vm = VirtualMachine.objects.get(id=vm.id)
135
        self.assertEqual(db_vm.operstate, 'STARTED')
136

    
137
    def test_create_error(self, client):
138
        """Test that error create sets vm to ERROR state"""
139
        vm = mfactory.VirtualMachineFactory()
140
        msg = self.create_msg(operation='OP_INSTANCE_CREATE',
141
                              instance=vm.backend_vm_id,
142
                              status='error')
143
        update_db(client, msg)
144
        client.basic_ack.assert_called_once()
145
        db_vm = VirtualMachine.objects.get(id=vm.id)
146
        self.assertEqual(db_vm.operstate, 'ERROR')
147

    
148
    def test_remove_from_error(self, client):
149
        """Test that error removes delete error builds"""
150
        vm = mfactory.VirtualMachineFactory(operstate='ERROR')
151
        # Also create a NIC
152
        mfactory.NetworkInterfaceFactory(machine=vm)
153
        msg = self.create_msg(operation='OP_INSTANCE_REMOVE',
154
                              instance=vm.backend_vm_id)
155
        update_db(client, msg)
156
        client.basic_ack.assert_called_once()
157
        db_vm = VirtualMachine.objects.get(id=vm.id)
158
        self.assertEqual(db_vm.operstate, 'DESTROYED')
159
        self.assertTrue(db_vm.deleted)
160
        # Check that nics are deleted
161
        self.assertFalse(db_vm.nics.all())
162

    
163
    def test_other_error(self, client):
164
        """Test that other error messages do no affect the VM"""
165
        vm = mfactory.VirtualMachineFactory()
166
        msg = self.create_msg(operation='OP_INSTANCE_STARTUP',
167
                              instance=vm.backend_vm_id,
168
                              status='error')
169
        update_db(client, msg)
170
        client.basic_ack.assert_called_once()
171
        db_vm = VirtualMachine.objects.get(id=vm.id)
172
        self.assertEqual(db_vm.operstate, vm.operstate)
173
        self.assertEqual(db_vm.backendtime, vm.backendtime)
174

    
175

    
176
@patch('synnefo.lib.amqp.AMQPClient')
177
class UpdateNetTest(TestCase):
178
    def create_msg(self, **kwargs):
179
        """Create snf-ganeti-hook message"""
180
        msg = {'event_time': split_time(time())}
181
        msg['type'] = 'ganeti-net-status'
182
        msg['status'] = 'success'
183
        msg['jobId'] = 1
184
        msg['logmsg'] = 'Dummy Log'
185
        for key, val in kwargs.items():
186
            msg[key] = val
187
        message = {'body': json.dumps(msg)}
188
        return message
189

    
190
    def test_missing_attribute(self, client):
191
        update_net(client, json.dumps({'body': {}}))
192
        client.basic_nack.assert_called_once()
193

    
194
    def test_unhandled_exception(self, client):
195
        update_net(client, {})
196
        client.basic_reject.assert_called_once()
197

    
198
    def test_wrong_type(self, client):
199
        msg = self.create_msg(type="WRONG_TYPE")
200
        update_net(client, msg)
201
        client.basic_ack.assert_called_once()
202

    
203
    def test_missing_instance(self, client):
204
        msg = self.create_msg(operation='OP_INSTANCE_STARTUP',
205
                              instance='foo')
206
        update_net(client, msg)
207
        client.basic_nack.assert_called_once()
208

    
209
    def test_no_nics(self, client):
210
        vm = mfactory.VirtualMachineFactory(operstate='ERROR')
211
        mfactory.NetworkInterfaceFactory(machine=vm)
212
        mfactory.NetworkInterfaceFactory(machine=vm)
213
        mfactory.NetworkInterfaceFactory(machine=vm)
214
        self.assertEqual(len(vm.nics.all()), 3)
215
        msg = self.create_msg(nics=[],
216
                              instance=vm.backend_vm_id)
217
        update_net(client, msg)
218
        client.basic_ack.assert_called_once()
219
        db_vm = VirtualMachine.objects.get(id=vm.id)
220
        self.assertEqual(len(db_vm.nics.all()), 0)
221

    
222
    def test_empty_nic(self, client):
223
        vm = mfactory.VirtualMachineFactory(operstate='ERROR')
224
        for public in [True, False]:
225
            net = mfactory.NetworkFactory(public=public)
226
            msg = self.create_msg(nics=[{'network':net.backend_id}],
227
                                  instance=vm.backend_vm_id)
228
            update_net(client, msg)
229
            client.basic_ack.assert_called_once()
230
            db_vm = VirtualMachine.objects.get(id=vm.id)
231
            nics = db_vm.nics.all()
232
            self.assertEqual(len(nics), 1)
233
            self.assertEqual(nics[0].index, 0)
234
            self.assertEqual(nics[0].ipv4, '')
235
            self.assertEqual(nics[0].ipv6, '')
236
            self.assertEqual(nics[0].mac, '')
237
            if public:
238
                self.assertEqual(nics[0].firewall_profile,
239
                                 settings.DEFAULT_FIREWALL_PROFILE)
240
            else:
241
                self.assertEqual(nics[0].firewall_profile, '')
242

    
243
    def test_full_nic(self, client):
244
        vm = mfactory.VirtualMachineFactory(operstate='ERROR')
245
        net = mfactory.NetworkFactory(subnet='10.0.0.0/24')
246
        pool = net.get_pool()
247
        self.assertTrue(pool.is_available('10.0.0.22'))
248
        pool.save()
249
        msg = self.create_msg(nics=[{'network':net.backend_id,
250
                                     'ip': '10.0.0.22',
251
                                     'mac': 'aa:bb:cc:00:11:22'}],
252
                              instance=vm.backend_vm_id)
253
        update_net(client, msg)
254
        client.basic_ack.assert_called_once()
255
        db_vm = VirtualMachine.objects.get(id=vm.id)
256
        nics = db_vm.nics.all()
257
        self.assertEqual(len(nics), 1)
258
        self.assertEqual(nics[0].index, 0)
259
        self.assertEqual(nics[0].ipv4, '10.0.0.22')
260
        self.assertEqual(nics[0].ipv6, '')
261
        self.assertEqual(nics[0].mac, 'aa:bb:cc:00:11:22')
262
        pool = net.get_pool()
263
        self.assertFalse(pool.is_available('10.0.0.22'))
264
        pool.save()
265

    
266

    
267
@patch('synnefo.lib.amqp.AMQPClient')
268
class UpdateNetworkTest(TestCase):
269
    def create_msg(self, **kwargs):
270
        """Create snf-ganeti-eventd message"""
271
        msg = {'event_time': split_time(time())}
272
        msg['type'] = 'ganeti-network-status'
273
        msg['status'] = 'success'
274
        msg['jobId'] = 1
275
        msg['logmsg'] = 'Dummy Log'
276
        for key, val in kwargs.items():
277
            msg[key] = val
278
        message = {'body': json.dumps(msg)}
279
        return message
280

    
281
    def test_missing_attribute(self, client):
282
        update_network(client, json.dumps({'body': {}}))
283
        client.basic_nack.assert_called_once()
284

    
285
    def test_unhandled_exception(self, client):
286
        update_network(client, {})
287
        client.basic_reject.assert_called_once()
288

    
289
    def test_wrong_type(self, client):
290
        msg = self.create_msg(type="WRONG_TYPE")
291
        update_network(client, msg)
292
        client.basic_ack.assert_called_once()
293

    
294
    def test_missing_network(self, client):
295
        msg = self.create_msg(operation='OP_NETWORK_CREATE',
296
                              network='foo')
297
        update_network(client, msg)
298
        client.basic_nack.assert_called_once()
299

    
300
    def test_create(self, client):
301
        back_network = mfactory.BackendNetworkFactory(operstate='PENDING')
302
        net = back_network.network
303
        back1 = back_network.backend
304

    
305
        back_network2 = mfactory.BackendNetworkFactory(operstate='PENDING',
306
                                                       network=net)
307
        back2 = back_network2.backend
308
        # Message from first backend network
309
        msg = self.create_msg(operation='OP_NETWORK_CONNECT',
310
                              network=net.backend_id,
311
                              cluster=back1.clustername)
312
        update_network(client, msg)
313
        client.basic_ack.assert_called_once()
314

    
315
        back_net = BackendNetwork.objects.get(id=back_network.id)
316
        self.assertEqual(back_net.operstate, 'ACTIVE')
317
        db_net = Network.objects.get(id=net.id)
318
        self.assertEqual(db_net.state, 'PENDING')
319
        # msg from second backend network
320
        msg = self.create_msg(operation='OP_NETWORK_CONNECT',
321
                              network=net.backend_id,
322
                              cluster=back2.clustername)
323
        update_network(client, msg)
324
        client.basic_ack.assert_called_once()
325

    
326
        db_net = Network.objects.get(id=net.id)
327
        self.assertEqual(db_net.state, 'ACTIVE')
328
        back_net = BackendNetwork.objects.get(id=back_network.id)
329
        self.assertEqual(back_net.operstate, 'ACTIVE')
330

    
331
    def test_disconnect(self, client):
332
        bn1 = mfactory.BackendNetworkFactory(operstate='ACTIVE')
333
        net1 = bn1.network
334
        net1.operstate = 'ACTIVE'
335
        net1.save()
336
        bn2 = mfactory.BackendNetworkFactory(operstate='ACTIVE',
337
                                             network=net1)
338
        msg = self.create_msg(operation='OP_NETWORK_DISCONNECT',
339
                              network=net1.backend_id,
340
                              cluster=bn2.backend.clustername)
341
        update_network(client, msg)
342
        client.basic_ack.assert_called_once()
343
        self.assertEqual(Network.objects.get(id=net1.id).state, 'PENDING')
344
        self.assertEqual(BackendNetwork.objects.get(id=bn2.id).operstate,
345
                        'PENDING')
346

    
347
    def test_remove(self, client):
348
        mfactory.MacPrefixPoolTableFactory()
349
        mfactory.BridgePoolTableFactory()
350
        bn = mfactory.BackendNetworkFactory(operstate='ACTIVE')
351
        for old_state in ['success', 'canceled', 'error']:
352
            for flavor in Network.FLAVORS.keys():
353
                bn.operstate = old_state
354
                bn.save()
355
                net = bn.network
356
                net.state = 'ACTIVE'
357
                net.flavor = flavor
358
                if flavor == 'PHYSICAL_VLAN':
359
                    net.link = allocate_resource('bridge')
360
                if flavor == 'MAC_FILTERED':
361
                    net.mac_prefix = allocate_resource('mac_prefix')
362
                net.save()
363
                msg = self.create_msg(operation='OP_NETWORK_REMOVE',
364
                                      network=net.backend_id,
365
                                      cluster=bn.backend.clustername)
366
                update_network(client, msg)
367
                client.basic_ack.assert_called_once()
368
                db_bnet = BackendNetwork.objects.get(id=bn.id)
369
                self.assertEqual(db_bnet.operstate,
370
                                'DELETED')
371
                db_net = Network.objects.get(id=net.id)
372
                self.assertEqual(db_net.state, 'DELETED', flavor)
373
                self.assertTrue(db_net.deleted)
374
                if flavor == 'PHYSICAL_VLAN':
375
                    pool = BridgePoolTable.get_pool()
376
                    self.assertTrue(pool.is_available(net.link))
377
                if flavor == 'MAC_FILTERED':
378
                    pool = MacPrefixPoolTable.get_pool()
379
                    self.assertTrue(pool.is_available(net.mac_prefix))
380

    
381
    def test_error_opcode(self, client):
382
        for state, _ in Network.OPER_STATES:
383
            bn = mfactory.BackendNetworkFactory()
384
            bn.operstate = state
385
            bn.save()
386
            network = bn.network
387
            network.state = state
388
            network.save()
389
            for opcode, _ in BackendNetwork.BACKEND_OPCODES:
390
                if opcode in ['OP_NETWORK_REMOVE', 'OP_NETWORK_ADD']:
391
                    continue
392
                msg = self.create_msg(operation=opcode,
393
                                      network=bn.network.backend_id,
394
                                      status='error',
395
                                      cluster=bn.backend.clustername)
396
                update_network(client, msg)
397
                client.basic_ack.assert_called_once()
398
                db_bnet = BackendNetwork.objects.get(id=bn.id)
399
                self.assertEqual(bn.operstate, db_bnet.operstate)
400
                self.assertEqual(bn.network.state, db_bnet.network.state)
401

    
402
    def test_ips(self, client):
403
        network = mfactory.NetworkFactory(subnet='10.0.0.0/24')
404
        bn = mfactory.BackendNetworkFactory(network=network)
405
        msg = self.create_msg(operation='OP_NETWORK_SET_PARAMS',
406
                              network=network.backend_id,
407
                              cluster=bn.backend.clustername,
408
                              status='success',
409
                              add_reserved_ips=['10.0.0.10', '10.0.0.20'],
410
                              remove_reserved_ips=[])
411
        update_network(client, msg)
412
        client.basic_ack.assert_called_once()
413
        pool = network.get_pool()
414
        self.assertTrue(pool.is_reserved('10.0.0.10'))
415
        self.assertTrue(pool.is_reserved('10.0.0.20'))
416
        pool.save()
417
        # Release them
418
        msg = self.create_msg(operation='OP_NETWORK_SET_PARAMS',
419
                              network=network.backend_id,
420
                              cluster=bn.backend.clustername,
421
                              add_reserved_ips=[],
422
                              remove_reserved_ips=['10.0.0.10', '10.0.0.20'])
423
        update_network(client, msg)
424
        client.basic_ack.assert_called_once()
425
        pool = network.get_pool()
426
        self.assertFalse(pool.is_reserved('10.0.0.10'))
427
        self.assertFalse(pool.is_reserved('10.0.0.20'))
428

    
429

    
430
@patch('synnefo.lib.amqp.AMQPClient')
431
class UpdateBuildProgressTest(TestCase):
432
    def setUp(self):
433
        self.vm = mfactory.VirtualMachineFactory()
434

    
435
    def get_db_vm(self):
436
        return VirtualMachine.objects.get(id=self.vm.id)
437

    
438
    def create_msg(self, **kwargs):
439
        """Create snf-progress-monitor message"""
440
        msg = {'event_time': split_time(time())}
441
        msg['type'] = 'image-copy-progress'
442
        msg['progress'] = 0
443
        for key, val in kwargs.items():
444
            msg[key] = val
445
        message = {'body': json.dumps(msg)}
446
        return message
447

    
448
    def test_missing_attribute(self, client):
449
        update_build_progress(client, json.dumps({'body': {}}))
450
        client.basic_nack.assert_called_once()
451

    
452
    def test_unhandled_exception(self, client):
453
        update_build_progress(client, {})
454
        client.basic_reject.assert_called_once()
455

    
456
    def test_missing_instance(self, client):
457
        msg = self.create_msg(instance='foo')
458
        update_build_progress(client, msg)
459
        client.basic_nack.assert_called_once()
460

    
461
    def test_wrong_type(self, client):
462
        msg = self.create_msg(type="WRONG_TYPE")
463
        update_build_progress(client, msg)
464
        client.basic_ack.assert_called_once()
465

    
466
    def test_progress_update(self, client):
467
        rprogress = randint(10, 100)
468
        msg = self.create_msg(progress=rprogress,
469
                              instance=self.vm.backend_vm_id)
470
        update_build_progress(client, msg)
471
        client.basic_ack.assert_called_once()
472
        vm = self.get_db_vm()
473
        self.assertEqual(vm.buildpercentage, rprogress)
474

    
475
    def test_invalid_value(self, client):
476
        old = self.vm.buildpercentage
477
        for rprogress in [0, -1, 'a']:
478
            msg = self.create_msg(progress=rprogress,
479
                                  instance=self.vm.backend_vm_id)
480
            update_build_progress(client, msg)
481
            client.basic_ack.assert_called_once()
482
            vm = self.get_db_vm()
483
            self.assertEqual(vm.buildpercentage, old)
484

    
485

    
486
class ReconciliationTest(TestCase):
487
    SERVERS = 1000
488
    fixtures = ['db_test_data']
489

    
490
    def test_get_servers_from_db(self):
491
        """Test getting a dictionary from each server to its operstate"""
492
        self.assertEquals(reconciliation.get_servers_from_db(),
493
                          {30000: 'STARTED', 30001: 'STOPPED', 30002: 'BUILD'})
494

    
495
    def test_stale_servers_in_db(self):
496
        """Test discovery of stale entries in DB"""
497

    
498
        D = {1: 'STARTED', 2: 'STOPPED', 3: 'STARTED', 30000: 'BUILD',
499
             30002: 'STOPPED'}
500
        G = {1: True, 3: True, 30000: True}
501
        self.assertEquals(reconciliation.stale_servers_in_db(D, G),
502
                          set([2, 30002]))
503

    
504
    @patch("synnefo.db.models.get_rapi_client")
505
    def test_stale_building_vm(self, client):
506
        vm = mfactory.VirtualMachineFactory()
507
        vm.state = 'BUILD'
508
        vm.backendjobid = 42
509
        vm.save()
510
        D = {vm.id: 'BUILD'}
511
        G = {}
512
        for status in ['queued', 'waiting', 'running']:
513
            client.return_value.GetJobStatus.return_value = {'status': status}
514
            self.assertEqual(reconciliation.stale_servers_in_db(D, G), set([]))
515
            client.return_value.GetJobStatus.assert_called_once_with(vm.backendjobid)
516
            client.reset_mock()
517
        for status in ['success', 'error', 'canceled']:
518
            client.return_value.GetJobStatus.return_value = {'status': status}
519
            self.assertEqual(reconciliation.stale_servers_in_db(D, G), set([]))
520
            client.return_value.GetInstance.assert_called_once_with(vm.backend_vm_id)
521
            client.return_value.GetJobStatus.assert_called_once_with(vm.backendjobid)
522
            client.reset_mock()
523
        from synnefo.logic.rapi import GanetiApiError
524
        client.return_value.GetJobStatus.side_effect = GanetiApiError('Foo')
525
        self.assertEqual(reconciliation.stale_servers_in_db(D, G), set([vm.id]))
526

    
527
    def test_orphan_instances_in_ganeti(self):
528
        """Test discovery of orphan instances in Ganeti, without a DB entry"""
529

    
530
        G = {1: True, 2: False, 3: False, 4: True, 50: True}
531
        D = {1: True, 3: False}
532
        self.assertEquals(reconciliation.orphan_instances_in_ganeti(D, G),
533
                          set([2, 4, 50]))
534

    
535
    def test_unsynced_operstate(self):
536
        """Test discovery of unsynced operstate between the DB and Ganeti"""
537

    
538
        G = {1: True, 2: False, 3: True, 4: False, 50: True}
539
        D = {1: 'STARTED', 2: 'STARTED', 3: 'BUILD', 4: 'STARTED', 50: 'BUILD'}
540
        self.assertEquals(reconciliation.unsynced_operstate(D, G),
541
                          set([(2, 'STARTED', False),
542
                           (3, 'BUILD', True), (4, 'STARTED', False),
543
                           (50, 'BUILD', True)]))