45
58
from provisioningserver.testing.factory import ProvisioningFakeFactory
46
59
from testtools.deferredruntest import AsynchronousDeferredRunTest
60
from testtools.matchers import (
47
64
from testtools.testcase import ExpectedException
48
65
from twisted.internet.defer import inlineCallbacks
69
class TestHelpers(TestCase):
70
"""Tests for helpers that don't actually need any kind of pserv."""
72
def test_metadata_server_url_refers_to_own_metadata_service(self):
75
% Config.objects.get_config('maas_url').rstrip('/'),
76
get_metadata_server_url())
78
def test_metadata_server_url_includes_script_name(self):
79
self.patch(settings, "FORCE_SCRIPT_NAME", "/MAAS")
82
% Config.objects.get_config('maas_url').rstrip('/'),
83
get_metadata_server_url())
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'])
91
preseed['datasource']['MAAS'],
93
'metadata_url', 'consumer_key', 'token_key', 'token_secret'))
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"))
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))
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))
107
get_metadata_server_url(),
108
preseed['datasource']['MAAS']['metadata_url'])
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)
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'])
127
def test_present_detailed_user_friendly_fault_describes_pserv_fault(self):
129
"provisioning server",
130
present_user_friendly_fault(Fault(8002, 'error')).message)
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)
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)
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)
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)
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)
165
def test_present_detailed_fault_describes_missing_profile(self):
166
profile = factory.getRandomString()
167
friendly_fault = present_detailed_user_friendly_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)
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)
183
def test_present_detailed_fault_returns_None_for_other_fault(self):
185
present_detailed_user_friendly_fault(Fault(9999, "!!!")))
187
def test_present_user_friendly_fault_describes_pserv_fault(self):
189
"provisioning server",
190
present_user_friendly_fault(Fault(8002, 'error')).message)
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)
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)
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)
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
215
"Failed to authenticate with the Cobbler server", friendly_text)
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
222
"Failed to authenticate with the Cobbler server", friendly_text)
224
def test_present_user_friendly_fault_describes_missing_profile(self):
225
profile = factory.getRandomString()
226
friendly_fault = present_user_friendly_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)
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
239
"Unknown problem encountered with the Cobbler server.",
51
243
class ProvisioningTests:
217
409
node = self.papi.get_nodes_by_name(["frank"])["frank"]
218
410
self.assertEqual([], node["mac_addresses"])
220
def test_metadata_server_url_refers_to_own_metadata_service(self):
223
% Config.objects.get_config('maas_url').rstrip('/'),
224
get_metadata_server_url())
226
def test_metadata_server_url_includes_script_name(self):
227
self.patch(settings, "FORCE_SCRIPT_NAME", "/MAAS")
230
% Config.objects.get_config('maas_url').rstrip('/'),
231
get_metadata_server_url())
233
def test_compose_metadata_includes_metadata_url(self):
234
node = factory.make_node()
236
get_metadata_server_url(),
237
compose_metadata(node)['maas-metadata-url'])
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)
244
'oauth_consumer_key': [token.consumer.key],
245
'oauth_token_key': [token.key],
246
'oauth_token_secret': [token.secret],
248
parse_qs(metadata['maas-metadata-credentials']))
250
412
def test_papi_xmlrpc_faults_are_reported_helpfully(self):
252
414
def raise_fault(*args, **kwargs):
253
415
raise Fault(8002, factory.getRandomString())
255
self.papi.patch('add_node', raise_fault)
417
self.patch(self.papi.proxy, 'add_node', raise_fault)
257
419
with ExpectedException(MAASAPIException, ".*provisioning server.*"):
258
self.papi.add_node('node', 'profile', 'power', {})
420
self.papi.add_node('node', 'profile', 'power', '')
260
422
def test_provisioning_errors_are_reported_helpfully(self):
262
424
def raise_provisioning_error(*args, **kwargs):
263
425
raise Fault(PSERV_FAULT.NO_COBBLER, factory.getRandomString())
265
self.papi.patch('add_node', raise_provisioning_error)
427
self.patch(self.papi.proxy, 'add_node', raise_provisioning_error)
267
429
with ExpectedException(MAASAPIException, ".*Cobbler.*"):
268
self.papi.add_node('node', 'profile', 'power', {})
270
def test_present_user_friendly_fault_describes_pserv_fault(self):
430
self.papi.add_node('node', 'profile', 'power', '')
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())
438
self.patch(self.papi.proxy, papi_method, raise_provisioning_error)
441
method = getattr(self.papi, papi_method)
443
except MAASAPIException:
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))
272
"provisioning server",
273
present_user_friendly_fault(Fault(8002, 'error')).message)
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)
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",
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)
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)
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)
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)
308
def test_present_user_friendly_fault_describes_missing_profile(self):
309
profile = factory.getRandomString()
310
friendly_fault = present_user_friendly_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)
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)
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))
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())
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())
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())
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())
330
498
class TestProvisioningWithFake(ProvisioningTests, ProvisioningFakeFactory,
337
505
super(TestProvisioningWithFake, self).setUp()
338
506
self.papi = provisioning.get_provisioning_api_proxy()
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.
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,
526
status: factory.make_node(status=status)
527
for status, title in NODE_STATUS_CHOICES
530
status: node.system_id
531
for status, node in nodes.items()
534
status: self.papi.nodes[pserv_node].get("netboot_enabled")
535
for status, pserv_node in pserv_nodes.items()
537
self.assertEqual(expected, observed)
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']
545
compose_commissioning_preseed(token), b64decode(preseed))
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']
552
compose_cloud_init_preseed(token), b64decode(preseed))
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
560
preseed = self.papi.nodes[node.system_id]['ks_meta']['MAAS_PRESEED']
562
compose_cloud_init_preseed(token), b64decode(preseed))
565
class TestProvisioningTransport(TestCase):
566
"""Tests for :class:`ProvisioningTransport`."""
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)