Revision c8908e51

b/snf-cyclades-app/synnefo/db/tests.py
1
# Copyright 2011 GRNET S.A. All rights reserved.
1
# Copyright 2012 GRNET S.A. All rights reserved.
2 2
#
3 3
# Redistribution and use in source and binary forms, with or without
4 4
# modification, are permitted provided that the following conditions
......
31 31
#
32 32
# Provides automated tests for db module
33 33

  
34
from synnefo.db.models import *
35 34
from django.test import TestCase
36 35

  
37 36
# Import pool tests
38 37
from synnefo.db.pools.tests import *
39 38

  
39
from synnefo.db.models import *
40
from synnefo.db import models_factory as mfact
41
from synnefo.db.pools import IPPool
42

  
43
from django.db import IntegrityError
44
from django.core.exceptions import MultipleObjectsReturned
45
from mock import patch
46

  
47

  
48
class FlavorTest(TestCase):
49
    def test_flavor_name(self):
50
        """Test a flavor object name method."""
51
        flavor = mfact.FlavorFactory(cpu=1, ram=1024, disk=40)
52
        self.assertEqual(flavor.name, "C1R1024D40", "flavor.name is not"
53
                " generated correctly. Name is %s instead of C1R1055D40" %
54
                flavor.name)
55

  
56

  
57
class BackendTest(TestCase):
58
    def setUp(self):
59
        self.backend = mfact.BackendFactory()
60

  
61
    @patch("synnefo.db.models.get_rapi_client")
62
    def test_get_client(self, client):
63
        id_ = self.backend.id
64
        hash_ = self.backend.hash
65
        name = self.backend.clustername
66
        passwd = self.backend.password
67
        user = self.backend.username
68
        port = self.backend.port
69
        self.backend.get_client()
70
        client.assert_called_once_with(id_, hash_, name, port, user, passwd)
71

  
72
    def test_save_hash(self):
73
        """Test that backend hash is generated on credential change"""
74
        old_hash = self.backend.hash
75
        for field in ['clustername', 'username', 'password', 'port']:
76
            value = 5181 if field == 'port' else 'foo'
77
            self.backend.__setattr__(field, value)
78
            self.backend.save()
79
            self.assertNotEqual(old_hash, self.backend.hash)
80
            old_hash = self.backend.hash
81

  
82
    def test_unique_index(self):
83
        """Test that each backend gets a unique index"""
84
        backends = [self.backend]
85
        for i in xrange(0, 14):
86
            backends.append(mfact.BackendFactory())
87
        indexes = map(lambda x: x.index, backends)
88
        self.assertEqual(len(indexes), len(set(indexes)))
89

  
90
    def test_backend_number(self):
91
        """Test that no more than 16 backends are created"""
92
        for i in xrange(0, 14):
93
            mfact.BackendFactory()
94
        self.assertRaises(Exception, mfact.BackendFactory, ())
95

  
96
    def test_delete_backend(self):
97
        vm = mfact.VirtualMachineFactory(backend=self.backend, deleted=True)
98
        bnet = mfact.BackendNetworkFactory(backend=self.backend)
99
        self.backend.delete()
100
        self.assertRaises(Backend.DoesNotExist, Backend.objects.get,
101
                          id=self.backend.id)
102
        # Test that VM is not deleted
103
        vm2 = VirtualMachine.objects.get(id=vm.id)
104
        self.assertEqual(vm2.backend, None)
105
        # Test tha backend networks are deleted, but not the network
106
        self.assertRaises(BackendNetwork.DoesNotExist,
107
                          BackendNetwork.objects.get, id=bnet.id)
108
        Network.objects.get(id=bnet.network.id)
109

  
110
    def test_delete_active_backend(self):
111
        """Test that a backend with non-deleted VMS is not deleted"""
112
        mfact.VirtualMachineFactory(backend=self.backend)
113
        self.assertRaises(IntegrityError, self.backend.delete, ())
114

  
115
    def test_password_encryption(self):
116
        password_hash = self.backend.password
117
        self.backend.password = '123'
118
        self.assertNotEqual(self.backend.password_hash, '123')
119
        self.assertNotEqual(self.backend.password_hash, password_hash)
120
        self.assertEqual(self.backend.password, '123')
121

  
122

  
123
class VirtualMachineTest(TestCase):
124
    def setUp(self):
125
        self.vm = mfact.VirtualMachineFactory()
126

  
127
    @patch("synnefo.db.models.get_rapi_client")
128
    def test_get_client(self, client):
129
        backend = self.vm.backend
130
        id_ = backend.id
131
        hash_ = backend.hash
132
        name = backend.clustername
133
        passwd = backend.password
134
        user = backend.username
135
        port = backend.port
136
        self.vm.get_client()
137
        client.assert_called_once_with(id_, hash_, name, port, user, passwd)
138

  
139
    def test_create(self):
140
        vm = mfact.VirtualMachineFactory()
141
        self.assertEqual(vm.action, None)
142
        self.assertEqual(vm.backendjobid, None)
143
        self.assertEqual(vm.backendjobstatus, None)
144
        self.assertEqual(vm.backendopcode, None)
145
        self.assertEqual(vm.backendlogmsg, None)
146
        self.assertEqual(vm.operstate, 'BUILD')
147

  
148

  
149
class NetworkTest(TestCase):
150
    def setUp(self):
151
        self.net = mfact.NetworkFactory()
152

  
153
    def test_tags(self):
154
        net1 = mfact.NetworkFactory(flavor='IP_LESS_ROUTED')
155
        self.assertEqual(net1.backend_tag, ['ip-less-routed'])
156
        net1 = mfact.NetworkFactory(flavor='CUSTOM')
157
        self.assertEqual(net1.backend_tag, [])
158

  
159
    def test_create_backend_network(self):
160
        len_backends = len(Backend.objects.all())
161
        back = mfact.BackendFactory()
162
        self.net.create_backend_network(backend=back)
163
        BackendNetwork.objects.get(network=self.net, backend=back)
164
        back1 = mfact.BackendFactory()
165
        back2 = mfact.BackendFactory()
166
        self.net.create_backend_network()
167
        BackendNetwork.objects.get(network=self.net, backend=back1)
168
        BackendNetwork.objects.get(network=self.net, backend=back2)
169
        self.assertEqual(len(BackendNetwork.objects.filter(network=self.net)),
170
                         len_backends + 3)
171

  
172
    def test_pool(self):
173
        pool = self.net.get_pool()
174
        pool.network = self.net
175
        self.assertTrue(isinstance(pool, IPPool))
176

  
177
    def test_reserve_ip(self):
178
        net1 = mfact.NetworkFactory(subnet='192.168.2.0/24')
179
        net1.reserve_address('192.168.2.12')
180
        pool = net1.get_pool()
181
        self.assertFalse(pool.is_available('192.168.2.12'))
182
        net1.release_address('192.168.2.12')
183
        pool = net1.get_pool()
184
        self.assertTrue(pool.is_available('192.168.2.12'))
185

  
186

  
187
class BackendNetworkTest(TestCase):
188
    def test_mac_prefix(self):
189
        network = mfact.NetworkFactory(mac_prefix='aa:bb:c')
190
        backend = mfact.BackendFactory()
191
        bnet = mfact.BackendNetworkFactory(network=network, backend=backend)
192
        self.assertTrue(backend.index < 10)
193
        self.assertEqual(bnet.mac_prefix, 'aa:bb:c%s' % backend.index)
194

  
195

  
196
class BridgePoolTest(TestCase):
197
    def test_no_pool(self):
198
        self.assertRaises(BridgePoolTable.DoesNotExist,
199
                          BridgePoolTable.get_pool)
200

  
201
    def test_two_pools(self):
202
        mfact.BridgePoolTableFactory()
203
        mfact.BridgePoolTableFactory()
204
        self.assertRaises(MultipleObjectsReturned, BridgePoolTable.get_pool)
40 205

  
41
class FlavorTestCase(TestCase):
42
    fixtures = [ 'db_test_data' ]
43 206

  
44
    def test_flavor(self):
45
        """Test a flavor object, its internal cost calculation and naming methods"""
46
        flavor = Flavor.objects.get(pk=30000)
207
class AESTest(TestCase):
208
    def test_encrypt_decrtypt(self):
209
        from synnefo.db import aes_encrypt as aes
210
        old = 'bar'
211
        new = aes.decrypt_db_charfield(aes.encrypt_db_charfield(old))
212
        self.assertEqual(old, new)
47 213

  
48
        # test name property, should be C1R1024D10
49
        f_name = flavor.name
214
    def test_password_change(self):
215
        from synnefo.db import aes_encrypt as aes
216
        old_pass = aes.SECRET_ENCRYPTION_KEY
217
        old = 'bar'
218
        encrypted = aes.encrypt_db_charfield(old)
219
        aes.SECRET_ENCRYPTION_KEY = 'foo2'
220
        self.assertRaises(aes.CorruptedPassword, aes.decrypt_db_charfield,
221
                          encrypted)
222
        aes.SECRET_ENCRYPTION_KEY = old_pass
223
        new = aes.decrypt_db_charfield(encrypted)
224
        self.assertEqual(old, new)
50 225

  
51
        self.assertEqual(f_name, 'C1R1024D10', 'flavor.name is not generated correctly, C1R1024D10! (%s)' % (f_name,))
226
    def test_big_secret(self):
227
        from synnefo.db import aes_encrypt as aes
228
        old = aes.SECRET_ENCRYPTION_KEY
229
        aes.SECRET_ENCRYPTION_KEY = \
230
            '91490231234814234812348913289481294812398421893489'
231
        self.assertRaises(ValueError, aes.encrypt_db_charfield, 'la')
232
        aes.SECRET_ENCRYPTION_KEY = old
b/snf-cyclades-app/synnefo/logic/tests.py
43 43
from datetime import datetime
44 44
from mock import patch
45 45
from synnefo.api.util import allocate_resource
46
from synnefo.logic.callbacks import update_db, update_net, update_network
46
from synnefo.logic.callbacks import (update_db, update_net, update_network,
47
                                    update_build_progress)
47 48

  
48 49
now = datetime.now
49 50
from time import time
......
428 429
        self.assertFalse(pool.is_reserved('10.0.0.20'))
429 430

  
430 431

  
431
class ProcessOpStatusTestCase(TestCase):
432
    fixtures = ['db_test_data']
433
    msg_op = {
434
        'instance': 'instance-name',
435
        'type': 'ganeti-op-status',
436
        'operation': 'OP_INSTANCE_STARTUP',
437
        'jobId': 0,
438
        'status': 'success',
439
        'logmsg': 'unittest - simulated message'
440
    }
441

  
442
    def test_op_startup_success(self):
443
        """Test notification for successful OP_INSTANCE_START"""
444
        msg = self.msg_op
445
        msg['operation'] = 'OP_INSTANCE_STARTUP'
446
        msg['status'] = 'success'
447

  
448
        # This machine is initially in BUILD
449
        vm = VirtualMachine.objects.get(pk=30002)
450
        backend.process_op_status(vm, now(), msg["jobId"], msg["operation"],
451
                                  msg["status"], msg["logmsg"])
452
        self.assertEquals(get_rsapi_state(vm), 'ACTIVE')
453

  
454
    def test_op_shutdown_success(self):
455
        """Test notification for successful OP_INSTANCE_SHUTDOWN"""
456
        msg = self.msg_op
457
        msg['operation'] = 'OP_INSTANCE_SHUTDOWN'
458
        msg['status'] = 'success'
459

  
460
        # This machine is initially in BUILD
461
        vm = VirtualMachine.objects.get(pk=30002)
462
        backend.process_op_status(vm, now(), msg["jobId"], msg["operation"],
463
                                  msg["status"], msg["logmsg"])
464
        self.assertEquals(get_rsapi_state(vm), 'STOPPED')
465

  
466
    def test_op_reboot_success(self):
467
        """Test notification for successful OP_INSTANCE_REBOOT"""
468
        msg = self.msg_op
469
        msg['operation'] = 'OP_INSTANCE_REBOOT'
470
        msg['status'] = 'success'
471

  
472
        # This machine is initially in BUILD
473
        vm = VirtualMachine.objects.get(pk=30002)
474
        backend.process_op_status(vm, now(), msg["jobId"], msg["operation"],
475
                                  msg["status"], msg["logmsg"])
476
        self.assertEquals(get_rsapi_state(vm), 'ACTIVE')
432
@patch('synnefo.lib.amqp.AMQPClient')
433
class UpdateBuildProgressTest(TestCase):
434
    def setUp(self):
435
        self.vm = mfactory.VirtualMachineFactory()
477 436

  
478
    def test_op_create_success(self):
479
        """Test notification for successful OP_INSTANCE_CREATE"""
480
        msg = self.msg_op
481
        msg['operation'] = 'OP_INSTANCE_CREATE'
482
        msg['status'] = 'success'
437
    def get_db_vm(self):
438
        return VirtualMachine.objects.get(id=self.vm.id)
483 439

  
484
        # This machine is initially in BUILD
485
        vm = VirtualMachine.objects.get(pk=30002)
486
        backend.process_op_status(vm, now(), msg["jobId"], msg["operation"],
487
                                  msg["status"], msg["logmsg"])
488
        self.assertEquals(get_rsapi_state(vm), 'ACTIVE')
440
    def create_msg(self, **kwargs):
441
        """Create snf-progress-monitor message"""
442
        msg = {'event_time': split_time(time())}
443
        msg['type'] = 'image-copy-progress'
444
        msg['progress'] = 0
445
        for key, val in kwargs.items():
446
            msg[key] = val
447
        message = {'body': json.dumps(msg)}
448
        return message
489 449

  
490
    def test_op_remove_success(self):
491
        """Test notification for successful OP_INSTANCE_REMOVE"""
492
        msg = self.msg_op
493
        msg['operation'] = 'OP_INSTANCE_REMOVE'
494
        msg['status'] = 'success'
450
    def test_missing_attribute(self, client):
451
        update_build_progress(client, json.dumps({'body': {}}))
452
        client.basic_nack.assert_called_once()
495 453

  
496
        # This machine is initially in BUILD
497
        vm = VirtualMachine.objects.get(pk=30002)
498
        backend.process_op_status(vm, now(), msg["jobId"], msg["operation"],
499
                                  msg["status"], msg["logmsg"])
500
        self.assertEquals(get_rsapi_state(vm), 'DELETED')
501
        self.assertTrue(vm.deleted)
502

  
503
    def test_op_create_error(self):
504
        """Test notification for failed OP_INSTANCE_CREATE"""
505
        msg = self.msg_op
506
        msg['operation'] = 'OP_INSTANCE_CREATE'
507
        msg['status'] = 'error'
508

  
509
        # This machine is initially in BUILD
510
        vm = VirtualMachine.objects.get(pk=30002)
511
        backend.process_op_status(vm, now(), msg["jobId"], msg["operation"],
512
                                  msg["status"], msg["logmsg"])
513
        self.assertEquals(get_rsapi_state(vm), 'ERROR')
514
        self.assertFalse(vm.deleted)
515

  
516
    def test_remove_machine_in_error(self):
517
        """Test notification for failed OP_INSTANCE_REMOVE, server in ERROR"""
518
        msg = self.msg_op
519
        msg['operation'] = 'OP_INSTANCE_REMOVE'
520
        msg['status'] = 'error'
521

  
522
        # This machine is initially in BUILD
523
        vm = VirtualMachine.objects.get(pk=30002)
524
        backend.process_op_status(vm, now(), 0, "OP_INSTANCE_CREATE", "error", "test")
525
        self.assertEquals(get_rsapi_state(vm), 'ERROR')
526

  
527
        backend.process_op_status(vm, now(), msg["jobId"], msg["operation"],
528
                                  msg["status"], msg["logmsg"])
529
        self.assertEquals(get_rsapi_state(vm), 'DELETED')
530
        self.assertTrue(vm.deleted)
531

  
532

  
533
class ProcessNetStatusTestCase(TestCase):
534
    fixtures = ['db_test_data']
454
    def test_unhandled_exception(self, client):
455
        update_build_progress(client, {})
456
        client.basic_reject.assert_called_once()
535 457

  
536
    def test_set_ipv4(self):
537
        """Test reception of a net status notification"""
538
        msg = {'instance': 'instance-name',
539
               'type':     'ganeti-net-status',
540
               'nics': [
541
                   {'ip': '10.0.0.21',
542
                    'mac': 'aa:00:00:58:1e:b9',
543
                    'network':'snf-net-30000'}
544
               ]
545
        }
546
        vm = VirtualMachine.objects.get(pk=30000)
547
        backend.process_net_status(vm, now(), msg['nics'])
548
        self.assertEquals(vm.nics.all()[0].ipv4, '10.0.0.21')
549

  
550
    def test_set_empty_ipv4(self):
551
        """Test reception of a net status notification with no IPv4 assigned"""
552
        msg = {'instance': 'instance-name',
553
               'type':     'ganeti-net-status',
554
               'nics': [
555
                   {'ip': '',
556
                    'mac': 'aa:00:00:58:1e:b9',
557
                    'network':'snf-net-30000'}
558
               ]
559
        }
560
        vm = VirtualMachine.objects.get(pk=30000)
561
        backend.process_net_status(vm, now(), msg['nics'])
562
        self.assertEquals(vm.nics.all()[0].ipv4, '')
563

  
564

  
565
class ProcessProgressUpdateTestCase(TestCase):
566
    fixtures = ['db_test_data']
458
    def test_missing_instance(self, client):
459
        msg = self.create_msg(instance='foo')
460
        update_build_progress(client, msg)
461
        client.basic_nack.assert_called_once()
567 462

  
568
    def test_progress_update(self):
569
        """Test reception of a create progress notification"""
463
    def test_wrong_type(self, client):
464
        msg = self.create_msg(type="WRONG_TYPE")
465
        update_build_progress(client, msg)
466
        client.basic_ack.assert_called_once()
570 467

  
571
        # This machine is in BUILD
572
        vm = VirtualMachine.objects.get(pk=30002)
468
    def test_progress_update(self, client):
573 469
        rprogress = randint(10, 100)
574

  
575
        backend.process_create_progress(vm, now(), rprogress)
576
        self.assertEquals(vm.buildpercentage, rprogress)
577

  
578
        #self.assertRaises(ValueError, backend.process_create_progress,
579
        #                  vm, 9, 0)
580
        self.assertRaises(ValueError, backend.process_create_progress,
581
                          now(), vm, -1)
582
        self.assertRaises(ValueError, backend.process_create_progress,
583
                          now(), vm, 'a')
584

  
585
        # This machine is ACTIVE
586
        #vm = VirtualMachine.objects.get(pk=30000)
587
        #self.assertRaises(VirtualMachine.IllegalState,
588
        #                  backend.process_create_progress, vm, 1)
470
        msg = self.create_msg(progress=rprogress,
471
                              instance=self.vm.backend_vm_id)
472
        update_build_progress(client, msg)
473
        client.basic_ack.assert_called_once()
474
        vm = self.get_db_vm()
475
        self.assertEqual(vm.buildpercentage, rprogress)
476

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

  
590 487

  
591
class ReconciliationTestCase(TestCase):
488
class ReconciliationTest(TestCase):
592 489
    SERVERS = 1000
593 490
    fixtures = ['db_test_data']
594 491

  
595 492
    def test_get_servers_from_db(self):
596 493
        """Test getting a dictionary from each server to its operstate"""
597
        reconciliation.get_servers_from_db()
598 494
        self.assertEquals(reconciliation.get_servers_from_db(),
599 495
                          {30000: 'STARTED', 30001: 'STOPPED', 30002: 'BUILD'})
600 496

  
......
607 503
        self.assertEquals(reconciliation.stale_servers_in_db(D, G),
608 504
                          set([2, 30002]))
609 505

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

  
610 529
    def test_orphan_instances_in_ganeti(self):
611 530
        """Test discovery of orphan instances in Ganeti, without a DB entry"""
612 531

  

Also available in: Unified diff