Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / tests.py @ 0c50c760

History | View | Annotate | Download (36.3 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 snf_django.lib.api import faults
38
from synnefo.db.models import *
39
from synnefo.db import models_factory as mfactory
40
from synnefo.logic import reconciliation, servers
41
from synnefo.lib.utils import split_time
42
from datetime import datetime
43
from mock import patch
44
from synnefo.api.util import allocate_resource
45
from synnefo.logic.callbacks import (update_db, update_network,
46
                                     update_build_progress)
47
from snf_django.utils.testing import mocked_quotaholder
48

    
49
now = datetime.now
50
from time import time
51
import json
52

    
53

    
54
@patch("synnefo.logic.rapi_pool.GanetiRapiClient")
55
class ServerCommandTest(TestCase):
56
    def test_pending_task(self, mrapi):
57
        vm = mfactory.VirtualMachineFactory(task="REBOOT", task_job_id=1)
58
        self.assertRaises(faults.BadRequest, servers.start, vm)
59
        vm = mfactory.VirtualMachineFactory(task="BUILD", task_job_id=1)
60
        self.assertRaises(faults.BuildInProgress, servers.start, vm)
61
        # Assert always succeeds
62
        vm = mfactory.VirtualMachineFactory(task="BUILD", task_job_id=1)
63
        mrapi().DeleteInstance.return_value = 1
64
        with mocked_quotaholder():
65
            servers.destroy(vm)
66
        vm = mfactory.VirtualMachineFactory(task="REBOOT", task_job_id=1)
67
        with mocked_quotaholder():
68
            servers.destroy(vm)
69

    
70
    def test_deleted_vm(self, mrapi):
71
        vm = mfactory.VirtualMachineFactory(deleted=True)
72
        self.assertRaises(faults.BadRequest, servers.start, vm)
73

    
74
    def test_invalid_operstate_for_action(self, mrapi):
75
        vm = mfactory.VirtualMachineFactory(operstate="STARTED")
76
        self.assertRaises(faults.BadRequest, servers.start, vm)
77
        vm = mfactory.VirtualMachineFactory(operstate="STOPPED")
78
        self.assertRaises(faults.BadRequest, servers.stop, vm)
79
        vm = mfactory.VirtualMachineFactory(operstate="STARTED")
80
        self.assertRaises(faults.BadRequest, servers.resize, vm)
81
        vm = mfactory.VirtualMachineFactory(operstate="STOPPED")
82
        self.assertRaises(faults.BadRequest, servers.stop, vm)
83
        #test valid
84
        mrapi().StartupInstance.return_value = 1
85
        with mocked_quotaholder():
86
            servers.start(vm)
87
        vm.task = None
88
        vm.task_job_id = None
89
        vm.save()
90
        mrapi().RebootInstance.return_value = 1
91
        with mocked_quotaholder():
92
            servers.reboot(vm, "HARD")
93

    
94
    def test_commission(self, mrapi):
95
        vm = mfactory.VirtualMachineFactory(operstate="STOPPED")
96
        # Still pending
97
        vm.serial = mfactory.QuotaHolderSerialFactory(serial=200,
98
                                                      resolved=False,
99
                                                      pending=True)
100
        serial = vm.serial
101
        mrapi().StartupInstance.return_value = 1
102
        with mocked_quotaholder() as m:
103
            servers.start(vm)
104
            m.resolve_commissions.assert_called_once_with('', [],
105
                                                          [serial.serial])
106
            self.assertTrue(m.issue_one_commission.called)
107
        # Not pending, rejct
108
        vm.task = None
109
        vm.serial = mfactory.QuotaHolderSerialFactory(serial=400,
110
                                                      resolved=False,
111
                                                      pending=False,
112
                                                      accept=False)
113
        serial = vm.serial
114
        mrapi().StartupInstance.return_value = 1
115
        with mocked_quotaholder() as m:
116
            servers.start(vm)
117
            m.resolve_commissions.assert_called_once_with('', [],
118
                                                          [serial.serial])
119
            self.assertTrue(m.issue_one_commission.called)
120
        # Not pending, accept
121
        vm.task = None
122
        vm.serial = mfactory.QuotaHolderSerialFactory(serial=600,
123
                                                      resolved=False,
124
                                                      pending=False,
125
                                                      accept=True)
126
        serial = vm.serial
127
        mrapi().StartupInstance.return_value = 1
128
        with mocked_quotaholder() as m:
129
            servers.start(vm)
130
            m.resolve_commissions.assert_called_once_with('', [serial.serial],
131
                                                          [])
132
            self.assertTrue(m.issue_one_commission.called)
133

    
134
        mrapi().StartupInstance.side_effect = ValueError
135
        vm.task = None
136
        vm.serial = None
137
        # Test reject if Ganeti erro
138
        with mocked_quotaholder() as m:
139
            try:
140
                servers.start(vm)
141
            except:
142
                m.resolve_commissions.assert_called_once_with('', [],
143
                                                            [vm.serial.serial])
144

    
145
    def test_task_after(self, mrapi):
146
        return
147
        vm = mfactory.VirtualMachineFactory()
148
        mrapi().StartupInstance.return_value = 1
149
        mrapi().ShutdownInstance.return_value = 2
150
        mrapi().RebootInstance.return_value = 2
151
        with mocked_quotaholder() as m:
152
            vm.task = None
153
            vm.operstate = "STOPPED"
154
            servers.start(vm)
155
            self.assertEqual(vm.task, "START")
156
            self.assertEqual(vm.task_job_id, 1)
157
        with mocked_quotaholder() as m:
158
            vm.task = None
159
            vm.operstate = "STARTED"
160
            servers.stop(vm)
161
            self.assertEqual(vm.task, "STOP")
162
            self.assertEqual(vm.task_job_id, 2)
163
        with mocked_quotaholder() as m:
164
            vm.task = None
165
            servers.reboot(vm)
166
            self.assertEqual(vm.task, "REBOOT")
167
            self.assertEqual(vm.task_job_id, 3)
168

    
169

    
170

    
171
## Test Callbacks
172

    
173

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

    
188
    def test_missing_attribute(self, client):
189
        update_db(client, json.dumps({'body': {}}))
190
        self.assertTrue(client.basic_reject.called)
191

    
192
    def test_unhandled_exception(self, client):
193
        update_db(client, {})
194
        client.basic_reject.assert_called_once()
195

    
196
    def test_missing_instance(self, client):
197
        msg = self.create_msg(operation='OP_INSTANCE_STARTUP',
198
                              instance='foo')
199
        update_db(client, msg)
200
        self.assertTrue(client.basic_ack.called)
201

    
202
    def test_wrong_type(self, client):
203
        msg = self.create_msg(type="WRONG_TYPE")
204
        update_db(client, msg)
205
        self.assertTrue(client.basic_nack.called)
206

    
207
    def test_old_msg(self, client):
208
        from time import sleep
209
        from datetime import datetime
210
        old_time = time()
211
        sleep(0.01)
212
        new_time = datetime.fromtimestamp(time())
213
        vm = mfactory.VirtualMachineFactory(backendtime=new_time)
214
        vm.operstate = 'STOPPED'
215
        vm.save()
216
        msg = self.create_msg(operation='OP_INSTANCE_STARTUP',
217
                              event_time=split_time(old_time),
218
                              instance=vm.backend_vm_id)
219
        update_db(client, msg)
220
        self.assertTrue(client.basic_ack.called)
221
        db_vm = VirtualMachine.objects.get(id=vm.id)
222
        self.assertEquals(db_vm.operstate, "STOPPED")
223
        self.assertEquals(db_vm.backendtime, new_time)
224

    
225
    def test_start(self, client):
226
        vm = mfactory.VirtualMachineFactory()
227
        msg = self.create_msg(operation='OP_INSTANCE_STARTUP',
228
                              instance=vm.backend_vm_id)
229
        with mocked_quotaholder():
230
            update_db(client, msg)
231
        self.assertTrue(client.basic_ack.called)
232
        db_vm = VirtualMachine.objects.get(id=vm.id)
233
        self.assertEqual(db_vm.operstate, 'STARTED')
234

    
235
    def test_stop(self, client):
236
        vm = mfactory.VirtualMachineFactory()
237
        msg = self.create_msg(operation='OP_INSTANCE_SHUTDOWN',
238
                              instance=vm.backend_vm_id)
239
        with mocked_quotaholder():
240
            update_db(client, msg)
241
        self.assertTrue(client.basic_ack.called)
242
        db_vm = VirtualMachine.objects.get(id=vm.id)
243
        self.assertEqual(db_vm.operstate, 'STOPPED')
244

    
245
    def test_reboot(self, client):
246
        vm = mfactory.VirtualMachineFactory()
247
        msg = self.create_msg(operation='OP_INSTANCE_REBOOT',
248
                              instance=vm.backend_vm_id)
249
        update_db(client, msg)
250
        self.assertTrue(client.basic_ack.called)
251
        db_vm = VirtualMachine.objects.get(id=vm.id)
252
        self.assertEqual(db_vm.operstate, 'STARTED')
253

    
254
    def test_remove(self, client):
255
        vm = mfactory.VirtualMachineFactory()
256
        # Also create a NIC
257
        nic = mfactory.NetworkInterfaceFactory(machine=vm)
258
        nic.network.get_pool().reserve(nic.ipv4)
259
        msg = self.create_msg(operation='OP_INSTANCE_REMOVE',
260
                              instance=vm.backend_vm_id)
261
        with mocked_quotaholder():
262
            update_db(client, msg)
263
        self.assertTrue(client.basic_ack.called)
264
        db_vm = VirtualMachine.objects.get(id=vm.id)
265
        self.assertEqual(db_vm.operstate, 'DESTROYED')
266
        self.assertTrue(db_vm.deleted)
267
        # Check that nics are deleted
268
        self.assertFalse(db_vm.nics.all())
269
        self.assertTrue(nic.network.get_pool().is_available(nic.ipv4))
270
        vm2 = mfactory.VirtualMachineFactory()
271
        network = mfactory.NetworkFactory(floating_ip_pool=True)
272
        fp1 = mfactory.FloatingIPFactory(machine=vm2, network=network)
273
        fp2 = mfactory.FloatingIPFactory(machine=vm2, network=network)
274
        mfactory.NetworkInterfaceFactory(machine=vm2, network=network,
275
                ipv4=fp1.ipv4)
276
        mfactory.NetworkInterfaceFactory(machine=vm2, network=network,
277
                ipv4=fp2.ipv4)
278
        pool = network.get_pool()
279
        pool.reserve(fp1.ipv4)
280
        pool.reserve(fp2.ipv4)
281
        pool.save()
282
        msg = self.create_msg(operation='OP_INSTANCE_REMOVE',
283
                              instance=vm2.backend_vm_id)
284
        with mocked_quotaholder():
285
            update_db(client, msg)
286
        client.basic_ack.assert_called_once()
287
        db_vm = VirtualMachine.objects.get(id=vm.id)
288
        self.assertEqual(db_vm.operstate, 'DESTROYED')
289
        self.assertTrue(db_vm.deleted)
290
        self.assertEqual(FloatingIP.objects.get(id=fp1.id).machine, None)
291
        self.assertEqual(FloatingIP.objects.get(id=fp2.id).machine, None)
292
        pool = network.get_pool()
293
        # Test that floating ips are not released
294
        self.assertFalse(pool.is_available(fp1.ipv4))
295
        self.assertFalse(pool.is_available(fp2.ipv4))
296

    
297
    def test_create(self, client):
298
        vm = mfactory.VirtualMachineFactory()
299
        msg = self.create_msg(operation='OP_INSTANCE_CREATE',
300
                              instance=vm.backend_vm_id)
301
        update_db(client, msg)
302
        self.assertTrue(client.basic_ack.called)
303
        db_vm = VirtualMachine.objects.get(id=vm.id)
304
        self.assertEqual(db_vm.operstate, 'STARTED')
305

    
306
    def test_create_error(self, client):
307
        """Test that error create sets vm to ERROR state"""
308
        vm = mfactory.VirtualMachineFactory()
309
        msg = self.create_msg(operation='OP_INSTANCE_CREATE',
310
                              instance=vm.backend_vm_id,
311
                              status='error')
312
        update_db(client, msg)
313
        self.assertTrue(client.basic_ack.called)
314
        db_vm = VirtualMachine.objects.get(id=vm.id)
315
        self.assertEqual(db_vm.operstate, 'ERROR')
316

    
317
    def test_remove_from_error(self, client):
318
        """Test that error removes delete error builds"""
319
        vm = mfactory.VirtualMachineFactory(operstate='ERROR')
320
        # Also create a NIC
321
        mfactory.NetworkInterfaceFactory(machine=vm)
322
        msg = self.create_msg(operation='OP_INSTANCE_REMOVE',
323
                              instance=vm.backend_vm_id)
324
        with mocked_quotaholder():
325
            update_db(client, msg)
326
        self.assertTrue(client.basic_ack.called)
327
        db_vm = VirtualMachine.objects.get(id=vm.id)
328
        self.assertEqual(db_vm.operstate, 'DESTROYED')
329
        self.assertTrue(db_vm.deleted)
330
        # Check that nics are deleted
331
        self.assertFalse(db_vm.nics.all())
332

    
333
    def test_other_error(self, client):
334
        """Test that other error messages do no affect the VM"""
335
        vm = mfactory.VirtualMachineFactory()
336
        msg = self.create_msg(operation='OP_INSTANCE_STARTUP',
337
                              instance=vm.backend_vm_id,
338
                              status='error')
339
        update_db(client, msg)
340
        self.assertTrue(client.basic_ack.called)
341
        db_vm = VirtualMachine.objects.get(id=vm.id)
342
        self.assertEqual(db_vm.operstate, vm.operstate)
343
        self.assertEqual(db_vm.backendtime, vm.backendtime)
344

    
345
    def test_resize_msg(self, client):
346
        vm = mfactory.VirtualMachineFactory()
347
        # Test empty beparams
348
        for status in ["success", "error"]:
349
            msg = self.create_msg(operation='OP_INSTANCE_SET_PARAMS',
350
                                  instance=vm.backend_vm_id,
351
                                  beparams={},
352
                                  status=status)
353
            client.reset_mock()
354
            with mocked_quotaholder():
355
                update_db(client, msg)
356
            self.assertTrue(client.basic_ack.called)
357
            db_vm = VirtualMachine.objects.get(id=vm.id)
358
            self.assertEqual(db_vm.operstate, vm.operstate)
359
        # Test intermediate states
360
        vm.operstate = "STOPPED"
361
        vm.save()
362
        for status in ["queued", "waiting", "running"]:
363
            msg = self.create_msg(operation='OP_INSTANCE_SET_PARAMS',
364
                                  instance=vm.backend_vm_id,
365
                                  beparams={"vcpus": 4, "minmem": 2048,
366
                                            "maxmem": 2048},
367
                                  status=status)
368
            client.reset_mock()
369
            update_db(client, msg)
370
            self.assertTrue(client.basic_ack.called)
371
            db_vm = VirtualMachine.objects.get(id=vm.id)
372
            self.assertEqual(db_vm.operstate, "STOPPED")
373
        # Test operstate after error
374
        msg = self.create_msg(operation='OP_INSTANCE_SET_PARAMS',
375
                              instance=vm.backend_vm_id,
376
                              beparams={"vcpus": 4},
377
                              status="error")
378
        client.reset_mock()
379
        with mocked_quotaholder():
380
            update_db(client, msg)
381
        self.assertTrue(client.basic_ack.called)
382
        db_vm = VirtualMachine.objects.get(id=vm.id)
383
        self.assertEqual(db_vm.operstate, "STOPPED")
384
        # Test success
385
        f1 = mfactory.FlavorFactory(cpu=4, ram=1024, disk_template="drbd",
386
                                    disk=1024)
387
        vm.flavor = f1
388
        vm.save()
389
        f2 = mfactory.FlavorFactory(cpu=8, ram=2048, disk_template="drbd",
390
                                    disk=1024)
391
        msg = self.create_msg(operation='OP_INSTANCE_SET_PARAMS',
392
                              instance=vm.backend_vm_id,
393
                              beparams={"vcpus": 8, "minmem": 2048,
394
                                        "maxmem": 2048},
395
                              status="success")
396
        client.reset_mock()
397
        with mocked_quotaholder():
398
            update_db(client, msg)
399
        self.assertTrue(client.basic_ack.called)
400
        db_vm = VirtualMachine.objects.get(id=vm.id)
401
        self.assertEqual(db_vm.operstate, "STOPPED")
402
        self.assertEqual(db_vm.flavor, f2)
403
        msg = self.create_msg(operation='OP_INSTANCE_SET_PARAMS',
404
                              instance=vm.backend_vm_id,
405
                              beparams={"vcpus": 100, "minmem": 2048,
406
                                        "maxmem": 2048},
407
                              status="success")
408
        client.reset_mock()
409
        with mocked_quotaholder():
410
            update_db(client, msg)
411
        self.assertTrue(client.basic_reject.called)
412

    
413

    
414
@patch('synnefo.lib.amqp.AMQPClient')
415
class UpdateNetTest(TestCase):
416
    def create_msg(self, **kwargs):
417
        """Create snf-ganeti-hook message"""
418
        msg = {'event_time': split_time(time())}
419
        msg['type'] = 'ganeti-op-status'
420
        msg['operation'] = 'OP_INSTANCE_SET_PARAMS'
421
        msg['status'] = 'success'
422
        msg['jobId'] = 1
423
        msg['logmsg'] = 'Dummy Log'
424
        for key, val in kwargs.items():
425
            msg[key] = val
426
        message = {'body': json.dumps(msg)}
427
        return message
428

    
429
    def test_missing_attribute(self, client):
430
        update_db(client, json.dumps({'body': {}}))
431
        self.assertTrue(client.basic_reject.called)
432

    
433
    def test_unhandled_exception(self, client):
434
        update_db(client, {})
435
        client.basic_reject.assert_called_once()
436

    
437
    def test_wrong_type(self, client):
438
        msg = self.create_msg(type="WRONG_TYPE")
439
        update_db(client, msg)
440
        self.assertTrue(client.basic_nack.called)
441

    
442
    def test_missing_instance(self, client):
443
        msg = self.create_msg(operation='OP_INSTANCE_STARTUP',
444
                              instance='foo')
445
        update_db(client, msg)
446
        self.assertTrue(client.basic_ack.called)
447

    
448
    def test_no_nics(self, client):
449
        vm = mfactory.VirtualMachineFactory(operstate='ERROR')
450
        mfactory.NetworkInterfaceFactory(machine=vm)
451
        mfactory.NetworkInterfaceFactory(machine=vm)
452
        mfactory.NetworkInterfaceFactory(machine=vm)
453
        self.assertEqual(len(vm.nics.all()), 3)
454
        msg = self.create_msg(nics=[],
455
                              instance=vm.backend_vm_id)
456
        update_db(client, msg)
457
        self.assertTrue(client.basic_ack.called)
458
        db_vm = VirtualMachine.objects.get(id=vm.id)
459
        self.assertEqual(len(db_vm.nics.all()), 0)
460

    
461
    def test_empty_nic(self, client):
462
        vm = mfactory.VirtualMachineFactory(operstate='ERROR')
463
        for public in [True, False]:
464
            net = mfactory.NetworkFactory(public=public)
465
            msg = self.create_msg(nics=[{'network': net.backend_id}],
466
                                  instance=vm.backend_vm_id)
467
            update_db(client, msg)
468
            self.assertTrue(client.basic_ack.called)
469
            db_vm = VirtualMachine.objects.get(id=vm.id)
470
            nics = db_vm.nics.all()
471
            self.assertEqual(len(nics), 1)
472
            self.assertEqual(nics[0].index, 0)
473
            self.assertEqual(nics[0].ipv4, '')
474
            self.assertEqual(nics[0].ipv6, '')
475
            self.assertEqual(nics[0].mac, '')
476
            if public:
477
                self.assertEqual(nics[0].firewall_profile,
478
                                 settings.DEFAULT_FIREWALL_PROFILE)
479
            else:
480
                self.assertEqual(nics[0].firewall_profile, '')
481

    
482
    def test_full_nic(self, client):
483
        vm = mfactory.VirtualMachineFactory(operstate='ERROR')
484
        net = mfactory.NetworkFactory(subnet='10.0.0.0/24')
485
        pool = net.get_pool()
486
        self.assertTrue(pool.is_available('10.0.0.22'))
487
        pool.save()
488
        msg = self.create_msg(nics=[{'network': net.backend_id,
489
                                     'ip': '10.0.0.22',
490
                                     'mac': 'aa:bb:cc:00:11:22'}],
491
                              instance=vm.backend_vm_id)
492
        update_db(client, msg)
493
        self.assertTrue(client.basic_ack.called)
494
        db_vm = VirtualMachine.objects.get(id=vm.id)
495
        nics = db_vm.nics.all()
496
        self.assertEqual(len(nics), 1)
497
        self.assertEqual(nics[0].index, 0)
498
        self.assertEqual(nics[0].ipv4, '10.0.0.22')
499
        self.assertEqual(nics[0].ipv6, '')
500
        self.assertEqual(nics[0].mac, 'aa:bb:cc:00:11:22')
501
        pool = net.get_pool()
502
        self.assertFalse(pool.is_available('10.0.0.22'))
503
        pool.save()
504

    
505

    
506
@patch('synnefo.lib.amqp.AMQPClient')
507
class UpdateNetworkTest(TestCase):
508
    def create_msg(self, **kwargs):
509
        """Create snf-ganeti-eventd message"""
510
        msg = {'event_time': split_time(time())}
511
        msg['type'] = 'ganeti-network-status'
512
        msg['status'] = 'success'
513
        msg['jobId'] = 1
514
        msg['logmsg'] = 'Dummy Log'
515
        for key, val in kwargs.items():
516
            msg[key] = val
517
        message = {'body': json.dumps(msg)}
518
        return message
519

    
520
    def test_missing_attribute(self, client):
521
        update_network(client, json.dumps({'body': {}}))
522
        self.assertTrue(client.basic_reject.called)
523

    
524
    def test_unhandled_exception(self, client):
525
        update_network(client, {})
526
        client.basic_reject.assert_called_once()
527

    
528
    def test_wrong_type(self, client):
529
        msg = self.create_msg(type="WRONG_TYPE")
530
        update_network(client, msg)
531
        self.assertTrue(client.basic_nack.called)
532

    
533
    def test_missing_network(self, client):
534
        msg = self.create_msg(operation='OP_NETWORK_CREATE',
535
                              network='foo')
536
        update_network(client, msg)
537
        self.assertTrue(client.basic_ack.called)
538

    
539
    def test_create(self, client):
540
        back_network = mfactory.BackendNetworkFactory(operstate='PENDING')
541
        net = back_network.network
542
        net.state = 'ACTIVE'
543
        net.save()
544
        back1 = back_network.backend
545

    
546
        back_network2 = mfactory.BackendNetworkFactory(operstate='PENDING',
547
                                                       network=net)
548
        back2 = back_network2.backend
549
        # Message from first backend network
550
        msg = self.create_msg(operation='OP_NETWORK_CONNECT',
551
                              network=net.backend_id,
552
                              cluster=back1.clustername)
553
        update_network(client, msg)
554
        self.assertTrue(client.basic_ack.called)
555

    
556
        back_net = BackendNetwork.objects.get(id=back_network.id)
557
        self.assertEqual(back_net.operstate, 'ACTIVE')
558
        db_net = Network.objects.get(id=net.id)
559
        self.assertEqual(db_net.state, 'ACTIVE')
560
        # msg from second backend network
561
        msg = self.create_msg(operation='OP_NETWORK_CONNECT',
562
                              network=net.backend_id,
563
                              cluster=back2.clustername)
564
        update_network(client, msg)
565
        self.assertTrue(client.basic_ack.called)
566

    
567
        db_net = Network.objects.get(id=net.id)
568
        self.assertEqual(db_net.state, 'ACTIVE')
569
        back_net = BackendNetwork.objects.get(id=back_network.id)
570
        self.assertEqual(back_net.operstate, 'ACTIVE')
571

    
572
    def test_create_offline_backend(self, client):
573
        """Test network creation when a backend is offline"""
574
        net = mfactory.NetworkFactory(state='ACTIVE')
575
        bn1 = mfactory.BackendNetworkFactory(network=net)
576
        bn2 = mfactory.BackendNetworkFactory(network=net,
577
                                             backend__offline=True)
578
        msg = self.create_msg(operation='OP_NETWORK_CONNECT',
579
                              network=net.backend_id,
580
                              cluster=bn1.backend.clustername)
581
        update_network(client, msg)
582
        self.assertTrue(client.basic_ack.called)
583
        new_net = Network.objects.get(id=net.id)
584
        self.assertEqual(new_net.state, 'ACTIVE')
585

    
586
    def test_disconnect(self, client):
587
        bn1 = mfactory.BackendNetworkFactory(operstate='ACTIVE')
588
        net1 = bn1.network
589
        net1.state = "ACTIVE"
590
        net1.state = 'ACTIVE'
591
        net1.save()
592
        bn2 = mfactory.BackendNetworkFactory(operstate='ACTIVE',
593
                                             network=net1)
594
        msg = self.create_msg(operation='OP_NETWORK_DISCONNECT',
595
                              network=net1.backend_id,
596
                              cluster=bn2.backend.clustername)
597
        update_network(client, msg)
598
        self.assertTrue(client.basic_ack.called)
599
        self.assertEqual(Network.objects.get(id=net1.id).state, 'ACTIVE')
600
        self.assertEqual(BackendNetwork.objects.get(id=bn2.id).operstate,
601
                         'PENDING')
602

    
603
    def test_remove(self, client):
604
        mfactory.MacPrefixPoolTableFactory()
605
        mfactory.BridgePoolTableFactory()
606
        bn = mfactory.BackendNetworkFactory(operstate='ACTIVE')
607
        for old_state in ['success', 'canceled', 'error']:
608
            for flavor in Network.FLAVORS.keys():
609
                bn.operstate = old_state
610
                bn.save()
611
                net = bn.network
612
                net.state = 'ACTIVE'
613
                net.flavor = flavor
614
                if flavor == 'PHYSICAL_VLAN':
615
                    net.link = allocate_resource('bridge')
616
                if flavor == 'MAC_FILTERED':
617
                    net.mac_prefix = allocate_resource('mac_prefix')
618
                net.save()
619
                msg = self.create_msg(operation='OP_NETWORK_REMOVE',
620
                                      network=net.backend_id,
621
                                      cluster=bn.backend.clustername)
622
                with mocked_quotaholder():
623
                    update_network(client, msg)
624
                self.assertTrue(client.basic_ack.called)
625
                db_bnet = BackendNetwork.objects.get(id=bn.id)
626
                self.assertEqual(db_bnet.operstate,
627
                                 'DELETED')
628
                db_net = Network.objects.get(id=net.id)
629
                self.assertEqual(db_net.state, 'DELETED', flavor)
630
                self.assertTrue(db_net.deleted)
631
                if flavor == 'PHYSICAL_VLAN':
632
                    pool = BridgePoolTable.get_pool()
633
                    self.assertTrue(pool.is_available(net.link))
634
                if flavor == 'MAC_FILTERED':
635
                    pool = MacPrefixPoolTable.get_pool()
636
                    self.assertTrue(pool.is_available(net.mac_prefix))
637

    
638
    def test_remove_offline_backend(self, client):
639
        """Test network removing when a backend is offline"""
640
        mfactory.BridgePoolTableFactory()
641
        net = mfactory.NetworkFactory(flavor='PHYSICAL_VLAN',
642
                                      state='ACTIVE',
643
                                      link='prv12')
644
        bn1 = mfactory.BackendNetworkFactory(network=net)
645
        mfactory.BackendNetworkFactory(network=net,
646
                                       operstate="ACTIVE",
647
                                       backend__offline=True)
648
        msg = self.create_msg(operation='OP_NETWORK_REMOVE',
649
                              network=net.backend_id,
650
                              cluster=bn1.backend.clustername)
651
        with mocked_quotaholder():
652
            update_network(client, msg)
653
        self.assertTrue(client.basic_ack.called)
654
        new_net = Network.objects.get(id=net.id)
655
        self.assertEqual(new_net.state, 'ACTIVE')
656
        self.assertFalse(new_net.deleted)
657

    
658
    def test_error_opcode(self, client):
659
        mfactory.MacPrefixPoolTableFactory()
660
        mfactory.BridgePoolTableFactory()
661
        for state, _ in Network.OPER_STATES:
662
            bn = mfactory.BackendNetworkFactory(operstate="ACTIVE")
663
            bn.operstate = state
664
            bn.save()
665
            network = bn.network
666
            network.state = state
667
            network.save()
668
            for opcode, _ in BackendNetwork.BACKEND_OPCODES:
669
                if opcode in ['OP_NETWORK_REMOVE', 'OP_NETWORK_ADD']:
670
                    continue
671
                msg = self.create_msg(operation=opcode,
672
                                      network=bn.network.backend_id,
673
                                      status='error',
674
                                      add_reserved_ips=[],
675
                                      remove_reserved_ips=[],
676
                                      cluster=bn.backend.clustername)
677
                with mocked_quotaholder():
678
                    update_network(client, msg)
679
                self.assertTrue(client.basic_ack.called)
680
                db_bnet = BackendNetwork.objects.get(id=bn.id)
681
                self.assertEqual(bn.operstate, db_bnet.operstate)
682
                self.assertEqual(bn.network.state, db_bnet.network.state)
683

    
684
    def test_ips(self, client):
685
        network = mfactory.NetworkFactory(subnet='10.0.0.0/24')
686
        bn = mfactory.BackendNetworkFactory(network=network)
687
        msg = self.create_msg(operation='OP_NETWORK_SET_PARAMS',
688
                              network=network.backend_id,
689
                              cluster=bn.backend.clustername,
690
                              status='success',
691
                              add_reserved_ips=['10.0.0.10', '10.0.0.20'],
692
                              remove_reserved_ips=[])
693
        update_network(client, msg)
694
        self.assertTrue(client.basic_ack.called)
695
        pool = network.get_pool()
696
        self.assertTrue(pool.is_reserved('10.0.0.10'))
697
        self.assertTrue(pool.is_reserved('10.0.0.20'))
698
        pool.save()
699
        # Release them
700
        msg = self.create_msg(operation='OP_NETWORK_SET_PARAMS',
701
                              network=network.backend_id,
702
                              cluster=bn.backend.clustername,
703
                              add_reserved_ips=[],
704
                              remove_reserved_ips=['10.0.0.10', '10.0.0.20'])
705
        update_network(client, msg)
706
        self.assertTrue(client.basic_ack.called)
707
        pool = network.get_pool()
708
        self.assertFalse(pool.is_reserved('10.0.0.10'))
709
        self.assertFalse(pool.is_reserved('10.0.0.20'))
710

    
711

    
712
@patch('synnefo.lib.amqp.AMQPClient')
713
class UpdateBuildProgressTest(TestCase):
714
    def setUp(self):
715
        self.vm = mfactory.VirtualMachineFactory()
716

    
717
    def get_db_vm(self):
718
        return VirtualMachine.objects.get(id=self.vm.id)
719

    
720
    def create_msg(self, **kwargs):
721
        """Create snf-progress-monitor message"""
722
        msg = {'event_time': split_time(time())}
723
        msg['type'] = 'image-copy-progress'
724
        msg['progress'] = 0
725
        for key, val in kwargs.items():
726
            msg[key] = val
727
        message = {'body': json.dumps(msg)}
728
        return message
729

    
730
    def test_missing_attribute(self, client):
731
        update_build_progress(client, json.dumps({'body': {}}))
732
        self.assertTrue(client.basic_reject.called)
733

    
734
    def test_unhandled_exception(self, client):
735
        update_build_progress(client, {})
736
        client.basic_reject.assert_called_once()
737

    
738
    def test_missing_instance(self, client):
739
        msg = self.create_msg(instance='foo')
740
        update_build_progress(client, msg)
741
        self.assertTrue(client.basic_ack.called)
742

    
743
    def test_wrong_type(self, client):
744
        msg = self.create_msg(type="WRONG_TYPE")
745
        update_build_progress(client, msg)
746
        self.assertTrue(client.basic_nack.called)
747

    
748
    def test_progress_update(self, client):
749
        rprogress = randint(10, 100)
750
        msg = self.create_msg(progress=rprogress,
751
                              instance=self.vm.backend_vm_id)
752
        update_build_progress(client, msg)
753
        self.assertTrue(client.basic_ack.called)
754
        vm = self.get_db_vm()
755
        self.assertEqual(vm.buildpercentage, rprogress)
756

    
757
    def test_invalid_value(self, client):
758
        old = self.vm.buildpercentage
759
        for rprogress in [0, -1, 'a']:
760
            msg = self.create_msg(progress=rprogress,
761
                                  instance=self.vm.backend_vm_id)
762
            update_build_progress(client, msg)
763
            self.assertTrue(client.basic_ack.called)
764
            vm = self.get_db_vm()
765
            self.assertEqual(vm.buildpercentage, old)
766

    
767

    
768
from synnefo.logic.reconciliation import VMState
769
class ReconciliationTest(TestCase):
770
    def get_vm(self, operstate, deleted=False):
771
        flavor = mfactory.FlavorFactory(cpu=2, ram=1024)
772
        vm = mfactory.VirtualMachineFactory(deleted=deleted, flavor=flavor)
773
        vm.operstate = operstate
774
        vm.save()
775
        return vm
776

    
777
    def test_get_servers_from_db(self):
778
        """Test getting a dictionary from each server to its operstate"""
779
        backends = Backend.objects.all()
780
        vm1 = self.get_vm('STARTED')
781
        vm2 = self.get_vm('DESTROYED', deleted=True)
782
        vm3 = self.get_vm('STOPPED')
783
        self.assertEquals(reconciliation.get_servers_from_db(backends),
784
                    {vm1.id: VMState(state='STARTED', cpu=2, ram=1024, nics=[]),
785
                     vm3.id: VMState(state='STOPPED', cpu=2, ram=1024, nics=[])}
786
                    )
787

    
788
    def test_stale_servers_in_db(self):
789
        """Test discovery of stale entries in DB"""
790

    
791
        D = {1: None, 2: 'None', 3: None, 30000: 'BUILD',
792
             30002: 'None'}
793
        G = {1: True, 3: True, 30000: True}
794
        self.assertEquals(reconciliation.stale_servers_in_db(D, G),
795
                          set([2, 30002]))
796

    
797
    @patch("synnefo.db.models.get_rapi_client")
798
    def test_stale_building_vm(self, client):
799
        vm = mfactory.VirtualMachineFactory()
800
        vm.state = 'BUILD'
801
        vm.backendjobid = 42
802
        vm.save()
803
        D = {vm.id: 'BUILD'}
804
        G = {}
805
        for status in ['queued', 'waiting', 'running']:
806
            client.return_value.GetJobStatus.return_value = {'status': status}
807
            self.assertEqual(reconciliation.stale_servers_in_db(D, G), set([]))
808
            client.return_value.GetJobStatus\
809
                               .assert_called_once_with(vm.backendjobid)
810
            client.reset_mock()
811
        for status in ['success', 'error', 'canceled']:
812
            client.return_value.GetJobStatus.return_value = {'status': status}
813
            self.assertEqual(reconciliation.stale_servers_in_db(D, G), set([]))
814
            client.return_value.GetInstance\
815
                               .assert_called_once_with(vm.backend_vm_id)
816
            client.return_value.GetJobStatus\
817
                               .assert_called_once_with(vm.backendjobid)
818
            client.reset_mock()
819
        from synnefo.logic.rapi import GanetiApiError
820
        client.return_value.GetJobStatus.side_effect = GanetiApiError('Foo')
821
        self.assertEqual(reconciliation.stale_servers_in_db(D, G),
822
                         set([vm.id]))
823

    
824
    def test_orphan_instances_in_ganeti(self):
825
        """Test discovery of orphan instances in Ganeti, without a DB entry"""
826

    
827
        G = {1: True, 2: False, 3: False, 4: True, 50: True}
828
        D = {1: True, 3: False}
829
        self.assertEquals(reconciliation.orphan_instances_in_ganeti(D, G),
830
                          set([2, 4, 50]))
831

    
832
    def test_unsynced_operstate(self):
833
        """Test discovery of unsynced operstate between the DB and Ganeti"""
834
        mkstate = lambda state: VMState(state=state, cpu=1, ram=1024, nics=[])
835
        vm1 = self.get_vm("STARTED")
836
        vm2 = self.get_vm("STARTED")
837
        vm3= self.get_vm("BUILD")
838
        vm4 = self.get_vm("STARTED")
839
        vm5 = self.get_vm("BUILD")
840

    
841
        D = {1: mkstate("STARTED"), 2: mkstate("STARTED"), 3: mkstate("BUILD"),
842
             4: mkstate("STARTED"), 50: mkstate("BUILD")}
843
        G = {vm1.id: mkstate(True), vm2.id: mkstate(False),
844
             vm4.id: mkstate(True), vm4.id: mkstate(False),
845
             vm5.id: mkstate(False)}
846
        self.assertEquals(reconciliation.unsynced_operstate(D, G),
847
                          set([(vm2.id, "STARTED", False),
848
                               (vm4.id, "STARTED", False)]))
849

    
850
from synnefo.logic.test.rapi_pool_tests import *
851
from synnefo.logic.test.utils_tests import *