Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / tests.py @ 9c0ac5af

History | View | Annotate | Download (14.5 kB)

1
# vim: set fileencoding=utf-8 :
2
# Copyright 2011 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
import time
34
import hashlib
35
from random import randint
36

    
37
from django.test import TestCase
38
from django.conf import settings
39

    
40
from synnefo.db.models import *
41
from synnefo.logic import backend
42
from synnefo.logic import credits
43
from synnefo.logic import users
44
from synnefo.logic import reconciliation
45
from synnefo.logic.utils import get_rsapi_state
46

    
47

    
48
class CostsTestCase(TestCase):
49
    fixtures = ['db_test_data']
50

    
51
    def test_get_costs(self):
52
        """Test the Flavor cost-related methods method"""
53
        # first an easy test, a Flavor with only one FlavorCost entry
54
        flavor = Flavor.objects.get(pk=30001)
55

    
56
        start_date = datetime.datetime(year=2010, month=1, day=1)
57
        end_date = datetime.datetime(year=2010, month=1, day=2)
58

    
59
        # we now that the cost should be 5*24 (inactive) and 10*24 (active)
60
        r_active = credits.get_cost_active(flavor, start_date, end_date)
61
        r_inactive = credits.get_cost_inactive(flavor, start_date, end_date)
62

    
63
        self.assertEqual(len(r_active), 1, 'get_cost_active() should have returned 1 entry (%d)' %(len(r_active),))
64
        self.assertEqual(len(r_inactive), 1, 'get_cost_inactive() should have returned 1 entry (%d)'% (len(r_inactive),))
65

    
66
        self.assertEqual(10*24, r_active[0][1], 'get_cost_active() is not working properly (%d!=%d)' % (r_active[0][1], 10*24))
67
        self.assertEqual(5*24, r_inactive[0][1], 'get_cost_inactive() is not working properly (%d!=%d)' % (r_inactive[0][1], 5*24))
68

    
69
        # The second test, will involve a more complex cost example
70
        # The overall cost will be calculated by two FlavorCost entries
71

    
72
        flavor = Flavor.objects.get(pk=30000)
73

    
74
        start_date = datetime.datetime(year=2010, month=12, day=31)
75
        end_date = datetime.datetime(year=2011, month=01, day=2)
76

    
77
        # this is more complicated, active costs are 5*24 + 10*24 = 360
78
        # and inactive costs are 2*24 + 5*24 = 168
79

    
80
        r_active = credits.get_cost_active(flavor, start_date, end_date)
81
        r_inactive = credits.get_cost_inactive(flavor, start_date, end_date)
82

    
83
        self.assertEqual(len(r_active), 2, 'get_cost_active() should have returned 2 entries (%d)' %(len(r_active),))
84
        self.assertEqual(len(r_inactive), 2, 'get_cost_inactive() should have returned 2 entries (%d)'% (len(r_inactive),))
85

    
86
        ta_cost = sum([x[1] for x in r_active])
87
        tia_cost = sum([x[1] for x in r_inactive])
88

    
89
        self.assertEqual(360, ta_cost, 'get_cost_active() is not working properly (%d!=%d)' % (ta_cost, 360))
90
        self.assertEqual(168, tia_cost, 'get_cost_inactive() is not working properly (%d!=%d)' % (tia_cost, 168))
91

    
92
        
93
class ChargeTestCase(TestCase):
94
    fixtures = ['db_test_data']
95

    
96
    def test_charge_method(self):
97
        """Test VirtualMachine.charge() method"""
98

    
99
        # Since we have tested the costs, with this test
100
        # we must ensure the following:
101
        # 1. The vm.charged is updated
102
        # 2. Users credits are decreased
103

    
104
        vm_started = VirtualMachine.objects.get(pk=30000)
105

    
106
        initial_date = vm_started.charged
107
        initial_credits = vm_started.owner.credit
108

    
109
        credits.charge(vm_started)
110

    
111
        self.assertTrue(vm_started.charged > initial_date, 'Initial charged date should not be greater')
112
        self.assertTrue(initial_credits > vm_started.owner.credit, 'The user should have less credits now! (%d>%d)' % (initial_credits, vm_started.owner.credit))
113

    
114

    
115
class DebitAccountTestCase(TestCase):
116
    fixtures = ['db_test_data']
117

    
118
    def test_debit_account(self):
119
        """Test a SynnefoUser object"""
120
        s_user = SynnefoUser.objects.get(pk=30000)
121
        v_machine = VirtualMachine.objects.get(pk=30000)
122

    
123
        # charge the user
124
        credits.debit_account(s_user, 10, v_machine, "This should be a structured debit message!")
125

    
126
        # should have only one debit object
127
        d_list = Debit.objects.all()
128

    
129
        self.assertEqual(len(d_list), 1, 'debit_account() writes more than one or zero (%d) debit entries!' % ( len(d_list), ))
130

    
131
        # retrieve the user, now he/she should have zero credits
132
        s_user = SynnefoUser.objects.get(pk=30000)
133

    
134
        self.assertEqual(0, s_user.credit, 'SynnefoUser (pk=30000) should have zero credits (%d)' % ( s_user.credit, ))
135

    
136

    
137
class AuthTestCase(TestCase):
138
    fixtures = ['db_test_data']
139

    
140
    def _register_user(self):
141
        users.register_student ("Jimmy Page", "jpage", "jpage@zoso.com")
142
        self.user = SynnefoUser.objects.get(name = "jpage")
143

    
144
    def test_register(self):
145
        """ test user registration
146
        """
147
        self._register_user()
148
        self.assertNotEquals(self.user, None)
149

    
150
        #Check hash generation
151
        md5 = hashlib.md5()
152
        md5.update(self.user.uniq)
153
        md5.update(self.user.name)
154
        md5.update(time.asctime())
155

    
156
        self.assertEquals(self.user.auth_token, md5.hexdigest())
157

    
158
    def test_del_user(self):
159
        """ test user deletion
160
        """
161
        self._register_user()
162
        self.assertNotEquals(self.user, None)
163
        
164
        users.delete_user(self.user)
165

    
166
        self.assertRaises(SynnefoUser.DoesNotExist, SynnefoUser.objects.get,
167
                          name = "jpage")
168

    
169

    
170
class ProcessOpStatusTestCase(TestCase):
171
    fixtures = ['db_test_data']
172
    msg_op = {
173
        'instance': 'instance-name',
174
        'type': 'ganeti-op-status',
175
        'operation': 'OP_INSTANCE_STARTUP',
176
        'jobId': 0,
177
        'status': 'success',
178
        'logmsg': 'unittest - simulated message'
179
    }
180

    
181
    def test_op_startup_success(self):
182
        """Test notification for successful OP_INSTANCE_START"""
183
        msg = self.msg_op
184
        msg['operation'] = 'OP_INSTANCE_STARTUP'
185
        msg['status'] = 'success'
186

    
187
        # This machine is initially in BUILD
188
        vm = VirtualMachine.objects.get(pk=30002)
189
        backend.process_op_status(vm, msg["jobId"], msg["operation"],
190
                                  msg["status"], msg["logmsg"])
191
        self.assertEquals(get_rsapi_state(vm), 'ACTIVE')
192

    
193
    def test_op_shutdown_success(self):
194
        """Test notification for successful OP_INSTANCE_SHUTDOWN"""
195
        msg = self.msg_op
196
        msg['operation'] = 'OP_INSTANCE_SHUTDOWN'
197
        msg['status'] = 'success'
198

    
199
        # This machine is initially in BUILD
200
        vm = VirtualMachine.objects.get(pk=30002)
201
        backend.process_op_status(vm, msg["jobId"], msg["operation"],
202
                                  msg["status"], msg["logmsg"])
203
        self.assertEquals(get_rsapi_state(vm), 'STOPPED')
204

    
205
    def test_op_reboot_success(self):
206
        """Test notification for successful OP_INSTANCE_REBOOT"""
207
        msg = self.msg_op
208
        msg['operation'] = 'OP_INSTANCE_REBOOT'
209
        msg['status'] = 'success'
210

    
211
        # This machine is initially in BUILD
212
        vm = VirtualMachine.objects.get(pk=30002)
213
        backend.process_op_status(vm, msg["jobId"], msg["operation"],
214
                                  msg["status"], msg["logmsg"])
215
        self.assertEquals(get_rsapi_state(vm), 'ACTIVE')
216

    
217
    def test_op_create_success(self):
218
        """Test notification for successful OP_INSTANCE_CREATE"""
219
        msg = self.msg_op
220
        msg['operation'] = 'OP_INSTANCE_CREATE'
221
        msg['status'] = 'success'
222

    
223
        # This machine is initially in BUILD
224
        vm = VirtualMachine.objects.get(pk=30002)
225
        backend.process_op_status(vm, msg["jobId"], msg["operation"],
226
                                  msg["status"], msg["logmsg"])
227
        self.assertEquals(get_rsapi_state(vm), 'ACTIVE')
228

    
229
    def test_op_remove_success(self):
230
        """Test notification for successful OP_INSTANCE_REMOVE"""
231
        msg = self.msg_op
232
        msg['operation'] = 'OP_INSTANCE_REMOVE'
233
        msg['status'] = 'success'
234

    
235
        # This machine is initially in BUILD
236
        vm = VirtualMachine.objects.get(pk=30002)
237
        backend.process_op_status(vm, msg["jobId"], msg["operation"],
238
                                  msg["status"], msg["logmsg"])
239
        self.assertEquals(get_rsapi_state(vm), 'DELETED')
240
        self.assertTrue(vm.deleted)
241

    
242
    def test_op_create_error(self):
243
        """Test notification for failed OP_INSTANCE_CREATE"""
244
        msg = self.msg_op
245
        msg['operation'] = 'OP_INSTANCE_CREATE'
246
        msg['status'] = 'error'
247

    
248
        # This machine is initially in BUILD
249
        vm = VirtualMachine.objects.get(pk=30002)
250
        backend.process_op_status(vm, msg["jobId"], msg["operation"],
251
                                  msg["status"], msg["logmsg"])
252
        self.assertEquals(get_rsapi_state(vm), 'ERROR')
253
        self.assertFalse(vm.deleted)
254

    
255
    def test_remove_machine_in_error(self):
256
        """Test notification for failed OP_INSTANCE_REMOVE, server in ERROR"""
257
        msg = self.msg_op
258
        msg['operation'] = 'OP_INSTANCE_REMOVE'
259
        msg['status'] = 'error'
260

    
261
        # This machine is initially in BUILD
262
        vm = VirtualMachine.objects.get(pk=30002)
263
        backend.process_op_status(vm, 0, "OP_INSTANCE_CREATE", "error", "test")
264
        self.assertEquals(get_rsapi_state(vm), 'ERROR')
265

    
266
        backend.process_op_status(vm, msg["jobId"], msg["operation"],
267
                                  msg["status"], msg["logmsg"])
268
        self.assertEquals(get_rsapi_state(vm), 'DELETED')
269
        self.assertTrue(vm.deleted)
270

    
271

    
272
class ProcessNetStatusTestCase(TestCase):
273
    fixtures = ['db_test_data']
274

    
275
    def test_set_ipv4(self):
276
        """Test reception of a net status notification"""
277
        msg = {'instance': 'instance-name',
278
               'type':     'ganeti-net-status',
279
               'nics': [
280
                   {'ip': '192.168.33.1', 'mac': 'aa:00:00:58:1e:b9'}
281
               ]
282
        }
283
        vm = VirtualMachine.objects.get(pk=30000)
284
        backend.process_net_status(vm, msg['nics'])
285
        self.assertEquals(vm.nics.all()[0].ipv4, '192.168.33.1')
286

    
287
    def test_set_empty_ipv4(self):
288
        """Test reception of a net status notification with no IPv4 assigned"""
289
        msg = {'instance': 'instance-name',
290
               'type':     'ganeti-net-status',
291
               'nics': [
292
                   {'ip': '', 'mac': 'aa:00:00:58:1e:b9'}
293
               ]
294
        }
295
        vm = VirtualMachine.objects.get(pk=30000)
296
        backend.process_net_status(vm, msg['nics'])
297
        self.assertEquals(vm.nics.all()[0].ipv4, '')
298

    
299

    
300
class UsersTestCase(TestCase):
301
    def test_create_uname(self):
302
        username = users.create_uname("Donald Knuth")
303
        self.assertEquals(username, "knuthd")
304

    
305
        username = users.create_uname("Nemichandra Siddhanta Chakravati")
306
        self.assertEquals(username, "chakravn")
307

    
308
        username = users.create_uname(u'Γεώργιος Παπαγεωργίου')
309
        self.assertEquals(username, u'παπαγεωγ')
310

    
311

    
312
class ProcessProgressUpdateTestCase(TestCase):
313
    fixtures = ['db_test_data']
314

    
315
    def test_progress_update(self):
316
        """Test reception of a create progress notification"""
317

    
318
        # This machine is in BUILD
319
        vm = VirtualMachine.objects.get(pk=30002)
320
        rprogress = randint(10, 100)
321

    
322
        backend.process_create_progress(vm, rprogress, 0)
323
        self.assertEquals(vm.buildpercentage, rprogress)
324

    
325
        #self.assertRaises(ValueError, backend.process_create_progress,
326
        #                  vm, 9, 0)
327
        self.assertRaises(ValueError, backend.process_create_progress,
328
                          vm, -1, 0)
329
        self.assertRaises(ValueError, backend.process_create_progress,
330
                          vm, 'a', 0)
331

    
332
        # This machine is ACTIVE
333
        #vm = VirtualMachine.objects.get(pk=30000)
334
        #self.assertRaises(VirtualMachine.IllegalState,
335
        #                  backend.process_create_progress, vm, 1)
336

    
337

    
338
class ReconciliationTestCase(TestCase):
339
    SERVERS = 1000
340
    fixtures = ['db_test_data']
341

    
342
    def test_get_servers_from_db(self):
343
        """Test getting a dictionary from each server to its operstate"""
344
        reconciliation.get_servers_from_db()
345
        self.assertEquals(reconciliation.get_servers_from_db(),
346
                          {30000: 'STARTED', 30001: 'STOPPED', 30002: 'BUILD'})
347

    
348
    def test_stale_servers_in_db(self):
349
        """Test discovery of stale entries in DB"""
350

    
351
        D = {1: 'STARTED', 2: 'STOPPED', 3: 'STARTED', 4: 'BUILD', 5: 'BUILD'}
352
        G = {1: True, 3: True}
353
        self.assertEquals(reconciliation.stale_servers_in_db(D, G),
354
                          set([2, 4, 5]))
355

    
356
    def test_orphan_instances_in_ganeti(self):
357
        """Test discovery of orphan instances in Ganeti, without a DB entry"""
358

    
359
        G = {1: True, 2: False, 3: False, 4: True, 50: True}
360
        D = {1: True, 3: False}
361
        self.assertEquals(reconciliation.orphan_instances_in_ganeti(D, G),
362
                          set([2, 4, 50]))
363

    
364
    def test_unsynced_operstate(self):
365
        """Test discovery of unsynced operstate between the DB and Ganeti"""
366

    
367
        G = {1: True, 2: False, 3: True, 4: False, 50: True}
368
        D = {1: 'STARTED', 2: 'STARTED', 3: 'BUILD', 4: 'STARTED', 50: 'BUILD'}
369
        self.assertEquals(reconciliation.unsynced_operstate(D, G),
370
                          set([(2, 'STARTED', False),
371
                           (3, 'BUILD', True), (4, 'STARTED', False),
372
                           (50, 'BUILD', True)]))