Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / tests.py @ 8d325d4b

History | View | Annotate | Download (41 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, None)
474
            self.assertEqual(nics[0].ipv6, None)
475
            self.assertEqual(nics[0].mac, None)
476
            if public:
477
                self.assertEqual(nics[0].firewall_profile,
478
                                 settings.DEFAULT_FIREWALL_PROFILE)
479
            else:
480
                self.assertEqual(nics[0].firewall_profile, None)
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, None)
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
import logging
769
from datetime import timedelta
770

    
771

    
772
@patch("synnefo.logic.rapi_pool.GanetiRapiClient")
773
class ReconciliationTest(TestCase):
774
    @patch("synnefo.logic.rapi_pool.GanetiRapiClient")
775
    def setUp(self, mrapi):
776
        self.backend = mfactory.BackendFactory()
777
        log = logging.getLogger()
778
        options = {"fix_unsynced": True,
779
                   "fix_stale": True,
780
                   "fix_orphans": True,
781
                   "fix_unsynced_nics": True,
782
                   "fix_unsynced_flavors": True}
783
        self.reconciler = reconciliation.BackendReconciler(self.backend,
784
                                                           options=options,
785
                                                           logger=log)
786

    
787
    def test_building_vm(self, mrapi):
788
        mrapi = self.reconciler.client
789
        vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
790
                                             backendjobid=None,
791
                                             operstate="BUILD")
792
        self.reconciler.reconcile()
793
        # Assert not deleted
794
        vm1 = VirtualMachine.objects.get(id=vm1.id)
795
        self.assertFalse(vm1.deleted)
796
        self.assertEqual(vm1.operstate, "BUILD")
797

    
798
        vm1.created = vm1.created - timedelta(seconds=120)
799
        vm1.save()
800
        with mocked_quotaholder():
801
            self.reconciler.reconcile()
802
        vm1 = VirtualMachine.objects.get(id=vm1.id)
803
        self.assertEqual(vm1.operstate, "ERROR")
804

    
805
        vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
806
                                             backendjobid=1,
807
                                             deleted=False,
808
                                             operstate="BUILD")
809
        vm1.backendtime = vm1.created - timedelta(seconds=120)
810
        vm1.backendjobid = 10
811
        vm1.save()
812
        for status in ["queued", "waiting", "running"]:
813
            mrapi.GetJobStatus.return_value = {"status": status}
814
            with mocked_quotaholder():
815
                self.reconciler.reconcile()
816
            vm1 = VirtualMachine.objects.get(id=vm1.id)
817
            self.assertFalse(vm1.deleted)
818
            self.assertEqual(vm1.operstate, "BUILD")
819

    
820
        mrapi.GetJobStatus.return_value = {"status": "error"}
821
        with mocked_quotaholder():
822
            self.reconciler.reconcile()
823
        vm1 = VirtualMachine.objects.get(id=vm1.id)
824
        self.assertFalse(vm1.deleted)
825
        self.assertEqual(vm1.operstate, "ERROR")
826

    
827
        for status in ["success", "cancelled"]:
828
            vm1.deleted = False
829
            vm1.save()
830
            mrapi.GetJobStatus.return_value = {"status": status}
831
            with mocked_quotaholder():
832
                self.reconciler.reconcile()
833
            vm1 = VirtualMachine.objects.get(id=vm1.id)
834
            self.assertTrue(vm1.deleted)
835
            self.assertEqual(vm1.operstate, "DESTROYED")
836

    
837
        vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
838
                                             backendjobid=1,
839
                                             operstate="BUILD")
840
        vm1.backendtime = vm1.created - timedelta(seconds=120)
841
        vm1.backendjobid = 10
842
        vm1.save()
843
        cmrapi = self.reconciler.client
844
        cmrapi.GetInstances.return_value = \
845
            [{"name": vm1.backend_vm_id,
846
             "beparams": {"maxmem": 1024,
847
                          "minmem": 1024,
848
                          "vcpus": 4},
849
             "oper_state": False,
850
             "mtime": time(),
851
             "disk.sizes": [],
852
             "nic.ips": [],
853
             "nic.macs": [],
854
             "nic.networks": [],
855
             "tags": []}]
856
        mrapi.GetJobStatus.return_value = {"status": "running"}
857
        with mocked_quotaholder():
858
            self.reconciler.reconcile()
859
        vm1 = VirtualMachine.objects.get(id=vm1.id)
860
        self.assertEqual(vm1.operstate, "BUILD")
861
        mrapi.GetJobStatus.return_value = {"status": "error"}
862
        with mocked_quotaholder():
863
            self.reconciler.reconcile()
864
        vm1 = VirtualMachine.objects.get(id=vm1.id)
865
        self.assertEqual(vm1.operstate, "ERROR")
866

    
867
    def test_stale_server(self, mrapi):
868
        mrapi.GetInstances = []
869
        vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
870
                                             deleted=False,
871
                                             operstate="ERROR")
872
        with mocked_quotaholder():
873
            self.reconciler.reconcile()
874
        vm1 = VirtualMachine.objects.get(id=vm1.id)
875
        self.assertTrue(vm1.deleted)
876

    
877
    def test_orphan_server(self, mrapi):
878
        cmrapi = self.reconciler.client
879
        mrapi().GetInstances.return_value =\
880
            [{"name": "%s22" % settings.BACKEND_PREFIX_ID,
881
             "beparams": {"maxmem": 1024,
882
                          "minmem": 1024,
883
                          "vcpus": 4},
884
             "oper_state": True,
885
             "mtime": time(),
886
             "disk.sizes": [],
887
             "nic.ips": [],
888
             "nic.macs": [],
889
             "nic.networks": [],
890
             "tags": []}]
891
        self.reconciler.reconcile()
892
        cmrapi.DeleteInstance.assert_called_once_with(
893
                "%s22" % settings.BACKEND_PREFIX_ID)
894

    
895
    def test_unsynced_operstate(self, mrapi):
896
        vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
897
                                             deleted=False,
898
                                             operstate="STOPPED")
899
        mrapi().GetInstances.return_value =\
900
            [{"name": vm1.backend_vm_id,
901
             "beparams": {"maxmem": 1024,
902
                          "minmem": 1024,
903
                          "vcpus": 4},
904
             "oper_state": True,
905
             "mtime": time(),
906
             "disk.sizes": [],
907
             "nic.ips": [],
908
             "nic.macs": [],
909
             "nic.networks": [],
910
             "tags": []}]
911
        with mocked_quotaholder():
912
            self.reconciler.reconcile()
913
        vm1 = VirtualMachine.objects.get(id=vm1.id)
914
        self.assertEqual(vm1.operstate, "STARTED")
915

    
916
    def test_unsynced_flavor(self, mrapi):
917
        flavor1 = mfactory.FlavorFactory(cpu=2, ram=1024, disk=1,
918
                                         disk_template="drbd")
919
        flavor2 = mfactory.FlavorFactory(cpu=4, ram=2048, disk=1,
920
                                         disk_template="drbd")
921
        vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
922
                                             deleted=False,
923
                                             flavor=flavor1,
924
                                             operstate="STARTED")
925
        mrapi().GetInstances.return_value =\
926
            [{"name": vm1.backend_vm_id,
927
             "beparams": {"maxmem": 2048,
928
                          "minmem": 2048,
929
                          "vcpus": 4},
930
             "oper_state": True,
931
             "mtime": time(),
932
             "disk.sizes": [],
933
             "nic.ips": [],
934
             "nic.macs": [],
935
             "nic.networks": [],
936
             "tags": []}]
937
        with mocked_quotaholder():
938
            self.reconciler.reconcile()
939
        vm1 = VirtualMachine.objects.get(id=vm1.id)
940
        self.assertEqual(vm1.flavor, flavor2)
941
        self.assertEqual(vm1.operstate, "STARTED")
942

    
943
    def test_unsynced_nics(self, mrapi):
944
        network1 = mfactory.NetworkFactory(subnet="10.0.0.0/24")
945
        network2 = mfactory.NetworkFactory(subnet="192.168.2.0/24")
946
        vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
947
                                             deleted=False,
948
                                             operstate="STOPPED")
949
        mfactory.NetworkInterfaceFactory(machine=vm1, network=network1,
950
                                         ipv4="10.0.0.0")
951
        mrapi().GetInstances.return_value =\
952
            [{"name": vm1.backend_vm_id,
953
             "beparams": {"maxmem": 2048,
954
                          "minmem": 2048,
955
                          "vcpus": 4},
956
             "oper_state": True,
957
             "mtime": time(),
958
             "disk.sizes": [],
959
             "nic.ips": ["192.168.2.1"],
960
             "nic.macs": ["aa:00:bb:cc:dd:ee"],
961
             "nic.networks": [network2.backend_id],
962
             "tags": []}]
963
        with mocked_quotaholder():
964
            self.reconciler.reconcile()
965
        vm1 = VirtualMachine.objects.get(id=vm1.id)
966
        self.assertEqual(vm1.operstate, "STARTED")
967
        nic = vm1.nics.all()[0]
968
        self.assertEqual(nic.network, network2)
969
        self.assertEqual(nic.ipv4, "192.168.2.1")
970
        self.assertEqual(nic.mac, "aa:00:bb:cc:dd:ee")
971

    
972

    
973
from synnefo.logic.test.rapi_pool_tests import *
974
from synnefo.logic.test.utils_tests import *