~ubuntu-branches/ubuntu/raring/maas/raring-updates

« back to all changes in this revision

Viewing changes to src/maasserver/tests/test_api.py

  • Committer: Package Import Robot
  • Author(s): Andres Rodriguez
  • Date: 2012-07-03 17:42:37 UTC
  • mfrom: (1.1.13)
  • Revision ID: package-import@ubuntu.com-20120703174237-p8l0keuuznfg721k
Tags: 0.1+bzr709+dfsg-0ubuntu1
* New Upstream release
* debian/control:
  - Depends on python-celery, python-tempita, libjs-yui3-{full,min},
    libjs-raphael
* debian/maas.install:
  - Install apiclient, celeryconfig.py, maas-import-pxe-files, preseeds_v2.
  - Update to install various files from chroot, rather tha manually copy
    them from the source.
* debian/maas.links: symlink celeryconfig.py
* debian/maas.maas-celery.upstart: Add job.
* debian/rules:
  - Install celery upstart job.
  - Do not install jslibs as packages are now used.
  - Drop copying of maas_local_settings_sample.py as source now ships
    a maas_local_settings.py
* debian/patches:
  - 04-maas-http-fix.patch: Drop. Merged upstream.
  - 01-fix-database-settings.patch: Refreshed.
  - 99_enums_js.patch: Added until creation of enum.js / build process
    is fixed.
* debian/maas.postinst: Update bzr version to correctly handle upgrades.

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
__metaclass__ = type
13
13
__all__ = []
14
14
 
 
15
from abc import (
 
16
    ABCMeta,
 
17
    abstractproperty,
 
18
    )
15
19
from base64 import b64encode
 
20
from collections import namedtuple
 
21
from datetime import (
 
22
    datetime,
 
23
    timedelta,
 
24
    )
16
25
import httplib
17
26
import json
18
27
import os
20
29
import shutil
21
30
 
22
31
from django.conf import settings
 
32
from django.contrib.auth.models import AnonymousUser
23
33
from django.db.models.signals import post_save
24
34
from django.http import QueryDict
25
35
from fixtures import Fixture
27
37
from maasserver.api import (
28
38
    extract_constraints,
29
39
    extract_oauth_key,
30
 
    )
 
40
    extract_oauth_key_from_auth_header,
 
41
    get_oauth_token,
 
42
    get_overrided_query_dict,
 
43
    )
 
44
from maasserver.enum import (
 
45
    ARCHITECTURE_CHOICES,
 
46
    NODE_AFTER_COMMISSIONING_ACTION,
 
47
    NODE_STATUS,
 
48
    NODE_STATUS_CHOICES_DICT,
 
49
    )
 
50
from maasserver.exceptions import Unauthorized
31
51
from maasserver.models import (
32
 
    ARCHITECTURE_CHOICES,
33
52
    Config,
 
53
    MACAddress,
 
54
    Node,
 
55
    )
 
56
from maasserver.models.user import (
34
57
    create_auth_token,
35
58
    get_auth_tokens,
36
 
    MACAddress,
37
 
    Node,
38
 
    NODE_STATUS,
39
 
    NODE_STATUS_CHOICES_DICT,
40
59
    )
41
60
from maasserver.testing import (
42
61
    reload_object,
43
62
    reload_objects,
44
63
    )
45
 
from maasserver.testing.enum import map_enum
46
64
from maasserver.testing.factory import factory
47
65
from maasserver.testing.oauthclient import OAuthAuthenticatedClient
48
66
from maasserver.testing.testcase import (
50
68
    LoggedInTestCase,
51
69
    TestCase,
52
70
    )
53
 
from maastesting.testcase import TransactionTestCase
 
71
from maasserver.utils import map_enum
 
72
from maastesting.djangotestcase import TransactionTestCase
54
73
from metadataserver.models import (
55
74
    NodeKey,
56
75
    NodeUserData,
57
76
    )
58
77
from metadataserver.nodeinituser import get_node_init_user
59
 
from provisioningserver.enum import POWER_TYPE
 
78
from provisioningserver.enum import (
 
79
    POWER_TYPE,
 
80
    POWER_TYPE_CHOICES,
 
81
    )
60
82
 
61
83
 
62
84
class APIv10TestMixin:
72
94
 
73
95
class TestModuleHelpers(TestCase):
74
96
 
75
 
    def test_extract_oauth_key_extracts_oauth_token_from_oauth_header(self):
76
 
        token = factory.getRandomString(18)
77
 
        self.assertEqual(
78
 
            token,
79
 
            extract_oauth_key(factory.make_oauth_header(oauth_token=token)))
80
 
 
81
 
    def test_extract_oauth_key_returns_None_without_oauth_key(self):
82
 
        self.assertIs(None, extract_oauth_key(''))
 
97
    def make_fake_request(self, auth_header):
 
98
        """Create a very simple fake request, with just an auth header."""
 
99
        FakeRequest = namedtuple('FakeRequest', ['META'])
 
100
        return FakeRequest(META={'HTTP_AUTHORIZATION': auth_header})
 
101
 
 
102
    def test_extract_oauth_key_from_auth_header_returns_key(self):
 
103
        token = factory.getRandomString(18)
 
104
        self.assertEqual(
 
105
            token,
 
106
            extract_oauth_key_from_auth_header(
 
107
                factory.make_oauth_header(oauth_token=token)))
 
108
 
 
109
    def test_extract_oauth_key_from_auth_header_returns_None_if_missing(self):
 
110
        self.assertIs(None, extract_oauth_key_from_auth_header(''))
 
111
 
 
112
    def test_extract_oauth_key_raises_Unauthorized_if_no_auth_header(self):
 
113
        self.assertRaises(
 
114
            Unauthorized,
 
115
            extract_oauth_key, self.make_fake_request(None))
 
116
 
 
117
    def test_extract_oauth_key_raises_Unauthorized_if_no_key(self):
 
118
        self.assertRaises(
 
119
            Unauthorized,
 
120
            extract_oauth_key, self.make_fake_request(''))
 
121
 
 
122
    def test_extract_oauth_key_returns_key(self):
 
123
        token = factory.getRandomString(18)
 
124
        self.assertEqual(
 
125
            token,
 
126
            extract_oauth_key(self.make_fake_request(
 
127
                factory.make_oauth_header(oauth_token=token))))
 
128
 
 
129
    def test_get_oauth_token_finds_token(self):
 
130
        user = factory.make_user()
 
131
        consumer, token = user.get_profile().create_authorisation_token()
 
132
        self.assertEqual(
 
133
            token,
 
134
            get_oauth_token(
 
135
                self.make_fake_request(
 
136
                    factory.make_oauth_header(oauth_token=token.key))))
 
137
 
 
138
    def test_get_oauth_token_raises_Unauthorized_for_unknown_token(self):
 
139
        fake_token = factory.getRandomString(18)
 
140
        header = factory.make_oauth_header(oauth_token=fake_token)
 
141
        self.assertRaises(
 
142
            Unauthorized,
 
143
            get_oauth_token, self.make_fake_request(header))
83
144
 
84
145
    def test_extract_constraints_ignores_unknown_parameters(self):
85
146
        unknown_parameter = "%s=%s" % (
95
156
            {'name': name},
96
157
            extract_constraints(QueryDict('name=%s' % name)))
97
158
 
98
 
 
99
 
class EnlistmentAPITest(APIv10TestMixin):
100
 
    # This is a mixin containing enlistement tests.  We will run this for:
101
 
    # an anonymous user, a simple (non-admin) user and an admin user.
102
 
    # XXX: rvb 2012-04-10 bug=978035: It would be better to use
103
 
    # testscenarios for this.
 
159
    def test_get_overrided_query_dict_returns_QueryDict(self):
 
160
        defaults = {factory.getRandomString(): factory.getRandomString()}
 
161
        results = get_overrided_query_dict(defaults, QueryDict(''))
 
162
        expected_results = QueryDict('').copy()
 
163
        expected_results.update(defaults)
 
164
        self.assertEqual(expected_results, results)
 
165
 
 
166
    def test_get_overrided_query_dict_values_in_data_replaces_defaults(self):
 
167
        key = factory.getRandomString()
 
168
        defaults = {key: factory.getRandomString()}
 
169
        data_value = factory.getRandomString()
 
170
        data = {key: data_value}
 
171
        results = get_overrided_query_dict(defaults, data)
 
172
        self.assertEqual([data_value], results.getlist(key))
 
173
 
 
174
 
 
175
class MultipleUsersScenarios:
 
176
    """A mixin that uses testscenarios to repeat a testcase as different
 
177
    users.
 
178
 
 
179
    The scenarios should inject a `userfactory` variable that will
 
180
    be called to produce the user used in the tests e.g.:
 
181
 
 
182
    class ExampleTest(MultipleUsersScenarios, TestCase):
 
183
        scenarios = [
 
184
            ('anon', dict(userfactory=lambda: AnonymousUser())),
 
185
            ('user', dict(userfactory=factory.make_user)),
 
186
            ('admin', dict(userfactory=factory.make_admin)),
 
187
            ]
 
188
 
 
189
        def test_something(self):
 
190
            pass
 
191
 
 
192
    The test `test_something` with be run 3 times: one with a anonymous user
 
193
    logged in, once with a simple (non-admin) user logged in and once with
 
194
    an admin user logged in.
 
195
    """
 
196
 
 
197
    __metaclass__ = ABCMeta
 
198
 
 
199
    scenarios = abstractproperty(
 
200
        "The scenarios as defined by testscenarios.")
 
201
 
 
202
    def setUp(self):
 
203
        super(MultipleUsersScenarios, self).setUp()
 
204
        user = self.userfactory()
 
205
        if not user.is_anonymous():
 
206
            password = factory.getRandomString()
 
207
            user.set_password(password)
 
208
            user.save()
 
209
            self.logged_in_user = user
 
210
            self.client.login(
 
211
                username=self.logged_in_user.username, password=password)
 
212
 
 
213
 
 
214
class EnlistmentAPITest(APIv10TestMixin, MultipleUsersScenarios, TestCase):
 
215
    """Enlistment tests."""
 
216
    scenarios = [
 
217
        ('anon', dict(userfactory=lambda: AnonymousUser())),
 
218
        ('user', dict(userfactory=factory.make_user)),
 
219
        ('admin', dict(userfactory=factory.make_admin)),
 
220
        ]
104
221
 
105
222
    def test_POST_new_creates_node(self):
106
223
        # The API allows a Node to be created.
111
228
                'op': 'new',
112
229
                'hostname': 'diane',
113
230
                'architecture': architecture,
114
 
                'after_commissioning_action': '2',
 
231
                'after_commissioning_action':
 
232
                    NODE_AFTER_COMMISSIONING_ACTION.DEFAULT,
115
233
                'mac_addresses': ['aa:bb:cc:dd:ee:ff', '22:bb:cc:dd:ee:ff'],
116
234
            })
117
235
        parsed_result = json.loads(response.content)
121
239
        self.assertEqual('diane', parsed_result['hostname'])
122
240
        self.assertNotEqual(0, len(parsed_result.get('system_id')))
123
241
        [diane] = Node.objects.filter(hostname='diane')
124
 
        # XXX JeroenVermeulen 2012-04-12, bug=979539: re-enable.
125
 
        #self.assertEqual(2, diane.after_commissioning_action)
126
242
        self.assertEqual(architecture, diane.architecture)
127
243
 
128
244
    def test_POST_new_power_type_defaults_to_asking_config(self):
137
253
            system_id=json.loads(response.content)['system_id'])
138
254
        self.assertEqual(POWER_TYPE.DEFAULT, node.power_type)
139
255
 
140
 
    def test_POST_new_sets_power_type(self):
141
 
        architecture = factory.getRandomChoice(ARCHITECTURE_CHOICES)
142
 
        response = self.client.post(
143
 
            self.get_uri('nodes/'), {
144
 
                'op': 'new',
145
 
                'architecture': architecture,
146
 
                'power_type': POWER_TYPE.VIRSH,
147
 
                'mac_addresses': ['00:11:22:33:44:55'],
148
 
                })
149
 
        node = Node.objects.get(
150
 
            system_id=json.loads(response.content)['system_id'])
151
 
        self.assertEqual(POWER_TYPE.VIRSH, node.power_type)
152
 
 
153
256
    def test_POST_new_associates_mac_addresses(self):
154
257
        # The API allows a Node to be created and associated with MAC
155
258
        # Addresses.
160
263
                'op': 'new',
161
264
                'hostname': 'diane',
162
265
                'architecture': architecture,
163
 
                'after_commissioning_action': '2',
 
266
                'after_commissioning_action':
 
267
                    NODE_AFTER_COMMISSIONING_ACTION.DEFAULT,
164
268
                'mac_addresses': ['aa:bb:cc:dd:ee:ff', '22:bb:cc:dd:ee:ff'],
165
269
            })
166
270
        [diane] = Node.objects.filter(hostname='diane')
267
371
        self.assertEqual(httplib.BAD_REQUEST, response.status_code)
268
372
        self.assertIn('application/json', response['Content-Type'])
269
373
        self.assertEqual(
270
 
            ["One or more MAC Addresses is invalid."],
 
374
            ["One or more MAC addresses is invalid."],
271
375
            parsed_result['mac_addresses'])
272
376
 
273
377
    def test_POST_invalid_architecture_returns_bad_request(self):
288
392
        self.assertItemsEqual(['architecture'], parsed_result)
289
393
 
290
394
 
291
 
class NonAdminEnlistmentAPITest(EnlistmentAPITest):
292
 
    # This is a mixin containing enlistement tests for non-admin users.
 
395
class NonAdminEnlistmentAPITest(APIv10TestMixin, MultipleUsersScenarios,
 
396
                                TestCase):
 
397
    # Enlistment tests for non-admin users.
 
398
 
 
399
    scenarios = [
 
400
        ('anon', dict(userfactory=lambda: AnonymousUser())),
 
401
        ('user', dict(userfactory=factory.make_user)),
 
402
        ]
293
403
 
294
404
    def test_POST_non_admin_creates_node_in_declared_state(self):
295
405
        # Upon non-admin enlistment, a node goes into the Declared
301
411
                'op': 'new',
302
412
                'hostname': factory.getRandomString(),
303
413
                'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
304
 
                'after_commissioning_action': '2',
 
414
                'after_commissioning_action':
 
415
                    NODE_AFTER_COMMISSIONING_ACTION.DEFAULT,
305
416
                'mac_addresses': ['aa:bb:cc:dd:ee:ff'],
306
417
            })
307
418
        self.assertEqual(httplib.OK, response.status_code)
311
422
            Node.objects.get(system_id=system_id).status)
312
423
 
313
424
 
314
 
class AnonymousEnlistmentAPITest(NonAdminEnlistmentAPITest, TestCase):
315
 
    # This is an actual test case that uses the NonAdminEnlistmentAPITest
316
 
    # mixin and adds enlistement tests specific to anonymous users.
 
425
class AnonymousEnlistmentAPITest(APIv10TestMixin, TestCase):
 
426
    # Enlistment tests specific to anonymous users.
317
427
 
318
428
    def test_POST_accept_not_allowed(self):
319
429
        # An anonymous user is not allowed to accept an anonymously
333
443
                'op': 'new',
334
444
                'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
335
445
                'hostname': factory.getRandomString(),
336
 
                'after_commissioning_action': '2',
 
446
                'after_commissioning_action':
 
447
                    NODE_AFTER_COMMISSIONING_ACTION.DEFAULT,
337
448
                'mac_addresses': ['aa:bb:cc:dd:ee:ff', '22:bb:cc:dd:ee:ff'],
338
449
            })
339
450
        parsed_result = json.loads(response.content)
340
451
        self.assertItemsEqual(
341
452
            [
342
 
                'hostname', 'system_id', 'macaddress_set', 'architecture',
 
453
                'hostname',
 
454
                'system_id',
 
455
                'macaddress_set',
 
456
                'architecture',
343
457
                'status',
 
458
                'netboot',
 
459
                'power_type',
 
460
                'power_parameters',
344
461
            ],
345
462
            list(parsed_result))
346
463
 
347
464
 
348
 
class SimpleUserLoggedInEnlistmentAPITest(NonAdminEnlistmentAPITest,
349
 
                                          LoggedInTestCase):
350
 
    # This is an actual test case that uses the NonAdminEnlistmentAPITest
351
 
    # mixin plus enlistement tests specific to simple (non-admin) users.
 
465
class SimpleUserLoggedInEnlistmentAPITest(APIv10TestMixin, LoggedInTestCase):
 
466
    # Enlistment tests specific to simple (non-admin) users.
352
467
 
353
468
    def test_POST_accept_not_allowed(self):
354
469
        # An non-admin user is not allowed to accept an anonymously
363
478
                "following node(s): %s." % node_id),
364
479
            (response.status_code, response.content))
365
480
 
 
481
    def test_POST_simple_user_cannot_set_power_type_and_parameters(self):
 
482
        new_power_address = factory.getRandomString()
 
483
        response = self.client.post(
 
484
            self.get_uri('nodes/'), {
 
485
                'op': 'new',
 
486
                'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
 
487
                'power_type': POWER_TYPE.WAKE_ON_LAN,
 
488
                'power_parameters_power_address': new_power_address,
 
489
                'mac_addresses': ['AA:BB:CC:DD:EE:FF'],
 
490
                })
 
491
 
 
492
        node = Node.objects.get(
 
493
            system_id=json.loads(response.content)['system_id'])
 
494
        self.assertEqual(
 
495
                (httplib.OK, '', POWER_TYPE.DEFAULT),
 
496
                (response.status_code, node.power_parameters,
 
497
                    node.power_type))
 
498
 
366
499
    def test_POST_returns_limited_fields(self):
367
500
        response = self.client.post(
368
501
            self.get_uri('nodes/'),
370
503
                'op': 'new',
371
504
                'hostname': factory.getRandomString(),
372
505
                'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
373
 
                'after_commissioning_action': '2',
 
506
                'after_commissioning_action':
 
507
                    NODE_AFTER_COMMISSIONING_ACTION.DEFAULT,
374
508
                'mac_addresses': ['aa:bb:cc:dd:ee:ff', '22:bb:cc:dd:ee:ff'],
375
509
            })
376
510
        parsed_result = json.loads(response.content)
377
511
        self.assertItemsEqual(
378
512
            [
379
 
                'hostname', 'system_id', 'macaddress_set', 'architecture',
380
 
                'status', 'resource_uri',
 
513
                'hostname',
 
514
                'system_id',
 
515
                'macaddress_set',
 
516
                'architecture',
 
517
                'status',
 
518
                'netboot',
 
519
                'power_type',
 
520
                'power_parameters',
 
521
                'resource_uri',
381
522
            ],
382
523
            list(parsed_result))
383
524
 
384
525
 
385
 
class AdminLoggedInEnlistmentAPITest(EnlistmentAPITest,
386
 
                                     AdminLoggedInTestCase):
387
 
    # This is an actual test case that uses the EnlistmentAPITest mixin
388
 
    # and adds enlistement tests specific to admin users.
 
526
class AdminLoggedInEnlistmentAPITest(APIv10TestMixin, AdminLoggedInTestCase):
 
527
    # Enlistment tests specific to admin users.
 
528
 
 
529
    def test_POST_new_creates_node_default_values_for_power_settings(self):
 
530
        architecture = factory.getRandomChoice(ARCHITECTURE_CHOICES)
 
531
        mac_address = 'AA:BB:CC:DD:EE:FF'
 
532
        response = self.client.post(
 
533
            self.get_uri('nodes/'), {
 
534
                'op': 'new',
 
535
                'architecture': architecture,
 
536
                'mac_addresses': [mac_address],
 
537
                })
 
538
        node = Node.objects.get(
 
539
            system_id=json.loads(response.content)['system_id'])
 
540
        self.assertAttributes(
 
541
            node,
 
542
            dict(
 
543
                architecture=architecture, power_type=POWER_TYPE.DEFAULT,
 
544
                power_parameters=''))
 
545
 
 
546
    def test_POST_new_sets_power_type_if_admin(self):
 
547
        response = self.client.post(
 
548
            self.get_uri('nodes/'), {
 
549
                'op': 'new',
 
550
                'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
 
551
                'power_type': POWER_TYPE.WAKE_ON_LAN,
 
552
                'mac_addresses': ['00:11:22:33:44:55'],
 
553
                })
 
554
        node = Node.objects.get(
 
555
            system_id=json.loads(response.content)['system_id'])
 
556
        self.assertEqual(POWER_TYPE.WAKE_ON_LAN, node.power_type)
 
557
        self.assertEqual('', node.power_parameters)
 
558
 
 
559
    def test_POST_new_sets_power_parameters_field(self):
 
560
        # The api allows the setting of a Node's power_parameters field.
 
561
        # Create a power_parameter valid for the selected power_type.
 
562
        new_power_address = factory.getRandomString()
 
563
        response = self.client.post(
 
564
            self.get_uri('nodes/'), {
 
565
                'op': 'new',
 
566
                'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
 
567
                'power_type': POWER_TYPE.WAKE_ON_LAN,
 
568
                'power_parameters_power_address': new_power_address,
 
569
                'mac_addresses': ['AA:BB:CC:DD:EE:FF'],
 
570
                })
 
571
 
 
572
        node = Node.objects.get(
 
573
            system_id=json.loads(response.content)['system_id'])
 
574
        self.assertEqual(httplib.OK, response.status_code)
 
575
        self.assertEqual(
 
576
            {'power_address': new_power_address},
 
577
            reload_object(node).power_parameters)
 
578
 
 
579
    def test_POST_updates_power_parameters_rejects_unknown_param(self):
 
580
        hostname = factory.getRandomString()
 
581
        response = self.client.post(
 
582
            self.get_uri('nodes/'), {
 
583
                'op': 'new',
 
584
                'hostname': hostname,
 
585
                'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
 
586
                'power_type': POWER_TYPE.WAKE_ON_LAN,
 
587
                'power_parameters_unknown_param': factory.getRandomString(),
 
588
                'mac_addresses': [factory.getRandomMACAddress()],
 
589
                })
 
590
 
 
591
        self.assertEqual(
 
592
            (
 
593
                httplib.BAD_REQUEST,
 
594
                {'power_parameters': ["Unknown parameter(s): unknown_param."]}
 
595
            ),
 
596
            (response.status_code, json.loads(response.content)))
 
597
        self.assertFalse(Node.objects.filter(hostname=hostname).exists())
 
598
 
 
599
    def test_POST_new_sets_power_parameters_skip_check(self):
 
600
        # The api allows to skip the validation step and set arbitrary
 
601
        # power parameters.
 
602
        param = factory.getRandomString()
 
603
        response = self.client.post(
 
604
            self.get_uri('nodes/'), {
 
605
                'op': 'new',
 
606
                'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
 
607
                'power_type': POWER_TYPE.WAKE_ON_LAN,
 
608
                'power_parameters_param': param,
 
609
                'power_parameters_skip_check': 'true',
 
610
                'mac_addresses': ['AA:BB:CC:DD:EE:FF'],
 
611
                })
 
612
 
 
613
        node = Node.objects.get(
 
614
            system_id=json.loads(response.content)['system_id'])
 
615
        self.assertEqual(httplib.OK, response.status_code)
 
616
        self.assertEqual(
 
617
            {'param': param},
 
618
            reload_object(node).power_parameters)
389
619
 
390
620
    def test_POST_admin_creates_node_in_commissioning_state(self):
391
621
        # When an admin user enlists a node, it goes into the
396
626
                'op': 'new',
397
627
                'hostname': factory.getRandomString(),
398
628
                'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
399
 
                'after_commissioning_action': '2',
 
629
                'after_commissioning_action':
 
630
                    NODE_AFTER_COMMISSIONING_ACTION.DEFAULT,
400
631
                'mac_addresses': ['aa:bb:cc:dd:ee:ff'],
401
632
            })
402
633
        self.assertEqual(httplib.OK, response.status_code)
412
643
                'op': 'new',
413
644
                'hostname': factory.getRandomString(),
414
645
                'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
415
 
                'after_commissioning_action': '2',
 
646
                'after_commissioning_action':
 
647
                    NODE_AFTER_COMMISSIONING_ACTION.DEFAULT,
416
648
                'mac_addresses': ['aa:bb:cc:dd:ee:ff', '22:bb:cc:dd:ee:ff'],
417
649
            })
418
650
        parsed_result = json.loads(response.content)
419
651
        self.assertItemsEqual(
420
652
            [
421
 
                'hostname', 'system_id', 'macaddress_set', 'architecture',
422
 
                'status', 'resource_uri',
 
653
                'hostname',
 
654
                'system_id',
 
655
                'macaddress_set',
 
656
                'architecture',
 
657
                'status',
 
658
                'netboot',
 
659
                'power_type',
 
660
                'power_parameters',
 
661
                'resource_uri',
423
662
            ],
424
663
            list(parsed_result))
425
664
 
449
688
            (response.status_code, response.content))
450
689
 
451
690
    def test_is_registered_normalizes_mac_address(self):
452
 
        # These two non-normalized MAC Addresses are the same.
 
691
        # These two non-normalized MAC addresses are the same.
453
692
        non_normalized_mac_address = 'AA-bb-cc-dd-ee-ff'
454
693
        non_normalized_mac_address2 = 'aabbccddeeff'
455
694
        factory.make_mac_address(non_normalized_mac_address)
584
823
            node.system_id, json.loads(response.content)['system_id'])
585
824
 
586
825
    def test_POST_stop_may_be_repeated(self):
587
 
        node = factory.make_node(owner=self.logged_in_user)
 
826
        node = factory.make_node(
 
827
            owner=self.logged_in_user, mac=True,
 
828
            power_type=POWER_TYPE.WAKE_ON_LAN)
588
829
        self.client.post(self.get_node_uri(node), {'op': 'stop'})
589
830
        response = self.client.post(self.get_node_uri(node), {'op': 'stop'})
590
831
        self.assertEqual(httplib.OK, response.status_code)
595
836
        self.assertEqual(httplib.FORBIDDEN, response.status_code)
596
837
 
597
838
    def test_POST_start_returns_node(self):
598
 
        node = factory.make_node(owner=self.logged_in_user)
 
839
        node = factory.make_node(
 
840
            owner=self.logged_in_user, mac=True,
 
841
            power_type=POWER_TYPE.WAKE_ON_LAN)
599
842
        response = self.client.post(self.get_node_uri(node), {'op': 'start'})
600
843
        self.assertEqual(httplib.OK, response.status_code)
601
844
        self.assertEqual(
602
845
            node.system_id, json.loads(response.content)['system_id'])
603
846
 
604
847
    def test_POST_start_may_be_repeated(self):
605
 
        node = factory.make_node(owner=self.logged_in_user)
 
848
        node = factory.make_node(
 
849
            owner=self.logged_in_user, mac=True,
 
850
            power_type=POWER_TYPE.WAKE_ON_LAN)
606
851
        self.client.post(self.get_node_uri(node), {'op': 'start'})
607
852
        response = self.client.post(self.get_node_uri(node), {'op': 'start'})
608
853
        self.assertEqual(httplib.OK, response.status_code)
609
854
 
610
855
    def test_POST_start_stores_user_data(self):
611
 
        node = factory.make_node(owner=self.logged_in_user)
 
856
        node = factory.make_node(
 
857
            owner=self.logged_in_user, mac=True,
 
858
            power_type=POWER_TYPE.WAKE_ON_LAN)
612
859
        user_data = (
613
860
            b'\xff\x00\xff\xfe\xff\xff\xfe' +
614
861
            factory.getRandomString().encode('ascii'))
683
930
        self.assertEqual(
684
931
            [httplib.CONFLICT] * len(unreleasable_statuses),
685
932
            [response.status_code for response in responses])
686
 
        self.assertEqual(
 
933
        self.assertItemsEqual(
687
934
            unreleasable_statuses,
688
935
            [node.status for node in reload_objects(Node, nodes)])
689
936
 
738
985
        self.assertEqual(0, Node.objects.filter(hostname='diane').count())
739
986
        self.assertEqual(1, Node.objects.filter(hostname='francis').count())
740
987
 
 
988
    def test_PUT_ignores_unknown_fields(self):
 
989
        node = factory.make_node(
 
990
            owner=self.logged_in_user,
 
991
            after_commissioning_action=(
 
992
                NODE_AFTER_COMMISSIONING_ACTION.DEFAULT))
 
993
        field = factory.getRandomString()
 
994
        response = self.client.put(
 
995
            self.get_node_uri(node),
 
996
            {field: factory.getRandomString()}
 
997
            )
 
998
 
 
999
        self.assertEqual(httplib.OK, response.status_code)
 
1000
 
 
1001
    def test_PUT_admin_can_change_power_type(self):
 
1002
        self.become_admin()
 
1003
        original_power_type = factory.getRandomChoice(
 
1004
            POWER_TYPE_CHOICES)
 
1005
        new_power_type = factory.getRandomChoice(
 
1006
            POWER_TYPE_CHOICES, but_not=original_power_type)
 
1007
        node = factory.make_node(
 
1008
            owner=self.logged_in_user,
 
1009
            power_type=original_power_type,
 
1010
            after_commissioning_action=(
 
1011
                NODE_AFTER_COMMISSIONING_ACTION.DEFAULT))
 
1012
        self.client.put(
 
1013
            self.get_node_uri(node),
 
1014
            {'power_type': new_power_type}
 
1015
            )
 
1016
 
 
1017
        self.assertEqual(
 
1018
            new_power_type, reload_object(node).power_type)
 
1019
 
 
1020
    def test_PUT_non_admin_cannot_change_power_type(self):
 
1021
        original_power_type = factory.getRandomChoice(
 
1022
            POWER_TYPE_CHOICES)
 
1023
        new_power_type = factory.getRandomChoice(
 
1024
            POWER_TYPE_CHOICES, but_not=original_power_type)
 
1025
        node = factory.make_node(
 
1026
            owner=self.logged_in_user,
 
1027
            power_type=original_power_type,
 
1028
            after_commissioning_action=(
 
1029
                NODE_AFTER_COMMISSIONING_ACTION.DEFAULT))
 
1030
        self.client.put(
 
1031
            self.get_node_uri(node),
 
1032
            {'power_type': new_power_type}
 
1033
            )
 
1034
 
 
1035
        self.assertEqual(
 
1036
            original_power_type, reload_object(node).power_type)
 
1037
 
741
1038
    def test_resource_uri_points_back_at_node(self):
742
1039
        # When a Node is returned by the API, the field 'resource_uri'
743
1040
        # provides the URI for this Node.
782
1079
 
783
1080
        self.assertEqual(httplib.NOT_FOUND, response.status_code)
784
1081
 
 
1082
    def test_PUT_updates_power_parameters_field(self):
 
1083
        # The api allows the updating of a Node's power_parameters field.
 
1084
        self.become_admin()
 
1085
        node = factory.make_node(
 
1086
            owner=self.logged_in_user,
 
1087
            power_type=POWER_TYPE.WAKE_ON_LAN)
 
1088
        # Create a power_parameter valid for the selected power_type.
 
1089
        new_power_address = factory.getRandomString()
 
1090
        response = self.client.put(
 
1091
            self.get_node_uri(node),
 
1092
            {'power_parameters_power_address': new_power_address})
 
1093
 
 
1094
        self.assertEqual(httplib.OK, response.status_code)
 
1095
        self.assertEqual(
 
1096
            {'power_address': new_power_address},
 
1097
            reload_object(node).power_parameters)
 
1098
 
 
1099
    def test_PUT_updates_power_parameters_rejects_unknown_param(self):
 
1100
        self.become_admin()
 
1101
        power_parameters = factory.getRandomString()
 
1102
        node = factory.make_node(
 
1103
            owner=self.logged_in_user,
 
1104
            power_type=POWER_TYPE.WAKE_ON_LAN,
 
1105
            power_parameters=power_parameters)
 
1106
        response = self.client.put(
 
1107
            self.get_node_uri(node),
 
1108
            {'power_parameters_unknown_param': factory.getRandomString()})
 
1109
 
 
1110
        self.assertEqual(
 
1111
            (
 
1112
                httplib.BAD_REQUEST,
 
1113
                {'power_parameters': ["Unknown parameter(s): unknown_param."]}
 
1114
            ),
 
1115
            (response.status_code, json.loads(response.content)))
 
1116
        self.assertEqual(
 
1117
            power_parameters, reload_object(node).power_parameters)
 
1118
 
 
1119
    def test_PUT_updates_power_type_default_resets_params(self):
 
1120
        # If one sets power_type to DEFAULT, power_parameter gets
 
1121
        # reset by default (if skip_check is not set).
 
1122
        self.become_admin()
 
1123
        power_parameters = factory.getRandomString()
 
1124
        node = factory.make_node(
 
1125
            owner=self.logged_in_user,
 
1126
            power_type=POWER_TYPE.WAKE_ON_LAN,
 
1127
            power_parameters=power_parameters)
 
1128
        response = self.client.put(
 
1129
            self.get_node_uri(node),
 
1130
            {'power_type': POWER_TYPE.DEFAULT})
 
1131
 
 
1132
        node = reload_object(node)
 
1133
        self.assertEqual(
 
1134
            (httplib.OK, node.power_type, node.power_parameters),
 
1135
            (response.status_code, POWER_TYPE.DEFAULT, ''))
 
1136
 
 
1137
    def test_PUT_updates_power_type_default_rejects_params(self):
 
1138
        # If one sets power_type to DEFAULT, on cannot set power_parameters.
 
1139
        self.become_admin()
 
1140
        power_parameters = factory.getRandomString()
 
1141
        node = factory.make_node(
 
1142
            owner=self.logged_in_user,
 
1143
            power_type=POWER_TYPE.WAKE_ON_LAN,
 
1144
            power_parameters=power_parameters)
 
1145
        new_param = factory.getRandomString()
 
1146
        response = self.client.put(
 
1147
            self.get_node_uri(node),
 
1148
            {
 
1149
                'power_type': POWER_TYPE.DEFAULT,
 
1150
                'power_parameters_address': new_param,
 
1151
            })
 
1152
 
 
1153
        node = reload_object(node)
 
1154
        self.assertEqual(
 
1155
            (
 
1156
                httplib.BAD_REQUEST,
 
1157
                {'power_parameters': ["Unknown parameter(s): address."]}
 
1158
            ),
 
1159
            (response.status_code, json.loads(response.content)))
 
1160
        self.assertEqual(
 
1161
            power_parameters, reload_object(node).power_parameters)
 
1162
 
 
1163
    def test_PUT_updates_power_type_default_skip_check_to_force_params(self):
 
1164
        # If one sets power_type to DEFAULT, it is possible to pass
 
1165
        # power_parameter_skip_check='true' to force power_parameters.
 
1166
        self.become_admin()
 
1167
        power_parameters = factory.getRandomString()
 
1168
        node = factory.make_node(
 
1169
            owner=self.logged_in_user,
 
1170
            power_type=POWER_TYPE.WAKE_ON_LAN,
 
1171
            power_parameters=power_parameters)
 
1172
        new_param = factory.getRandomString()
 
1173
        response = self.client.put(
 
1174
            self.get_node_uri(node),
 
1175
            {
 
1176
                'power_type': POWER_TYPE.DEFAULT,
 
1177
                'power_parameters_param': new_param,
 
1178
                'power_parameters_skip_check': 'true',
 
1179
            })
 
1180
 
 
1181
        node = reload_object(node)
 
1182
        self.assertEqual(
 
1183
            (httplib.OK, node.power_type, node.power_parameters),
 
1184
            (response.status_code, POWER_TYPE.DEFAULT, {'param': new_param}))
 
1185
 
 
1186
    def test_PUT_updates_power_parameters_skip_ckeck(self):
 
1187
        # With power_parameters_skip_check, arbitrary data
 
1188
        # can be put in a Node's power_parameter field.
 
1189
        self.become_admin()
 
1190
        node = factory.make_node(owner=self.logged_in_user)
 
1191
        new_param = factory.getRandomString()
 
1192
        new_value = factory.getRandomString()
 
1193
        response = self.client.put(
 
1194
            self.get_node_uri(node),
 
1195
           {
 
1196
                'power_parameters_%s' % new_param: new_value,
 
1197
                'power_parameters_skip_check': 'true',
 
1198
            })
 
1199
 
 
1200
        self.assertEqual(httplib.OK, response.status_code)
 
1201
        self.assertEqual(
 
1202
            {new_param: new_value}, reload_object(node).power_parameters)
 
1203
 
 
1204
    def test_PUT_updates_power_parameters_empty_string(self):
 
1205
        self.become_admin()
 
1206
        node = factory.make_node(
 
1207
            owner=self.logged_in_user,
 
1208
            power_type=POWER_TYPE.WAKE_ON_LAN,
 
1209
            power_parameters=factory.getRandomString())
 
1210
        response = self.client.put(
 
1211
            self.get_node_uri(node),
 
1212
            {'power_parameters_power_address': ''})
 
1213
 
 
1214
        self.assertEqual(httplib.OK, response.status_code)
 
1215
        self.assertEqual(
 
1216
            {'power_address': ''},
 
1217
            reload_object(node).power_parameters)
 
1218
 
785
1219
    def test_DELETE_deletes_node(self):
786
1220
        # The api allows to delete a Node.
787
1221
        self.become_admin()
841
1275
    """Tests for /api/1.0/nodes/."""
842
1276
 
843
1277
    def test_POST_new_creates_node(self):
844
 
        # The API allows a Node to be created, even as a logged-in user.
 
1278
        # The API allows a non-admin logged-in user to create a Node.
845
1279
        architecture = factory.getRandomChoice(ARCHITECTURE_CHOICES)
846
1280
        response = self.client.post(
847
1281
            self.get_uri('nodes/'),
849
1283
                'op': 'new',
850
1284
                'hostname': factory.getRandomString(),
851
1285
                'architecture': architecture,
852
 
                'after_commissioning_action': '2',
 
1286
                'after_commissioning_action':
 
1287
                    NODE_AFTER_COMMISSIONING_ACTION.DEFAULT,
853
1288
                'mac_addresses': ['aa:bb:cc:dd:ee:ff', '22:bb:cc:dd:ee:ff'],
854
1289
            })
855
1290
 
864
1299
                'op': 'new',
865
1300
                'hostname': factory.getRandomString(),
866
1301
                'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
867
 
                'after_commissioning_action': '2',
 
1302
                'after_commissioning_action':
 
1303
                    NODE_AFTER_COMMISSIONING_ACTION.DEFAULT,
868
1304
                'mac_addresses': ['aa:bb:cc:dd:ee:ff'],
869
1305
            })
870
1306
        self.assertEqual(httplib.OK, response.status_code)
1245
1681
        return node, mac1, mac2
1246
1682
 
1247
1683
    def test_macs_GET(self):
1248
 
        # The api allows for fetching the list of the MAC Addresss for a node.
 
1684
        # The api allows for fetching the list of the MAC address for a node.
1249
1685
        node, mac1, mac2 = self.createNodeWithMacs()
1250
1686
        response = self.client.get(
1251
1687
            self.get_uri('nodes/%s/macs/') % node.system_id)
1259
1695
            mac2.mac_address, parsed_result[1]['mac_address'])
1260
1696
 
1261
1697
    def test_macs_GET_forbidden(self):
1262
 
        # When fetching MAC Addresses, the api returns a 'Forbidden' (403)
 
1698
        # When fetching MAC addresses, the api returns a 'Forbidden' (403)
1263
1699
        # error if the node is not visible to the logged-in user.
1264
1700
        other_node = factory.make_node(
1265
1701
            status=NODE_STATUS.ALLOCATED, owner=factory.make_user())
1269
1705
        self.assertEqual(httplib.FORBIDDEN, response.status_code)
1270
1706
 
1271
1707
    def test_macs_GET_not_found(self):
1272
 
        # When fetching MAC Addresses, the api returns a 'Not Found' (404)
 
1708
        # When fetching MAC addresses, the api returns a 'Not Found' (404)
1273
1709
        # error if no node is found.
1274
1710
        response = self.client.get(self.get_uri('nodes/invalid-id/macs/'))
1275
1711
 
1276
1712
        self.assertEqual(httplib.NOT_FOUND, response.status_code)
1277
1713
 
1278
1714
    def test_macs_GET_node_not_found(self):
1279
 
        # When fetching a MAC Address, the api returns a 'Not Found' (404)
1280
 
        # error if the MAC Address does not exist.
 
1715
        # When fetching a MAC address, the api returns a 'Not Found' (404)
 
1716
        # error if the MAC address does not exist.
1281
1717
        node = factory.make_node()
1282
1718
        response = self.client.get(
1283
1719
            self.get_uri(
1286
1722
        self.assertEqual(httplib.NOT_FOUND, response.status_code)
1287
1723
 
1288
1724
    def test_macs_GET_node_forbidden(self):
1289
 
        # When fetching a MAC Address, the api returns a 'Forbidden' (403)
 
1725
        # When fetching a MAC address, the api returns a 'Forbidden' (403)
1290
1726
        # error if the node is not visible to the logged-in user.
1291
1727
        other_node = factory.make_node(
1292
1728
            status=NODE_STATUS.ALLOCATED, owner=factory.make_user())
1297
1733
        self.assertEqual(httplib.FORBIDDEN, response.status_code)
1298
1734
 
1299
1735
    def test_macs_GET_node_bad_request(self):
1300
 
        # When fetching a MAC Address, the api returns a 'Bad Request' (400)
1301
 
        # error if the MAC Address is not valid.
 
1736
        # When fetching a MAC address, the api returns a 'Bad Request' (400)
 
1737
        # error if the MAC address is not valid.
1302
1738
        node = factory.make_node()
1303
1739
        response = self.client.get(
1304
1740
            self.get_uri('nodes/%s/macs/invalid-mac/') % node.system_id)
1306
1742
        self.assertEqual(400, response.status_code)
1307
1743
 
1308
1744
    def test_macs_POST_add_mac(self):
1309
 
        # The api allows to add a MAC Address to an existing node.
 
1745
        # The api allows to add a MAC address to an existing node.
1310
1746
        node = factory.make_node(owner=self.logged_in_user)
1311
1747
        nb_macs = MACAddress.objects.filter(node=node).count()
1312
1748
        response = self.client.post(
1321
1757
            MACAddress.objects.filter(node=node).count())
1322
1758
 
1323
1759
    def test_macs_POST_add_mac_without_edit_perm(self):
1324
 
        # Adding a MAC Address to a node requires the NODE_PERMISSION.EDIT
 
1760
        # Adding a MAC address to a node requires the NODE_PERMISSION.EDIT
1325
1761
        # permission.
1326
1762
        node = factory.make_node()
1327
1763
        response = self.client.post(
1332
1768
 
1333
1769
    def test_macs_POST_add_mac_invalid(self):
1334
1770
        # A 'Bad Request' response is returned if one tries to add an invalid
1335
 
        # MAC Address to a node.
 
1771
        # MAC address to a node.
1336
1772
        node = self.createNodeWithMacs(self.logged_in_user)[0]
1337
1773
        response = self.client.post(
1338
1774
            self.get_uri('nodes/%s/macs/') % node.system_id,
1346
1782
            parsed_result['mac_address'])
1347
1783
 
1348
1784
    def test_macs_DELETE_mac(self):
1349
 
        # The api allows to delete a MAC Address.
 
1785
        # The api allows to delete a MAC address.
1350
1786
        node, mac1, mac2 = self.createNodeWithMacs(self.logged_in_user)
1351
1787
        nb_macs = node.macaddress_set.count()
1352
1788
        response = self.client.delete(
1359
1795
            node.macaddress_set.count())
1360
1796
 
1361
1797
    def test_macs_DELETE_mac_forbidden(self):
1362
 
        # When deleting a MAC Address, the api returns a 'Forbidden' (403)
 
1798
        # When deleting a MAC address, the api returns a 'Forbidden' (403)
1363
1799
        # error if the node is not visible to the logged-in user.
1364
1800
        node, mac1, _ = self.createNodeWithMacs()
1365
1801
        other_node = factory.make_node(
1371
1807
        self.assertEqual(httplib.FORBIDDEN, response.status_code)
1372
1808
 
1373
1809
    def test_macs_DELETE_not_found(self):
1374
 
        # When deleting a MAC Address, the api returns a 'Not Found' (404)
1375
 
        # error if no existing MAC Address is found.
 
1810
        # When deleting a MAC address, the api returns a 'Not Found' (404)
 
1811
        # error if no existing MAC address is found.
1376
1812
        node = factory.make_node(owner=self.logged_in_user)
1377
1813
        response = self.client.delete(
1378
1814
            self.get_uri('nodes/%s/macs/%s/') % (
1381
1817
        self.assertEqual(httplib.NOT_FOUND, response.status_code)
1382
1818
 
1383
1819
    def test_macs_DELETE_forbidden(self):
1384
 
        # When deleting a MAC Address, the api returns a 'Forbidden'
 
1820
        # When deleting a MAC address, the api returns a 'Forbidden'
1385
1821
        # (403) error if the user does not have the 'edit' permission on the
1386
1822
        # node.
1387
1823
        node = factory.make_node(owner=self.logged_in_user)
1392
1828
        self.assertEqual(httplib.NOT_FOUND, response.status_code)
1393
1829
 
1394
1830
    def test_macs_DELETE_bad_request(self):
1395
 
        # When deleting a MAC Address, the api returns a 'Bad Request' (400)
1396
 
        # error if the provided MAC Address is not valid.
 
1831
        # When deleting a MAC address, the api returns a 'Bad Request' (400)
 
1832
        # error if the provided MAC address is not valid.
1397
1833
        node = factory.make_node()
1398
1834
        response = self.client.delete(
1399
1835
            self.get_uri('nodes/%s/macs/%s/') % (
1460
1896
        self.tmpdir = os.path.join(media_root, "testing")
1461
1897
        os.mkdir(self.tmpdir)
1462
1898
 
1463
 
    def make_file(self, name="foo", contents="test file contents"):
1464
 
        """Make a temp file named `name` with contents `contents`.
 
1899
    def make_upload_file(self, name=None, contents=None):
 
1900
        """Make a temp upload file named `name` with contents `contents`.
1465
1901
 
1466
1902
        :return: The full file path of the file that was created.
1467
1903
        """
1468
 
        filepath = os.path.join(self.tmpdir, name)
1469
 
        with open(filepath, "w") as f:
1470
 
            f.write(contents)
1471
 
        return filepath
 
1904
        return factory.make_file(
 
1905
            location=self.tmpdir, name=name, contents=contents)
1472
1906
 
1473
1907
    def _create_API_params(self, op=None, filename=None, fileObj=None):
1474
1908
        params = {}
1504
1938
class FileStorageAPITest(FileStorageAPITestMixin, APITestCase):
1505
1939
 
1506
1940
    def test_add_file_succeeds(self):
1507
 
        filepath = self.make_file()
 
1941
        filepath = self.make_upload_file()
1508
1942
 
1509
1943
        with open(filepath) as f:
1510
1944
            response = self.make_API_POST_request("add", "foo", f)
1512
1946
        self.assertEqual(httplib.CREATED, response.status_code)
1513
1947
 
1514
1948
    def test_add_file_fails_with_no_filename(self):
1515
 
        filepath = self.make_file()
 
1949
        filepath = self.make_upload_file()
1516
1950
 
1517
1951
        with open(filepath) as f:
1518
1952
            response = self.make_API_POST_request("add", fileObj=f)
1529
1963
        self.assertEqual("File not supplied", response.content)
1530
1964
 
1531
1965
    def test_add_file_fails_with_too_many_files(self):
1532
 
        filepath = self.make_file(name="foo")
1533
 
        filepath2 = self.make_file(name="foo2")
 
1966
        filepath = self.make_upload_file(name="foo")
 
1967
        filepath2 = self.make_upload_file(name="foo2")
1534
1968
 
1535
1969
        with open(filepath) as f, open(filepath2) as f2:
1536
1970
            response = self.client.post(
1548
1982
 
1549
1983
    def test_add_file_can_overwrite_existing_file_of_same_name(self):
1550
1984
        # Write file one.
1551
 
        filepath = self.make_file(contents="file one")
 
1985
        filepath = self.make_upload_file(contents="file one")
1552
1986
        with open(filepath) as f:
1553
1987
            response = self.make_API_POST_request("add", "foo", f)
1554
1988
        self.assertEqual(httplib.CREATED, response.status_code)
1555
1989
 
1556
1990
        # Write file two with the same name but different contents.
1557
 
        filepath = self.make_file(contents="file two")
 
1991
        filepath = self.make_upload_file(contents="file two")
1558
1992
        with open(filepath) as f:
1559
1993
            response = self.make_API_POST_request("add", "foo", f)
1560
1994
        self.assertEqual(httplib.CREATED, response.status_code)
1705
2139
 
1706
2140
class APIErrorsTest(APIv10TestMixin, TransactionTestCase):
1707
2141
 
1708
 
    def test_internal_error_generate_proper_api_response(self):
 
2142
    def test_internal_error_generates_proper_api_response(self):
1709
2143
        error_message = factory.getRandomString()
1710
2144
 
1711
2145
        # Monkey patch api.create_node to have it raise a RuntimeError.
1734
2168
            'op': 'new',
1735
2169
            'hostname': hostname,
1736
2170
            'architecture': architecture,
1737
 
            'after_commissioning_action': '2',
 
2171
            'after_commissioning_action':
 
2172
                NODE_AFTER_COMMISSIONING_ACTION.DEFAULT,
1738
2173
            'mac_addresses': ['aa:bb:cc:dd:ee:ff'],
1739
2174
        })
1740
2175
 
1743
2178
            (response.status_code, response.content))
1744
2179
        self.assertRaises(
1745
2180
            Node.DoesNotExist, Node.objects.get, hostname=hostname)
 
2181
 
 
2182
 
 
2183
class TestAnonymousCommissioningTimeout(APIv10TestMixin, TestCase):
 
2184
    """Testing of commissioning timeout API."""
 
2185
 
 
2186
    def test_check_with_no_action(self):
 
2187
        node = factory.make_node(status=NODE_STATUS.READY)
 
2188
        self.client.post(
 
2189
            self.get_uri('nodes/'), {'op': 'check_commissioning'})
 
2190
        # Anything that's not commissioning should be ignored.
 
2191
        node = reload_object(node)
 
2192
        self.assertEqual(NODE_STATUS.READY, node.status)
 
2193
 
 
2194
    def test_check_with_commissioning_but_not_expired_node(self):
 
2195
        node = factory.make_node(
 
2196
            status=NODE_STATUS.COMMISSIONING)
 
2197
        self.client.post(
 
2198
            self.get_uri('nodes/'), {'op': 'check_commissioning'})
 
2199
        node = reload_object(node)
 
2200
        self.assertEqual(NODE_STATUS.COMMISSIONING, node.status)
 
2201
 
 
2202
    def test_check_with_commissioning_and_expired_node(self):
 
2203
        # Have an interval 1 second longer than the timeout.
 
2204
        interval = timedelta(seconds=1, minutes=settings.COMMISSIONING_TIMEOUT)
 
2205
        updated_at = datetime.now() - interval
 
2206
        node = factory.make_node(
 
2207
            status=NODE_STATUS.COMMISSIONING, created=datetime.now(),
 
2208
            updated=updated_at)
 
2209
 
 
2210
        self.client.post(
 
2211
            self.get_uri('nodes/'), {'op': 'check_commissioning'})
 
2212
        node = reload_object(node)
 
2213
        self.assertEqual(NODE_STATUS.FAILED_TESTS, node.status)