~ubuntu-branches/ubuntu/precise/maas/precise-security

« back to all changes in this revision

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

  • Committer: Package Import Robot
  • Author(s): Andres Rodriguez, Scott Moser, Andres Rodriguez, Dave Walker (Daviey), Gavin Panella
  • Date: 2012-04-12 16:46:22 UTC
  • mfrom: (1.1.11)
  • Revision ID: package-import@ubuntu.com-20120412164622-u1qnsq0s9tsc2f14
Tags: 0.1+bzr462+dfsg-0ubuntu1
* New upstream release (LP: #980240)

[ Scott Moser ]
* add dependency on distro-info (LP: #949442)
* debian/control: add dependency on tgt for ephemeral iscsi environment

[ Andres Rodriguez ]
* Make package lintian clean:
  - maas{-dhcp}.lintian-overrides: Add to make lintian clean.
  - debian/control: Add missing dependencies; correct section and desc.
  - debian/maas.postinst: Do not use absolute path for rabbitmqctl.
  - debian/patches: Add headers to all patches.
* debian/maas-dhcp.postrm: Added to disable dnsmasq in cobbler on removal.
* debian/maas.config: Do not set a password with pwgen as it is not an
  essential package; allow dbconfig-common to create a password instead by
  creating an empty question. (LP: #977475)
* Run MAAS, pserv, txlongpoll as non-root user. (LP: #975436)
  - debian/maas.postinst: Create user/group; set correct permissions for
    directories.
  - debian/maas.postrm: Remove user/group; restart apache2.
  - debian/maas.maas-{pserv,txlongpoll}.upstart: Update to run as non-root
    'maas' user.
* debian/patches/01-fix-database-settings.patch: Remove adding of PSERV_URL.
* debian/maas.postinst:
  - Handle config file upgrade from versions lower than 0.1+bzr445+dfsg-0ubuntu1,
    by creating new passwords and updating accordingly
  - use local variables in functions.
  - Handle maas tgt configuration for upgrades from 0.1+bzr459+dfsg-0ubuntu1.
* debian/extras/99-maas: Add squid-deb-proxy file to enable PPAs. (LP: #979383)
* debian/maas.install: Install missing commissioning-user-data script.

[ Dave Walker (Daviey) ]
* debian/patches/02-pserv-config.patch: Refreshed to apply to updated config.

[ Gavin Panella ]
* debian/maas.postinst: Update pserv.yaml and maas_local_settings.py to use
  password.

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
__all__ = []
13
13
 
14
14
from abc import ABCMeta
15
 
from urlparse import parse_qs
 
15
from base64 import b64decode
16
16
from xmlrpclib import Fault
17
17
 
18
18
from django.conf import settings
19
 
from maasserver import provisioning
 
19
from maasserver import (
 
20
    components,
 
21
    provisioning,
 
22
    )
 
23
from maasserver.components import (
 
24
    COMPONENT,
 
25
    get_persistent_errors,
 
26
    register_persistent_error,
 
27
    )
20
28
from maasserver.exceptions import MAASAPIException
21
29
from maasserver.models import (
22
30
    ARCHITECTURE,
23
 
    ARCHITECTURE_CHOICES,
24
31
    Config,
25
32
    Node,
26
33
    NODE_AFTER_COMMISSIONING_ACTION,
27
34
    NODE_STATUS,
 
35
    NODE_STATUS_CHOICES,
28
36
    )
29
37
from maasserver.provisioning import (
30
 
    compose_metadata,
 
38
    compose_cloud_init_preseed,
 
39
    compose_commissioning_preseed,
 
40
    compose_preseed,
 
41
    DETAILED_PRESENTATIONS,
31
42
    get_metadata_server_url,
32
43
    name_arch_in_cobbler_style,
 
44
    present_detailed_user_friendly_fault,
33
45
    present_user_friendly_fault,
34
 
    PRESENTATIONS,
 
46
    ProvisioningTransport,
35
47
    select_profile_for_node,
 
48
    SHORT_PRESENTATIONS,
36
49
    )
37
50
from maasserver.testing.enum import map_enum
38
51
from maasserver.testing.factory import factory
44
57
    )
45
58
from provisioningserver.testing.factory import ProvisioningFakeFactory
46
59
from testtools.deferredruntest import AsynchronousDeferredRunTest
 
60
from testtools.matchers import (
 
61
    KeysEqual,
 
62
    StartsWith,
 
63
    )
47
64
from testtools.testcase import ExpectedException
48
65
from twisted.internet.defer import inlineCallbacks
 
66
import yaml
 
67
 
 
68
 
 
69
class TestHelpers(TestCase):
 
70
    """Tests for helpers that don't actually need any kind of pserv."""
 
71
 
 
72
    def test_metadata_server_url_refers_to_own_metadata_service(self):
 
73
        self.assertEqual(
 
74
            "%s/metadata/"
 
75
            % Config.objects.get_config('maas_url').rstrip('/'),
 
76
            get_metadata_server_url())
 
77
 
 
78
    def test_metadata_server_url_includes_script_name(self):
 
79
        self.patch(settings, "FORCE_SCRIPT_NAME", "/MAAS")
 
80
        self.assertEqual(
 
81
            "%s/MAAS/metadata/"
 
82
            % Config.objects.get_config('maas_url').rstrip('/'),
 
83
            get_metadata_server_url())
 
84
 
 
85
    def test_compose_preseed_for_commissioning_node_produces_yaml(self):
 
86
        node = factory.make_node(status=NODE_STATUS.COMMISSIONING)
 
87
        preseed = yaml.load(compose_preseed(node))
 
88
        self.assertIn('datasource', preseed)
 
89
        self.assertIn('MAAS', preseed['datasource'])
 
90
        self.assertThat(
 
91
            preseed['datasource']['MAAS'],
 
92
            KeysEqual(
 
93
                'metadata_url', 'consumer_key', 'token_key', 'token_secret'))
 
94
 
 
95
    def test_compose_preseed_for_commissioning_node_has_header(self):
 
96
        node = factory.make_node(status=NODE_STATUS.COMMISSIONING)
 
97
        self.assertThat(compose_preseed(node), StartsWith("#cloud-config\n"))
 
98
 
 
99
    def test_compose_preseed_includes_metadata_url(self):
 
100
        node = factory.make_node(status=NODE_STATUS.READY)
 
101
        self.assertIn(get_metadata_server_url(), compose_preseed(node))
 
102
 
 
103
    def test_compose_preseed_for_commissioning_includes_metadata_url(self):
 
104
        node = factory.make_node(status=NODE_STATUS.COMMISSIONING)
 
105
        preseed = yaml.load(compose_preseed(node))
 
106
        self.assertEqual(
 
107
            get_metadata_server_url(),
 
108
            preseed['datasource']['MAAS']['metadata_url'])
 
109
 
 
110
    def test_compose_preseed_includes_node_oauth_token(self):
 
111
        node = factory.make_node(status=NODE_STATUS.READY)
 
112
        preseed = compose_preseed(node)
 
113
        token = NodeKey.objects.get_token_for_node(node)
 
114
        self.assertIn('oauth_consumer_key=%s' % token.consumer.key, preseed)
 
115
        self.assertIn('oauth_token_key=%s' % token.key, preseed)
 
116
        self.assertIn('oauth_token_secret=%s' % token.secret, preseed)
 
117
 
 
118
    def test_compose_preseed_for_commissioning_includes_auth_token(self):
 
119
        node = factory.make_node(status=NODE_STATUS.COMMISSIONING)
 
120
        preseed = yaml.load(compose_preseed(node))
 
121
        maas_dict = preseed['datasource']['MAAS']
 
122
        token = NodeKey.objects.get_token_for_node(node)
 
123
        self.assertEqual(token.consumer.key, maas_dict['consumer_key'])
 
124
        self.assertEqual(token.key, maas_dict['token_key'])
 
125
        self.assertEqual(token.secret, maas_dict['token_secret'])
 
126
 
 
127
    def test_present_detailed_user_friendly_fault_describes_pserv_fault(self):
 
128
        self.assertIn(
 
129
            "provisioning server",
 
130
            present_user_friendly_fault(Fault(8002, 'error')).message)
 
131
 
 
132
    def test_present_detailed_fault_covers_all_pserv_faults(self):
 
133
        all_pserv_faults = set(map_enum(PSERV_FAULT).values())
 
134
        presentable_pserv_faults = set(DETAILED_PRESENTATIONS.keys())
 
135
        self.assertItemsEqual([], all_pserv_faults - presentable_pserv_faults)
 
136
 
 
137
    def test_present_detailed_fault_rerepresents_all_pserv_faults(self):
 
138
        fault_string = factory.getRandomString()
 
139
        for fault_code in map_enum(PSERV_FAULT).values():
 
140
            original_fault = Fault(fault_code, fault_string)
 
141
            new_fault = present_detailed_user_friendly_fault(original_fault)
 
142
            self.assertNotEqual(fault_string, new_fault.message)
 
143
 
 
144
    def test_present_detailed_fault_describes_cobbler_fault(self):
 
145
        friendly_fault = present_detailed_user_friendly_fault(
 
146
            Fault(PSERV_FAULT.NO_COBBLER, factory.getRandomString()))
 
147
        friendly_text = friendly_fault.message
 
148
        self.assertIn("unable to reach", friendly_text)
 
149
        self.assertIn("Cobbler", friendly_text)
 
150
 
 
151
    def test_present_detailed_fault_describes_cobbler_auth_fail(self):
 
152
        friendly_fault = present_detailed_user_friendly_fault(
 
153
            Fault(PSERV_FAULT.COBBLER_AUTH_FAILED, factory.getRandomString()))
 
154
        friendly_text = friendly_fault.message
 
155
        self.assertIn("failed to authenticate", friendly_text)
 
156
        self.assertIn("Cobbler", friendly_text)
 
157
 
 
158
    def test_present_detailed_fault_describes_cobbler_auth_error(self):
 
159
        friendly_fault = present_detailed_user_friendly_fault(
 
160
            Fault(PSERV_FAULT.COBBLER_AUTH_ERROR, factory.getRandomString()))
 
161
        friendly_text = friendly_fault.message
 
162
        self.assertIn("authentication token", friendly_text)
 
163
        self.assertIn("Cobbler", friendly_text)
 
164
 
 
165
    def test_present_detailed_fault_describes_missing_profile(self):
 
166
        profile = factory.getRandomString()
 
167
        friendly_fault = present_detailed_user_friendly_fault(
 
168
            Fault(
 
169
                PSERV_FAULT.NO_SUCH_PROFILE,
 
170
                "invalid profile name: %s" % profile))
 
171
        friendly_text = friendly_fault.message
 
172
        self.assertIn(profile, friendly_text)
 
173
        self.assertIn("maas-import-isos", friendly_text)
 
174
 
 
175
    def test_present_detailed_fault_describes_generic_cobbler_fail(self):
 
176
        error_text = factory.getRandomString()
 
177
        friendly_fault = present_detailed_user_friendly_fault(
 
178
            Fault(PSERV_FAULT.GENERIC_COBBLER_ERROR, error_text))
 
179
        friendly_text = friendly_fault.message
 
180
        self.assertIn("Cobbler", friendly_text)
 
181
        self.assertIn(error_text, friendly_text)
 
182
 
 
183
    def test_present_detailed_fault_returns_None_for_other_fault(self):
 
184
        self.assertIsNone(
 
185
            present_detailed_user_friendly_fault(Fault(9999, "!!!")))
 
186
 
 
187
    def test_present_user_friendly_fault_describes_pserv_fault(self):
 
188
        self.assertIn(
 
189
            "provisioning server",
 
190
            present_user_friendly_fault(Fault(8002, 'error')).message)
 
191
 
 
192
    def test_present_user_friendly_fault_covers_all_pserv_faults(self):
 
193
        all_pserv_faults = set(map_enum(PSERV_FAULT).values())
 
194
        presentable_pserv_faults = set(SHORT_PRESENTATIONS.keys())
 
195
        self.assertItemsEqual([], all_pserv_faults - presentable_pserv_faults)
 
196
 
 
197
    def test_present_user_friendly_fault_rerepresents_all_pserv_faults(self):
 
198
        fault_string = factory.getRandomString()
 
199
        for fault_code in map_enum(PSERV_FAULT).values():
 
200
            original_fault = Fault(fault_code, fault_string)
 
201
            new_fault = present_user_friendly_fault(original_fault)
 
202
            self.assertNotEqual(fault_string, new_fault.message)
 
203
 
 
204
    def test_present_user_friendly_fault_describes_cobbler_fault(self):
 
205
        friendly_fault = present_user_friendly_fault(
 
206
            Fault(PSERV_FAULT.NO_COBBLER, factory.getRandomString()))
 
207
        friendly_text = friendly_fault.message
 
208
        self.assertIn("Unable to reach the Cobbler server", friendly_text)
 
209
 
 
210
    def test_present_user_friendly_fault_describes_cobbler_auth_fail(self):
 
211
        friendly_fault = present_user_friendly_fault(
 
212
            Fault(PSERV_FAULT.COBBLER_AUTH_FAILED, factory.getRandomString()))
 
213
        friendly_text = friendly_fault.message
 
214
        self.assertIn(
 
215
            "Failed to authenticate with the Cobbler server", friendly_text)
 
216
 
 
217
    def test_present_user_friendly_fault_describes_cobbler_auth_error(self):
 
218
        friendly_fault = present_user_friendly_fault(
 
219
            Fault(PSERV_FAULT.COBBLER_AUTH_ERROR, factory.getRandomString()))
 
220
        friendly_text = friendly_fault.message
 
221
        self.assertIn(
 
222
            "Failed to authenticate with the Cobbler server", friendly_text)
 
223
 
 
224
    def test_present_user_friendly_fault_describes_missing_profile(self):
 
225
        profile = factory.getRandomString()
 
226
        friendly_fault = present_user_friendly_fault(
 
227
            Fault(
 
228
                PSERV_FAULT.NO_SUCH_PROFILE,
 
229
                "invalid profile name: %s" % profile))
 
230
        friendly_text = friendly_fault.message
 
231
        self.assertIn(profile, friendly_text)
 
232
 
 
233
    def test_present_user_friendly_fault_describes_generic_cobbler_fail(self):
 
234
        error_text = factory.getRandomString()
 
235
        friendly_fault = present_user_friendly_fault(
 
236
            Fault(PSERV_FAULT.GENERIC_COBBLER_ERROR, error_text))
 
237
        friendly_text = friendly_fault.message
 
238
        self.assertIn(
 
239
            "Unknown problem encountered with the Cobbler server.",
 
240
            friendly_text)
49
241
 
50
242
 
51
243
class ProvisioningTests:
131
323
        def raise_missing_profile(*args, **kwargs):
132
324
            raise Fault(PSERV_FAULT.NO_SUCH_PROFILE, "Unknown profile.")
133
325
 
134
 
        self.papi.patch('add_node', raise_missing_profile)
 
326
        self.patch(self.papi.proxy, 'add_node', raise_missing_profile)
135
327
        with ExpectedException(MAASAPIException):
136
328
            node = factory.make_node(architecture='amd32k')
137
329
            provisioning.provision_post_save_Node(
142
334
        def raise_fault(*args, **kwargs):
143
335
            raise Fault(PSERV_FAULT.NO_COBBLER, factory.getRandomString())
144
336
 
145
 
        self.papi.patch('add_node', raise_fault)
 
337
        self.patch(self.papi.proxy, 'add_node', raise_fault)
146
338
        with ExpectedException(MAASAPIException):
147
339
            node = factory.make_node(architecture='amd32k')
148
340
            provisioning.provision_post_save_Node(
217
409
        node = self.papi.get_nodes_by_name(["frank"])["frank"]
218
410
        self.assertEqual([], node["mac_addresses"])
219
411
 
220
 
    def test_metadata_server_url_refers_to_own_metadata_service(self):
221
 
        self.assertEqual(
222
 
            "%s/metadata/"
223
 
            % Config.objects.get_config('maas_url').rstrip('/'),
224
 
            get_metadata_server_url())
225
 
 
226
 
    def test_metadata_server_url_includes_script_name(self):
227
 
        self.patch(settings, "FORCE_SCRIPT_NAME", "/MAAS")
228
 
        self.assertEqual(
229
 
            "%s/MAAS/metadata/"
230
 
            % Config.objects.get_config('maas_url').rstrip('/'),
231
 
            get_metadata_server_url())
232
 
 
233
 
    def test_compose_metadata_includes_metadata_url(self):
234
 
        node = factory.make_node()
235
 
        self.assertEqual(
236
 
            get_metadata_server_url(),
237
 
            compose_metadata(node)['maas-metadata-url'])
238
 
 
239
 
    def test_compose_metadata_includes_node_oauth_token(self):
240
 
        node = factory.make_node()
241
 
        metadata = compose_metadata(node)
242
 
        token = NodeKey.objects.get_token_for_node(node)
243
 
        self.assertEqual({
244
 
            'oauth_consumer_key': [token.consumer.key],
245
 
            'oauth_token_key': [token.key],
246
 
            'oauth_token_secret': [token.secret],
247
 
            },
248
 
            parse_qs(metadata['maas-metadata-credentials']))
249
 
 
250
412
    def test_papi_xmlrpc_faults_are_reported_helpfully(self):
251
413
 
252
414
        def raise_fault(*args, **kwargs):
253
415
            raise Fault(8002, factory.getRandomString())
254
416
 
255
 
        self.papi.patch('add_node', raise_fault)
 
417
        self.patch(self.papi.proxy, 'add_node', raise_fault)
256
418
 
257
419
        with ExpectedException(MAASAPIException, ".*provisioning server.*"):
258
 
            self.papi.add_node('node', 'profile', 'power', {})
 
420
            self.papi.add_node('node', 'profile', 'power', '')
259
421
 
260
422
    def test_provisioning_errors_are_reported_helpfully(self):
261
423
 
262
424
        def raise_provisioning_error(*args, **kwargs):
263
425
            raise Fault(PSERV_FAULT.NO_COBBLER, factory.getRandomString())
264
426
 
265
 
        self.papi.patch('add_node', raise_provisioning_error)
 
427
        self.patch(self.papi.proxy, 'add_node', raise_provisioning_error)
266
428
 
267
429
        with ExpectedException(MAASAPIException, ".*Cobbler.*"):
268
 
            self.papi.add_node('node', 'profile', 'power', {})
269
 
 
270
 
    def test_present_user_friendly_fault_describes_pserv_fault(self):
 
430
            self.papi.add_node('node', 'profile', 'power', '')
 
431
 
 
432
    def patch_and_call_papi_method(self, fault_code, papi_method='add_node'):
 
433
        # Patch papi method to make it raise a Fault of the provided
 
434
        # fault_code.  Then call this method.
 
435
        def raise_provisioning_error(*args, **kwargs):
 
436
            raise Fault(fault_code, factory.getRandomString())
 
437
 
 
438
        self.patch(self.papi.proxy, papi_method, raise_provisioning_error)
 
439
 
 
440
        try:
 
441
            method = getattr(self.papi, papi_method)
 
442
            method()
 
443
        except MAASAPIException:
 
444
            pass
 
445
 
 
446
    def test_error_registered_when_NO_COBBLER_raised(self):
 
447
        self.patch(components, '_PERSISTENT_ERRORS', {})
 
448
        self.patch_and_call_papi_method(PSERV_FAULT.NO_COBBLER)
 
449
        errors = get_persistent_errors()
 
450
        self.assertEqual(1, len(errors))
271
451
        self.assertIn(
272
 
            "provisioning server",
273
 
            present_user_friendly_fault(Fault(8002, 'error')).message)
274
 
 
275
 
    def test_present_user_friendly_fault_covers_all_pserv_faults(self):
276
 
        all_pserv_faults = set(map_enum(PSERV_FAULT).values())
277
 
        presentable_pserv_faults = set(PRESENTATIONS.keys())
278
 
        self.assertItemsEqual([], all_pserv_faults - presentable_pserv_faults)
279
 
 
280
 
    def test_present_user_friendly_fault_rerepresents_all_pserv_faults(self):
281
 
        fault_string = factory.getRandomString()
 
452
            "The provisioning server was unable to reach the Cobbler",
 
453
            errors[0])
 
454
 
 
455
    def test_error_registered_can_handle_all_the_exceptions(self):
282
456
        for fault_code in map_enum(PSERV_FAULT).values():
283
 
            original_fault = Fault(fault_code, fault_string)
284
 
            new_fault = present_user_friendly_fault(original_fault)
285
 
            self.assertNotEqual(fault_string, new_fault.message)
286
 
 
287
 
    def test_present_user_friendly_fault_describes_cobbler_fault(self):
288
 
        friendly_fault = present_user_friendly_fault(
289
 
            Fault(PSERV_FAULT.NO_COBBLER, factory.getRandomString()))
290
 
        friendly_text = friendly_fault.message
291
 
        self.assertIn("unable to reach", friendly_text)
292
 
        self.assertIn("Cobbler", friendly_text)
293
 
 
294
 
    def test_present_user_friendly_fault_describes_cobbler_auth_fail(self):
295
 
        friendly_fault = present_user_friendly_fault(
296
 
            Fault(PSERV_FAULT.COBBLER_AUTH_FAILED, factory.getRandomString()))
297
 
        friendly_text = friendly_fault.message
298
 
        self.assertIn("failed to authenticate", friendly_text)
299
 
        self.assertIn("Cobbler", friendly_text)
300
 
 
301
 
    def test_present_user_friendly_fault_describes_cobbler_auth_error(self):
302
 
        friendly_fault = present_user_friendly_fault(
303
 
            Fault(PSERV_FAULT.COBBLER_AUTH_ERROR, factory.getRandomString()))
304
 
        friendly_text = friendly_fault.message
305
 
        self.assertIn("authentication token", friendly_text)
306
 
        self.assertIn("Cobbler", friendly_text)
307
 
 
308
 
    def test_present_user_friendly_fault_describes_missing_profile(self):
309
 
        profile = factory.getRandomString()
310
 
        friendly_fault = present_user_friendly_fault(
311
 
            Fault(
312
 
                PSERV_FAULT.NO_SUCH_PROFILE,
313
 
                "invalid profile name: %s" % profile))
314
 
        friendly_text = friendly_fault.message
315
 
        self.assertIn(profile, friendly_text)
316
 
        self.assertIn("maas-import-isos", friendly_text)
317
 
 
318
 
    def test_present_user_friendly_fault_describes_generic_cobbler_fail(self):
319
 
        error_text = factory.getRandomString()
320
 
        friendly_fault = present_user_friendly_fault(
321
 
            Fault(PSERV_FAULT.GENERIC_COBBLER_ERROR, error_text))
322
 
        friendly_text = friendly_fault.message
323
 
        self.assertIn("Cobbler", friendly_text)
324
 
        self.assertIn(error_text, friendly_text)
325
 
 
326
 
    def test_present_user_friendly_fault_returns_None_for_other_fault(self):
327
 
        self.assertIsNone(present_user_friendly_fault(Fault(9999, "!!!")))
 
457
            self.patch(components, '_PERSISTENT_ERRORS', {})
 
458
            self.patch_and_call_papi_method(fault_code)
 
459
            errors = get_persistent_errors()
 
460
            self.assertEqual(1, len(errors))
 
461
 
 
462
    def test_failing_components_cleared_if_add_node_works(self):
 
463
        self.patch(components, '_PERSISTENT_ERRORS', {})
 
464
        register_persistent_error(COMPONENT.PSERV, factory.getRandomString())
 
465
        register_persistent_error(COMPONENT.COBBLER, factory.getRandomString())
 
466
        register_persistent_error(
 
467
            COMPONENT.IMPORT_ISOS, factory.getRandomString())
 
468
        self.papi.add_node('node', 'hostname', 'profile', 'power', '')
 
469
        self.assertEqual([], get_persistent_errors())
 
470
 
 
471
    def test_only_failing_components_are_cleared_if_modify_nodes_works(self):
 
472
        # Only the components listed in METHOD_COMPONENTS[method_name]
 
473
        # are cleared with the run of method_name is successfull.
 
474
        self.patch(components, '_PERSISTENT_ERRORS', {})
 
475
        other_error = factory.getRandomString()
 
476
        other_component = factory.getRandomString()
 
477
        register_persistent_error(other_component, other_error)
 
478
        self.papi.modify_nodes({})
 
479
        self.assertEqual([other_error], get_persistent_errors())
 
480
 
 
481
    def test_failing_components_cleared_if_modify_nodes_works(self):
 
482
        self.patch(components, '_PERSISTENT_ERRORS', {})
 
483
        register_persistent_error(COMPONENT.PSERV, factory.getRandomString())
 
484
        register_persistent_error(COMPONENT.COBBLER, factory.getRandomString())
 
485
        self.papi.modify_nodes({})
 
486
        self.assertEqual([], get_persistent_errors())
 
487
 
 
488
    def test_failing_components_cleared_if_delete_nodes_by_name_works(self):
 
489
        self.patch(components, '_PERSISTENT_ERRORS', {})
 
490
        register_persistent_error(COMPONENT.PSERV, factory.getRandomString())
 
491
        register_persistent_error(COMPONENT.COBBLER, factory.getRandomString())
 
492
        other_error = factory.getRandomString()
 
493
        register_persistent_error(factory.getRandomString(), other_error)
 
494
        self.papi.delete_nodes_by_name([])
 
495
        self.assertEqual([other_error], get_persistent_errors())
328
496
 
329
497
 
330
498
class TestProvisioningWithFake(ProvisioningTests, ProvisioningFakeFactory,
336
504
    def setUp(self):
337
505
        super(TestProvisioningWithFake, self).setUp()
338
506
        self.papi = provisioning.get_provisioning_api_proxy()
 
507
 
 
508
    def test_provision_post_save_Node_set_netboot_enabled(self):
 
509
        # When a node is under MAAS's control - i.e. not allocated and not
 
510
        # retired - it is always configured for netbooting. When the node is
 
511
        # allocated, netbooting is left alone; its state may change in
 
512
        # response to interactions between the node and the provisioning
 
513
        # server and MAAS ought to leave that alone. When the node is retired
 
514
        # netbooting is disabled.
 
515
        expected = {
 
516
            NODE_STATUS.DECLARED: False,
 
517
            NODE_STATUS.COMMISSIONING: True,
 
518
            NODE_STATUS.FAILED_TESTS: True,
 
519
            NODE_STATUS.MISSING: True,
 
520
            NODE_STATUS.READY: True,
 
521
            NODE_STATUS.RESERVED: True,
 
522
            NODE_STATUS.ALLOCATED: None,  # No setting.
 
523
            NODE_STATUS.RETIRED: False,
 
524
            }
 
525
        nodes = {
 
526
            status: factory.make_node(status=status)
 
527
            for status, title in NODE_STATUS_CHOICES
 
528
            }
 
529
        pserv_nodes = {
 
530
            status: node.system_id
 
531
            for status, node in nodes.items()
 
532
            }
 
533
        observed = {
 
534
            status: self.papi.nodes[pserv_node].get("netboot_enabled")
 
535
            for status, pserv_node in pserv_nodes.items()
 
536
            }
 
537
        self.assertEqual(expected, observed)
 
538
 
 
539
    def test_commissioning_node_gets_commissioning_preseed(self):
 
540
        node = factory.make_node(status=NODE_STATUS.DECLARED)
 
541
        token = NodeKey.objects.get_token_for_node(node)
 
542
        node.start_commissioning(factory.make_admin())
 
543
        preseed = self.papi.nodes[node.system_id]['ks_meta']['MAAS_PRESEED']
 
544
        self.assertEqual(
 
545
            compose_commissioning_preseed(token), b64decode(preseed))
 
546
 
 
547
    def test_non_commissioning_node_gets_cloud_init_preseed(self):
 
548
        node = factory.make_node(status=NODE_STATUS.READY)
 
549
        token = NodeKey.objects.get_token_for_node(node)
 
550
        preseed = self.papi.nodes[node.system_id]['ks_meta']['MAAS_PRESEED']
 
551
        self.assertEqual(
 
552
            compose_cloud_init_preseed(token), b64decode(preseed))
 
553
 
 
554
    def test_node_gets_cloud_init_preseed_after_commissioning(self):
 
555
        node = factory.make_node(status=NODE_STATUS.DECLARED)
 
556
        token = NodeKey.objects.get_token_for_node(node)
 
557
        node.start_commissioning(factory.make_admin())
 
558
        node.status = NODE_STATUS.READY
 
559
        node.save()
 
560
        preseed = self.papi.nodes[node.system_id]['ks_meta']['MAAS_PRESEED']
 
561
        self.assertEqual(
 
562
            compose_cloud_init_preseed(token), b64decode(preseed))
 
563
 
 
564
 
 
565
class TestProvisioningTransport(TestCase):
 
566
    """Tests for :class:`ProvisioningTransport`."""
 
567
 
 
568
    def test_make_connection(self):
 
569
        transport = ProvisioningTransport()
 
570
        connection = transport.make_connection("nowhere.example.com")
 
571
        # The connection has not yet been established.
 
572
        self.assertIsNone(connection.sock)
 
573
        # The desired timeout has been modified.
 
574
        self.assertEqual(transport.timeout, connection.timeout)