Merge lp:~blake-rouse/maas/remove-pxeconfig into lp:~maas-committers/maas/trunk
- remove-pxeconfig
- Merge into trunk
Proposed by
Blake Rouse
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Blake Rouse | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 4799 | ||||
Proposed branch: | lp:~blake-rouse/maas/remove-pxeconfig | ||||
Merge into: | lp:~maas-committers/maas/trunk | ||||
Prerequisite: | lp:~blake-rouse/maas/tftp-rpc-boot-config | ||||
Diff against target: |
1140 lines (+1/-1080) 6 files modified
src/maasserver/api/pxeconfig.py (+0/-340) src/maasserver/api/tests/test_pxeconfig.py (+0/-712) src/maasserver/models/node.py (+1/-1) src/maasserver/urls_api.py (+0/-2) src/provisioningserver/config.py (+0/-5) src/provisioningserver/tests/test_config.py (+0/-20) |
||||
To merge this branch: | bzr merge lp:~blake-rouse/maas/remove-pxeconfig | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Andres Rodriguez (community) | Approve | ||
Review via email: mp+289277@code.launchpad.net |
Commit message
Remove pxeconfig API as its not longer used. Has been replaced by the GetBootConfig RPC call.
Description of the change
To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote : | # |
Revision history for this message
Gavin Panella (allenap) wrote : | # |
I count two lines of green ;)
Nice stuff!
Revision history for this message
Blake Rouse (blake-rouse) wrote : | # |
Now its all red. ;-)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === removed file 'src/maasserver/api/pxeconfig.py' |
2 | --- src/maasserver/api/pxeconfig.py 2016-03-09 06:54:33 +0000 |
3 | +++ src/maasserver/api/pxeconfig.py 1970-01-01 00:00:00 +0000 |
4 | @@ -1,340 +0,0 @@ |
5 | -# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
6 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
7 | - |
8 | -"""API handler: `pxeconfig`.""" |
9 | - |
10 | -__all__ = [ |
11 | - 'pxeconfig', |
12 | - ] |
13 | - |
14 | -import http.client |
15 | -import json |
16 | - |
17 | -from crochet import TimeoutError |
18 | -from django.http import HttpResponse |
19 | -from maasserver import logger |
20 | -from maasserver.api.utils import ( |
21 | - get_mandatory_param, |
22 | - get_optional_param, |
23 | -) |
24 | -from maasserver.clusterrpc.boot_images import get_boot_images_for |
25 | -from maasserver.enum import INTERFACE_TYPE |
26 | -from maasserver.models import ( |
27 | - BootResource, |
28 | - Config, |
29 | - Event, |
30 | - RackController, |
31 | -) |
32 | -from maasserver.models.interface import ( |
33 | - Interface, |
34 | - PhysicalInterface, |
35 | -) |
36 | -from maasserver.preseed import ( |
37 | - compose_enlistment_preseed_url, |
38 | - compose_preseed_url, |
39 | -) |
40 | -from maasserver.server_address import get_maas_facing_server_address |
41 | -from maasserver.third_party_drivers import get_third_party_driver |
42 | -from maasserver.utils import find_rack_controller |
43 | -from maasserver.utils.orm import get_one |
44 | -from provisioningserver.events import EVENT_TYPES |
45 | -from provisioningserver.kernel_opts import KernelParameters |
46 | -from provisioningserver.rpc.exceptions import NoConnectionsAvailable |
47 | - |
48 | - |
49 | -def find_rack_controller_for_pxeconfig_request(request): |
50 | - """Find the rack controller responsible for a `pxeconfig` request. |
51 | - |
52 | - Looks for the `rackcontroller_id` parameter in the request. If there is |
53 | - none, figures it out based on the requesting IP as a compatibility |
54 | - measure. In that case, the result may be incorrect. |
55 | - """ |
56 | - rackcontroller_id = request.GET.get('rackcontroller_id', None) |
57 | - if rackcontroller_id is None: |
58 | - return find_rack_controller(request) |
59 | - else: |
60 | - return RackController.objects.get(system_id=rackcontroller_id) |
61 | - |
62 | - |
63 | -def get_node_from_mac_string(mac_string): |
64 | - """Get a Node object from a MAC address string. |
65 | - |
66 | - Returns a Node object or None if no node with the given MAC address exists. |
67 | - |
68 | - :param mac_string: MAC address string in the form "12-34-56-78-9a-bc" |
69 | - :return: Node object or None |
70 | - """ |
71 | - if mac_string is None: |
72 | - return None |
73 | - interface = get_one( |
74 | - Interface.objects.filter( |
75 | - type=INTERFACE_TYPE.PHYSICAL, mac_address=mac_string)) |
76 | - return interface.node if interface else None |
77 | - |
78 | - |
79 | -def get_boot_image( |
80 | - rack_controller, osystem, architecture, subarchitecture, series, |
81 | - purpose): |
82 | - """Obtain the first available boot image for this rack controller for the |
83 | - given osystem, architecture, subarchitecute, series, and purpose.""" |
84 | - # When local booting a node we put it through a PXE cycle. In |
85 | - # this case it requests a purpose of "local" when looking for |
86 | - # boot images. To avoid unnecessary work, we can shortcut that |
87 | - # here and just return None right away. |
88 | - if purpose == "local": |
89 | - return None |
90 | - |
91 | - try: |
92 | - images = get_boot_images_for( |
93 | - rack_controller, osystem, architecture, subarchitecture, series) |
94 | - except (NoConnectionsAvailable, TimeoutError): |
95 | - logger.error( |
96 | - "Unable to identify boot image for (%s/%s/%s/%s/%s): " |
97 | - "no RPC connection to rack controller '%s'", |
98 | - osystem, architecture, subarchitecture, series, purpose, |
99 | - rack_controller.hostname) |
100 | - return None |
101 | - for image in images: |
102 | - # get_boot_images_for returns all images that match the subarchitecure |
103 | - # and its supporting subarches. Only want to return the image with |
104 | - # the exact subarchitecture. |
105 | - if (image['subarchitecture'] == subarchitecture and |
106 | - image['purpose'] == purpose): |
107 | - return image |
108 | - logger.error( |
109 | - "Unable to identify boot image for (%s/%s/%s/%s/%s): " |
110 | - "rack controller '%s' does not have matching boot image.", |
111 | - osystem, architecture, subarchitecture, series, purpose, |
112 | - rack_controller.hostname) |
113 | - return None |
114 | - |
115 | - |
116 | -# XXX newell 2014-10-01 bug=1376489: Currently logging the pxe |
117 | -# request for the given boot purpose here. It would be better to |
118 | -# someday fix this to create the log entries when the file |
119 | -# transfer completes, rather than when we receive the first |
120 | -# (and potentially duplicate) packet of the request. |
121 | -def event_log_pxe_request(node, purpose): |
122 | - """Log PXE request to node's event log.""" |
123 | - options = { |
124 | - 'commissioning': "commissioning", |
125 | - 'xinstall': "curtin install", |
126 | - 'install': "d-i install", |
127 | - 'local': "local boot", |
128 | - 'poweroff': "power off", |
129 | - } |
130 | - Event.objects.create_node_event( |
131 | - system_id=node.system_id, event_type=EVENT_TYPES.NODE_PXE_REQUEST, |
132 | - event_description=options[purpose]) |
133 | - |
134 | - |
135 | -DEFAULT_ARCH = 'i386' |
136 | - |
137 | - |
138 | -def pxeconfig(request): |
139 | - """Get the PXE configuration given a node's details. |
140 | - |
141 | - Returns a JSON object corresponding to a |
142 | - :class:`provisioningserver.kernel_opts.KernelParameters` instance. |
143 | - |
144 | - This is now fairly decoupled from pxelinux's TFTP filename encoding |
145 | - mechanism, with one notable exception. Call this function with (mac, arch, |
146 | - subarch) and it will do the right thing. If details it needs are missing |
147 | - (ie. arch/subarch missing when the MAC is supplied but unknown), then it |
148 | - will as an exception return an HTTP NO_CONTENT (204) in the expectation |
149 | - that this will be translated to a TFTP file not found and pxelinux (or an |
150 | - emulator) will fall back to default-<arch>-<subarch> (in the case of an |
151 | - alternate architecture emulator) or just straight to default (in the case |
152 | - of native pxelinux on i386 or amd64). See bug 1041092 for details and |
153 | - discussion. |
154 | - |
155 | - :param mac: MAC address to produce a boot configuration for. |
156 | - :param arch: Architecture name (in the pxelinux namespace, eg. 'arm' not |
157 | - 'armhf'). |
158 | - :param subarch: Subarchitecture name (in the pxelinux namespace). |
159 | - :param local: The IP address of the cluster controller. |
160 | - :param remote: The IP address of the booting node. |
161 | - :param rackcontroller_id: system_id of the rackcontroller responsible for |
162 | - this node. If omitted, the call will attempt to figure it out based on |
163 | - the requesting IP address, for compatibility. Passing |
164 | - `rackcontroller_id` is preferred. |
165 | - """ |
166 | - request_mac = request.GET.get('mac', None) |
167 | - cluster_ip = get_mandatory_param(request.GET, "local") |
168 | - bios_boot_method = request.GET.get('bios_boot_method', None) |
169 | - node = get_node_from_mac_string(request_mac) |
170 | - |
171 | - if node is not None: |
172 | - node_needs_saving = False |
173 | - |
174 | - # Only update the booting interface for the node if it has |
175 | - # changed. |
176 | - if (node.boot_interface is None or |
177 | - node.boot_interface.mac_address != request_mac): |
178 | - node.boot_interface = PhysicalInterface.objects.get( |
179 | - mac_address=request_mac) |
180 | - node_needs_saving = True |
181 | - |
182 | - # Update the last IP address the cluster booted from. |
183 | - if (node.boot_cluster_ip is None or |
184 | - node.boot_cluster_ip != cluster_ip): |
185 | - node.boot_cluster_ip = cluster_ip |
186 | - node_needs_saving = True |
187 | - |
188 | - # Only update the bios boot method if its changed. |
189 | - if node.bios_boot_method != bios_boot_method: |
190 | - node.bios_boot_method = bios_boot_method |
191 | - node_needs_saving = True |
192 | - |
193 | - if node_needs_saving: |
194 | - node.save() |
195 | - |
196 | - if node is None or node.get_boot_purpose() == "commissioning": |
197 | - osystem = Config.objects.get_config('commissioning_osystem') |
198 | - series = Config.objects.get_config('commissioning_distro_series') |
199 | - else: |
200 | - osystem = node.get_osystem() |
201 | - series = node.get_distro_series() |
202 | - |
203 | - rack_controller = find_rack_controller_for_pxeconfig_request(request) |
204 | - if node: |
205 | - arch, subarch = node.architecture.split('/') |
206 | - preseed_url = compose_preseed_url(node, rack_controller) |
207 | - hostname = node.hostname |
208 | - domain = node.domain.name |
209 | - |
210 | - # Pre MAAS-1.9 the subarchitecture defined any kernel the node needed |
211 | - # to be able to boot. This could be a hardware enablement kernel(e.g |
212 | - # hwe-t) or something like highbank. With MAAS-1.9 any hardware |
213 | - # enablement kernel must be specifed in the hwe_kernel field, any other |
214 | - # kernel, such as highbank, is still specifed as a |
215 | - # subarchitecture. Since Ubuntu does not support architecture specific |
216 | - # hardware enablement kernels(i.e a highbank hwe-t kernel on precise) |
217 | - # we give precedence to any kernel defined in the subarchitecture field |
218 | - if subarch == "generic" and node.hwe_kernel: |
219 | - subarch = node.hwe_kernel |
220 | - elif(subarch == "generic" and |
221 | - node.get_boot_purpose() == "commissioning" and |
222 | - node.min_hwe_kernel): |
223 | - subarch = node.min_hwe_kernel |
224 | - else: |
225 | - preseed_url = compose_enlistment_preseed_url( |
226 | - rack_controller=rack_controller) |
227 | - hostname = 'maas-enlist' |
228 | - domain = 'local' |
229 | - |
230 | - arch = get_optional_param(request.GET, 'arch') |
231 | - if arch is None: |
232 | - if 'mac' in request.GET: |
233 | - # Request was pxelinux.cfg/01-<mac>, so attempt fall back |
234 | - # to pxelinux.cfg/default-<arch>-<subarch> for arch detection. |
235 | - return HttpResponse(status=int(http.client.NO_CONTENT)) |
236 | - else: |
237 | - # Look in BootResource for an resource that actually exists for |
238 | - # the current series. If nothing is found, fall back to i386 |
239 | - # like we used to. LP #1181334 |
240 | - resource = ( |
241 | - BootResource.objects.get_default_commissioning_resource( |
242 | - osystem, series)) |
243 | - if resource is None: |
244 | - arch = DEFAULT_ARCH |
245 | - else: |
246 | - arch, _ = resource.split_arch() |
247 | - |
248 | - default_min_hwe_kernel = Config.objects.get_config( |
249 | - 'default_min_hwe_kernel') |
250 | - if default_min_hwe_kernel: |
251 | - subarch = get_optional_param( |
252 | - request.GET, 'subarch', default_min_hwe_kernel) |
253 | - else: |
254 | - subarch = get_optional_param( |
255 | - request.GET, 'subarch', 'generic') |
256 | - |
257 | - # If we are booting with "xinstall", then we should always return the |
258 | - # commissioning operating system and distro_series. |
259 | - if node is None: |
260 | - purpose = "commissioning" # enlistment |
261 | - else: |
262 | - purpose = node.get_boot_purpose() |
263 | - event_log_pxe_request(node, purpose) |
264 | - |
265 | - # Use only the commissioning osystem and series, for operating systems |
266 | - # other than Ubuntu. As Ubuntu supports HWE kernels, and needs to use |
267 | - # that kernel to perform the installation. |
268 | - if purpose == "xinstall" and osystem != 'ubuntu': |
269 | - osystem = Config.objects.get_config('commissioning_osystem') |
270 | - series = Config.objects.get_config('commissioning_distro_series') |
271 | - |
272 | - if purpose == 'poweroff': |
273 | - # In order to power the node off, we need to get it booted in the |
274 | - # commissioning environment and issue a `poweroff` command. |
275 | - boot_purpose = 'commissioning' |
276 | - else: |
277 | - boot_purpose = purpose |
278 | - |
279 | - # We use as our default label the label of the most recent image for |
280 | - # the criteria we've assembled above. If there is no latest image |
281 | - # (which should never happen in reality but may happen in tests), we |
282 | - # fall back to using 'no-such-image' as our default. |
283 | - latest_image = get_boot_image( |
284 | - rack_controller, osystem, arch, subarch, series, boot_purpose) |
285 | - if latest_image is None: |
286 | - # XXX 2014-03-18 gmb bug=1294131: |
287 | - # We really ought to raise an exception here so that client |
288 | - # and server can handle it according to their needs. At the |
289 | - # moment, though, that breaks too many tests in awkward |
290 | - # ways. |
291 | - latest_label = 'no-such-image' |
292 | - else: |
293 | - latest_label = latest_image['label'] |
294 | - # subarch may be different from the request because newer images |
295 | - # support older hardware enablement, e.g. trusty/generic |
296 | - # supports trusty/hwe-s. We must override the subarch to the one |
297 | - # on the image otherwise the config path will be wrong if |
298 | - # get_latest_image() returned an image with a different subarch. |
299 | - subarch = latest_image['subarchitecture'] |
300 | - label = get_optional_param(request.GET, 'label', latest_label) |
301 | - |
302 | - if node is not None: |
303 | - # We don't care if the kernel opts is from the global setting or a tag, |
304 | - # just get the options |
305 | - _, effective_kernel_opts = node.get_effective_kernel_options() |
306 | - |
307 | - # Add any extra options from a third party driver. |
308 | - use_driver = Config.objects.get_config('enable_third_party_drivers') |
309 | - if use_driver: |
310 | - driver = get_third_party_driver(node) |
311 | - driver_kernel_opts = driver.get('kernel_opts', '') |
312 | - |
313 | - combined_opts = ('%s %s' % ( |
314 | - '' if effective_kernel_opts is None else effective_kernel_opts, |
315 | - driver_kernel_opts)).strip() |
316 | - if len(combined_opts): |
317 | - extra_kernel_opts = combined_opts |
318 | - else: |
319 | - extra_kernel_opts = None |
320 | - else: |
321 | - extra_kernel_opts = effective_kernel_opts |
322 | - else: |
323 | - # If there's no node defined then we must be enlisting here, but |
324 | - # we still need to return the global kernel options. |
325 | - extra_kernel_opts = Config.objects.get_config("kernel_opts") |
326 | - |
327 | - server_address = get_maas_facing_server_address( |
328 | - rack_controller=rack_controller) |
329 | - |
330 | - # If the node is enlisting and the arch is the default arch (i386), |
331 | - # use the dedicated enlistment template which performs architecture |
332 | - # detection. |
333 | - if node is None and arch == DEFAULT_ARCH: |
334 | - boot_purpose = "enlist" |
335 | - |
336 | - params = KernelParameters( |
337 | - osystem=osystem, arch=arch, subarch=subarch, release=series, |
338 | - label=label, purpose=boot_purpose, hostname=hostname, domain=domain, |
339 | - preseed_url=preseed_url, log_host=server_address, |
340 | - fs_host=cluster_ip, extra_opts=extra_kernel_opts) |
341 | - |
342 | - return HttpResponse( |
343 | - json.dumps(params._asdict()), |
344 | - content_type="application/json") |
345 | |
346 | === removed file 'src/maasserver/api/tests/test_pxeconfig.py' |
347 | --- src/maasserver/api/tests/test_pxeconfig.py 2016-02-29 19:21:03 +0000 |
348 | +++ src/maasserver/api/tests/test_pxeconfig.py 1970-01-01 00:00:00 +0000 |
349 | @@ -1,712 +0,0 @@ |
350 | -# Copyright 2013-2016 Canonical Ltd. This software is licensed under the |
351 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
352 | - |
353 | -"""Tests for PXE configuration retrieval from the API.""" |
354 | - |
355 | -__all__ = [] |
356 | - |
357 | -import http.client |
358 | -import json |
359 | -from unittest import skip |
360 | - |
361 | -from crochet import TimeoutError |
362 | -from django.conf import settings |
363 | -from django.core.urlresolvers import reverse |
364 | -from django.test.client import RequestFactory |
365 | -from maasserver import ( |
366 | - preseed as preseed_module, |
367 | - server_address, |
368 | -) |
369 | -from maasserver.api import pxeconfig as pxeconfig_module |
370 | -from maasserver.api.pxeconfig import ( |
371 | - event_log_pxe_request, |
372 | - find_rack_controller_for_pxeconfig_request, |
373 | - get_boot_image, |
374 | -) |
375 | -from maasserver.clusterrpc.testing.boot_images import make_rpc_boot_image |
376 | -from maasserver.enum import ( |
377 | - BOOT_RESOURCE_TYPE, |
378 | - INTERFACE_TYPE, |
379 | - NODE_STATUS, |
380 | -) |
381 | -from maasserver.models import ( |
382 | - Config, |
383 | - Event, |
384 | - Interface, |
385 | - Node, |
386 | -) |
387 | -from maasserver.preseed import ( |
388 | - compose_enlistment_preseed_url, |
389 | - compose_preseed_url, |
390 | -) |
391 | -from maasserver.testing.architecture import make_usable_architecture |
392 | -from maasserver.testing.config import RegionConfigurationFixture |
393 | -from maasserver.testing.factory import factory |
394 | -from maasserver.testing.testcase import MAASServerTestCase |
395 | -from maasserver.utils.orm import reload_object |
396 | -from maastesting.fakemethod import FakeMethod |
397 | -from maastesting.matchers import ( |
398 | - MockCalledOnceWith, |
399 | - MockNotCalled, |
400 | -) |
401 | -from mock import sentinel |
402 | -from netaddr import IPNetwork |
403 | -from provisioningserver import kernel_opts |
404 | -from provisioningserver.kernel_opts import KernelParameters |
405 | -from provisioningserver.rpc.exceptions import NoConnectionsAvailable |
406 | -from testtools.matchers import ( |
407 | - Contains, |
408 | - ContainsAll, |
409 | - Equals, |
410 | - Is, |
411 | - MatchesListwise, |
412 | - StartsWith, |
413 | -) |
414 | - |
415 | - |
416 | -class TestGetBootImage(MAASServerTestCase): |
417 | - |
418 | - def setUp(self): |
419 | - super().setUp() |
420 | - self.rackcontroller = factory.make_RackController() |
421 | - |
422 | - def test__returns_None_when_connection_unavailable(self): |
423 | - self.patch( |
424 | - pxeconfig_module, |
425 | - 'get_boot_images_for').side_effect = NoConnectionsAvailable |
426 | - self.assertEqual( |
427 | - None, |
428 | - get_boot_image( |
429 | - self.rackcontroller, sentinel.osystem, |
430 | - sentinel.architecture, sentinel.subarchitecture, |
431 | - sentinel.series, sentinel.purpose)) |
432 | - |
433 | - def test__returns_None_when_timeout_error(self): |
434 | - self.patch( |
435 | - pxeconfig_module, |
436 | - 'get_boot_images_for').side_effect = TimeoutError |
437 | - self.assertEqual( |
438 | - None, |
439 | - get_boot_image( |
440 | - self.rackcontroller, sentinel.osystem, |
441 | - sentinel.architecture, sentinel.subarchitecture, |
442 | - sentinel.series, sentinel.purpose)) |
443 | - |
444 | - def test__returns_matching_image(self): |
445 | - subarch = factory.make_name('subarch') |
446 | - purpose = factory.make_name('purpose') |
447 | - boot_image = make_rpc_boot_image( |
448 | - subarchitecture=subarch, purpose=purpose) |
449 | - other_images = [make_rpc_boot_image() for _ in range(3)] |
450 | - self.patch( |
451 | - pxeconfig_module, |
452 | - 'get_boot_images_for').return_value = other_images + [boot_image] |
453 | - self.assertEqual( |
454 | - boot_image, |
455 | - get_boot_image( |
456 | - self.rackcontroller, sentinel.osystem, |
457 | - sentinel.architecture, subarch, |
458 | - sentinel.series, purpose)) |
459 | - |
460 | - def test__returns_None_on_no_matching_image(self): |
461 | - subarch = factory.make_name('subarch') |
462 | - purpose = factory.make_name('purpose') |
463 | - other_images = [make_rpc_boot_image() for _ in range(3)] |
464 | - self.patch( |
465 | - pxeconfig_module, |
466 | - 'get_boot_images_for').return_value = other_images |
467 | - self.assertEqual( |
468 | - None, |
469 | - get_boot_image( |
470 | - self.rackcontroller, sentinel.osystem, |
471 | - sentinel.architecture, subarch, |
472 | - sentinel.series, purpose)) |
473 | - |
474 | - def test__returns_None_immediately_if_purpose_is_local(self): |
475 | - self.patch(pxeconfig_module, 'get_boot_images_for') |
476 | - self.expectThat( |
477 | - get_boot_image( |
478 | - self.rackcontroller, sentinel.osystem, |
479 | - sentinel.architecture, sentinel.subarchitecture, |
480 | - sentinel.series, "local"), |
481 | - Is(None)) |
482 | - self.expectThat(pxeconfig_module.get_boot_images_for, MockNotCalled()) |
483 | - |
484 | - |
485 | -class TestPXEConfigAPI(MAASServerTestCase): |
486 | - def setUp(self): |
487 | - super(TestPXEConfigAPI, self).setUp() |
488 | - self.useFixture(RegionConfigurationFixture()) |
489 | - |
490 | - def get_default_params(self, rack_controller=None): |
491 | - if rack_controller is None: |
492 | - rack_controller = factory.make_RackController() |
493 | - return { |
494 | - "local": factory.make_ipv4_address(), |
495 | - "remote": factory.make_ipv4_address(), |
496 | - "rackcontroller_id": rack_controller.system_id, |
497 | - } |
498 | - |
499 | - def get_mac_params(self): |
500 | - node = factory.make_Node_with_Interface_on_Subnet( |
501 | - status=NODE_STATUS.DEPLOYING) |
502 | - arch, subarch = node.split_arch() |
503 | - image = make_rpc_boot_image( |
504 | - osystem=node.get_osystem(), release=node.get_distro_series(), |
505 | - architecture=arch, subarchitecture=subarch, |
506 | - purpose='install') |
507 | - self.patch( |
508 | - preseed_module, |
509 | - 'get_boot_images_for').return_value = [image] |
510 | - params = self.get_default_params() |
511 | - params['mac'] = node.get_boot_interface().mac_address |
512 | - return params |
513 | - |
514 | - def get_pxeconfig(self, params=None): |
515 | - """Make a request to `pxeconfig`, and return its response dict.""" |
516 | - if params is None: |
517 | - params = self.get_default_params() |
518 | - response = self.client.get(reverse('pxeconfig'), params) |
519 | - return json.loads(response.content.decode(settings.DEFAULT_CHARSET)) |
520 | - |
521 | - @skip("XXX: GavinPanella 2016-02-24 bug=1549206: Fails spuriously.") |
522 | - def test_pxeconfig_returns_json(self): |
523 | - params = self.get_default_params() |
524 | - response = self.client.get( |
525 | - reverse('pxeconfig'), params) |
526 | - self.assertThat( |
527 | - ( |
528 | - response.status_code, |
529 | - response['Content-Type'], |
530 | - response.content, |
531 | - response.content.decode(settings.DEFAULT_CHARSET), |
532 | - ), |
533 | - MatchesListwise( |
534 | - ( |
535 | - Equals(http.client.OK), |
536 | - Equals("application/json"), |
537 | - StartsWith(b'{'), |
538 | - Contains('arch'), |
539 | - )), |
540 | - response) |
541 | - |
542 | - def test_pxeconfig_returns_all_kernel_parameters(self): |
543 | - params = self.get_default_params() |
544 | - self.assertThat( |
545 | - self.get_pxeconfig(params), |
546 | - ContainsAll(KernelParameters._fields)) |
547 | - |
548 | - def test_pxeconfig_returns_success_for_known_node(self): |
549 | - params = self.get_mac_params() |
550 | - response = self.client.get(reverse('pxeconfig'), params) |
551 | - self.assertEqual(http.client.OK, response.status_code) |
552 | - |
553 | - def test_pxeconfig_returns_no_content_for_unknown_node(self): |
554 | - params = dict( |
555 | - mac=factory.make_mac_address(delimiter='-'), |
556 | - local=factory.make_ipv4_address()) |
557 | - response = self.client.get(reverse('pxeconfig'), params) |
558 | - self.assertEqual(http.client.NO_CONTENT, response.status_code) |
559 | - |
560 | - def test_pxeconfig_returns_success_for_detailed_but_unknown_node(self): |
561 | - architecture = make_usable_architecture(self) |
562 | - arch, subarch = architecture.split('/') |
563 | - rack = factory.make_RackController() |
564 | - params = dict( |
565 | - self.get_default_params(), |
566 | - mac=factory.make_mac_address(delimiter='-'), |
567 | - arch=arch, |
568 | - subarch=subarch, |
569 | - rackcontroller_id=rack.system_id) |
570 | - response = self.client.get(reverse('pxeconfig'), params) |
571 | - self.assertEqual(http.client.OK, response.status_code) |
572 | - |
573 | - def test_pxeconfig_returns_global_kernel_params_for_enlisting_node(self): |
574 | - # An 'enlisting' node means it looks like a node with details but we |
575 | - # don't know about it yet. It should still receive the global |
576 | - # kernel options. |
577 | - value = factory.make_string() |
578 | - Config.objects.set_config("kernel_opts", value) |
579 | - architecture = make_usable_architecture(self) |
580 | - arch, subarch = architecture.split('/') |
581 | - rack = factory.make_RackController() |
582 | - params = dict( |
583 | - self.get_default_params(), |
584 | - mac=factory.make_mac_address(delimiter='-'), |
585 | - arch=arch, |
586 | - subarch=subarch, |
587 | - rackcontroller_id=rack.system_id) |
588 | - response = self.client.get(reverse('pxeconfig'), params) |
589 | - response_dict = json.loads( |
590 | - response.content.decode(settings.DEFAULT_CHARSET)) |
591 | - self.assertEqual(value, response_dict['extra_opts']) |
592 | - |
593 | - def test_pxeconfig_uses_present_boot_image(self): |
594 | - osystem = Config.objects.get_config('commissioning_osystem') |
595 | - release = Config.objects.get_config('commissioning_distro_series') |
596 | - resource_name = '%s/%s' % (osystem, release) |
597 | - factory.make_usable_boot_resource( |
598 | - rtype=BOOT_RESOURCE_TYPE.SYNCED, |
599 | - name=resource_name, architecture='amd64/generic') |
600 | - params = self.get_default_params() |
601 | - params_out = self.get_pxeconfig(params) |
602 | - self.assertEqual("amd64", params_out["arch"]) |
603 | - |
604 | - def test_pxeconfig_defaults_to_i386_for_default(self): |
605 | - # As a lowest-common-denominator, i386 is chosen when the node is not |
606 | - # yet known to MAAS. |
607 | - expected_arch = tuple( |
608 | - make_usable_architecture( |
609 | - self, arch_name="i386", subarch_name="generic").split("/")) |
610 | - params = self.get_default_params() |
611 | - params_out = self.get_pxeconfig(params) |
612 | - observed_arch = params_out["arch"], params_out["subarch"] |
613 | - self.assertEqual(expected_arch, observed_arch) |
614 | - |
615 | - def test_pxeconfig_uses_fixed_hostname_for_enlisting_node(self): |
616 | - params = self.get_default_params() |
617 | - self.assertEqual( |
618 | - 'maas-enlist', self.get_pxeconfig(params).get('hostname')) |
619 | - |
620 | - def test_pxeconfig_uses_local_domain_for_enlisting_node(self): |
621 | - params = self.get_default_params() |
622 | - self.assertEqual( |
623 | - "local", |
624 | - self.get_pxeconfig(params).get('domain')) |
625 | - |
626 | - def test_pxeconfig_splits_domain_from_node_hostname(self): |
627 | - host = factory.make_name('host') |
628 | - domainname = factory.make_name('domain') |
629 | - domain = factory.make_Domain(name=domainname) |
630 | - full_hostname = '.'.join([host, domainname]) |
631 | - node = factory.make_Node_with_Interface_on_Subnet( |
632 | - hostname=full_hostname, domain=domain) |
633 | - interface = node.get_boot_interface() |
634 | - params = self.get_default_params() |
635 | - params['mac'] = interface.mac_address |
636 | - pxe_config = self.get_pxeconfig(params) |
637 | - self.assertEqual(host, pxe_config.get('hostname')) |
638 | - self.assertNotIn(domain, pxe_config.values()) |
639 | - |
640 | - def test_pxeconfig_uses_domain_for_node(self): |
641 | - node = factory.make_Node_with_Interface_on_Subnet() |
642 | - interface = node.get_boot_interface() |
643 | - params = self.get_default_params() |
644 | - params['mac'] = interface.mac_address |
645 | - self.assertEqual( |
646 | - node.domain.name, |
647 | - self.get_pxeconfig(params).get('domain')) |
648 | - |
649 | - def get_without_param(self, param): |
650 | - """Request a `pxeconfig()` response, but omit `param` from request.""" |
651 | - params = self.get_params() |
652 | - del params[param] |
653 | - return self.client.get(reverse('pxeconfig'), params) |
654 | - |
655 | - def silence_get_ephemeral_name(self): |
656 | - # Silence `get_ephemeral_name` to avoid having to fetch the |
657 | - # ephemeral name from the filesystem. |
658 | - self.patch( |
659 | - kernel_opts, 'get_ephemeral_name', |
660 | - FakeMethod(result=factory.make_string())) |
661 | - |
662 | - def test_pxeconfig_has_enlistment_preseed_url_for_default(self): |
663 | - self.silence_get_ephemeral_name() |
664 | - params = self.get_default_params() |
665 | - response = self.client.get(reverse('pxeconfig'), params) |
666 | - self.assertEqual( |
667 | - compose_enlistment_preseed_url(), |
668 | - json.loads(response.content.decode( |
669 | - settings.DEFAULT_CHARSET))["preseed_url"]) |
670 | - |
671 | - def test_pxeconfig_enlistment_preseed_url_detects_request_origin(self): |
672 | - self.silence_get_ephemeral_name() |
673 | - hostname = factory.make_hostname() |
674 | - rack_url = 'http://%s' % hostname |
675 | - network = IPNetwork("10.1.1/24") |
676 | - ip = factory.pick_ip_in_network(network) |
677 | - self.patch(server_address, 'resolve_hostname').return_value = {ip} |
678 | - rack_controller = factory.make_RackController(url=rack_url) |
679 | - subnet = factory.make_Subnet(cidr=str(network.cidr), dhcp_on=True) |
680 | - subnet.vlan.primary_rack = rack_controller |
681 | - subnet.vlan.save() |
682 | - params = self.get_default_params(rack_controller) |
683 | - del params['rackcontroller_id'] |
684 | - |
685 | - # Simulate that the request originates from ip by setting |
686 | - # 'REMOTE_ADDR'. |
687 | - response = self.client.get( |
688 | - reverse('pxeconfig'), params, REMOTE_ADDR=ip) |
689 | - self.assertThat( |
690 | - json.loads( |
691 | - response.content.decode( |
692 | - settings.DEFAULT_CHARSET))["preseed_url"], |
693 | - StartsWith(rack_url)) |
694 | - |
695 | - def test_pxeconfig_enlistment_log_host_url_detects_request_origin(self): |
696 | - self.silence_get_ephemeral_name() |
697 | - hostname = factory.make_hostname() |
698 | - rack_url = 'http://%s' % hostname |
699 | - network = IPNetwork("10.1.1/24") |
700 | - ip = factory.pick_ip_in_network(network) |
701 | - mock = self.patch(server_address, 'resolve_hostname') |
702 | - mock.return_value = {ip} |
703 | - rack_controller = factory.make_RackController(url=rack_url) |
704 | - subnet = factory.make_Subnet(cidr=str(network.cidr), dhcp_on=True) |
705 | - subnet.vlan.primary_rack = rack_controller |
706 | - subnet.vlan.save() |
707 | - params = self.get_default_params(rack_controller) |
708 | - del params['rackcontroller_id'] |
709 | - |
710 | - # Simulate that the request originates from ip by setting |
711 | - # 'REMOTE_ADDR'. |
712 | - response = self.client.get( |
713 | - reverse('pxeconfig'), params, REMOTE_ADDR=ip) |
714 | - self.assertEqual( |
715 | - (ip, hostname), |
716 | - (json.loads( |
717 | - response.content.decode( |
718 | - settings.DEFAULT_CHARSET))["log_host"], |
719 | - mock.call_args[0][0])) |
720 | - |
721 | - def test_pxeconfig_enlistment_checks_default_min_hwe_kernel(self): |
722 | - params = self.get_default_params() |
723 | - params['arch'] = 'armhf' |
724 | - Config.objects.set_config('default_min_hwe_kernel', 'hwe-v') |
725 | - response = self.client.get(reverse('pxeconfig'), params) |
726 | - self.assertEqual( |
727 | - "hwe-v", |
728 | - json.loads( |
729 | - response.content.decode(settings.DEFAULT_CHARSET))["subarch"]) |
730 | - |
731 | - def test_pxeconfig_has_preseed_url_for_known_node(self): |
732 | - rack_controller = factory.make_RackController() |
733 | - params = self.get_mac_params() |
734 | - params["rackcontroller_id"] = rack_controller.system_id |
735 | - node = Interface.objects.get(mac_address=params['mac']).node |
736 | - response = self.client.get(reverse('pxeconfig'), params) |
737 | - self.assertEqual( |
738 | - compose_preseed_url(node, rack_controller), |
739 | - json.loads( |
740 | - response.content.decode( |
741 | - settings.DEFAULT_CHARSET))["preseed_url"]) |
742 | - |
743 | - def test_find_rack_for_pxeconfig_request_uses_rack_system_id(self): |
744 | - params = self.get_mac_params() |
745 | - rack = factory.make_RackController() |
746 | - params['rackcontroller_id'] = rack.system_id |
747 | - request = RequestFactory().get(reverse('pxeconfig'), params) |
748 | - self.assertEqual( |
749 | - rack, |
750 | - find_rack_controller_for_pxeconfig_request(request)) |
751 | - |
752 | - def test_preseed_url_for_known_node_uses_rack_url(self): |
753 | - rack_url = 'http://%s' % factory.make_name('host') |
754 | - network = IPNetwork("10.1.1/24") |
755 | - ip = factory.pick_ip_in_network(network) |
756 | - self.patch(server_address, 'resolve_hostname').return_value = {ip} |
757 | - rack_controller = factory.make_RackController(url=rack_url) |
758 | - node = factory.make_Node_with_Interface_on_Subnet( |
759 | - primary_rack=rack_controller) |
760 | - params = self.get_default_params() |
761 | - params['mac'] = str(node.get_boot_interface().mac_address) |
762 | - params['rackcontroller_id'] = rack_controller.system_id |
763 | - |
764 | - # Simulate that the request originates from ip by setting |
765 | - # 'REMOTE_ADDR'. |
766 | - response = self.client.get( |
767 | - reverse('pxeconfig'), params, REMOTE_ADDR=ip) |
768 | - self.assertThat( |
769 | - json.loads( |
770 | - response.content.decode( |
771 | - settings.DEFAULT_CHARSET))["preseed_url"], |
772 | - StartsWith(rack_url)) |
773 | - |
774 | - def test_pxeconfig_uses_boot_purpose_enlistment(self): |
775 | - # test that purpose is set to "commissioning" for |
776 | - # enlistment (when node is None). |
777 | - params = self.get_default_params() |
778 | - params['arch'] = 'armhf' |
779 | - response = self.client.get(reverse('pxeconfig'), params) |
780 | - self.assertEqual( |
781 | - "commissioning", |
782 | - json.loads( |
783 | - response.content.decode(settings.DEFAULT_CHARSET))["purpose"]) |
784 | - |
785 | - def test_pxeconfig_returns_enlist_config_if_no_architecture_provided(self): |
786 | - params = self.get_default_params() |
787 | - pxe_config = self.get_pxeconfig(params) |
788 | - self.assertEqual('enlist', pxe_config['purpose']) |
789 | - |
790 | - def test_pxeconfig_returns_fs_host_as_cluster_controller(self): |
791 | - # The kernel parameter `fs_host` points to the cluster controller |
792 | - # address, which is passed over within the `local` parameter. |
793 | - params = self.get_default_params() |
794 | - kernel_params = KernelParameters(**self.get_pxeconfig(params)) |
795 | - self.assertEqual(params["local"], kernel_params.fs_host) |
796 | - |
797 | - def test_pxeconfig_returns_extra_kernel_options(self): |
798 | - extra_kernel_opts = factory.make_string() |
799 | - Config.objects.set_config('kernel_opts', extra_kernel_opts) |
800 | - params = self.get_mac_params() |
801 | - pxe_config = self.get_pxeconfig(params) |
802 | - self.assertEqual(extra_kernel_opts, pxe_config['extra_opts']) |
803 | - |
804 | - def test_pxeconfig_returns_None_for_extra_kernel_opts(self): |
805 | - params = self.get_mac_params() |
806 | - pxe_config = self.get_pxeconfig(params) |
807 | - self.assertEqual(None, pxe_config['extra_opts']) |
808 | - |
809 | - def test_pxeconfig_returns_commissioning_for_insane_state(self): |
810 | - node = factory.make_Node_with_Interface_on_Subnet( |
811 | - status=NODE_STATUS.BROKEN) |
812 | - nic = node.get_boot_interface() |
813 | - params = self.get_default_params() |
814 | - params['mac'] = nic.mac_address |
815 | - pxe_config = self.get_pxeconfig(params) |
816 | - # The 'purpose' of the PXE config is 'commissioning' here |
817 | - # even if the 'purpose' returned by node.get_boot_purpose |
818 | - # is 'poweroff' because MAAS needs to bring the machine |
819 | - # up in a commissioning environment in order to power |
820 | - # the machine down. |
821 | - self.assertEqual('commissioning', pxe_config['purpose']) |
822 | - |
823 | - def test_pxeconfig_returns_commissioning_for_ready_node(self): |
824 | - node = factory.make_Node_with_Interface_on_Subnet( |
825 | - status=NODE_STATUS.READY) |
826 | - nic = node.get_boot_interface() |
827 | - params = self.get_default_params() |
828 | - params['mac'] = nic.mac_address |
829 | - pxe_config = self.get_pxeconfig(params) |
830 | - self.assertEqual('commissioning', pxe_config['purpose']) |
831 | - |
832 | - def test_pxeconfig_returns_image_subarch_not_node_subarch(self): |
833 | - # In the scenario such as deploying trusty on an hwe-s subarch |
834 | - # node, the code will have fallen back to using trusty's generic |
835 | - # image as per the supported_subarches on the image. However, |
836 | - # pxeconfig needs to make sure the image path refers to the |
837 | - # subarch from the image, rather than the requested one. |
838 | - osystem = 'ubuntu' |
839 | - release = Config.objects.get_config('default_distro_series') |
840 | - rack = factory.make_RackController() |
841 | - generic_image = make_rpc_boot_image( |
842 | - osystem=osystem, release=release, |
843 | - architecture="amd64", subarchitecture="generic", |
844 | - purpose='install') |
845 | - hwe_s_image = make_rpc_boot_image( |
846 | - osystem=osystem, release=release, |
847 | - architecture="amd64", subarchitecture="hwe-s", |
848 | - purpose='install') |
849 | - self.patch( |
850 | - preseed_module, |
851 | - 'get_boot_images_for').return_value = [generic_image, hwe_s_image] |
852 | - self.patch( |
853 | - pxeconfig_module, |
854 | - 'get_boot_images_for').return_value = [generic_image, hwe_s_image] |
855 | - node = factory.make_Node_with_Interface_on_Subnet( |
856 | - status=NODE_STATUS.DEPLOYING, |
857 | - architecture="amd64/hwe-s", |
858 | - primary_rack=rack) |
859 | - params = self.get_default_params() |
860 | - params['rackcontroller_id'] = rack.system_id |
861 | - params['mac'] = node.get_boot_interface().mac_address |
862 | - params['arch'] = "amd64" |
863 | - params['subarch'] = "hwe-s" |
864 | - |
865 | - params_out = self.get_pxeconfig(params) |
866 | - self.assertEqual("hwe-s", params_out["subarch"]) |
867 | - |
868 | - def test_pxeconfig_calls_event_log_pxe_request(self): |
869 | - node = factory.make_Node_with_Interface_on_Subnet() |
870 | - nic = node.get_boot_interface() |
871 | - params = self.get_default_params() |
872 | - params['mac'] = nic.mac_address |
873 | - event_log_pxe_request = self.patch_autospec( |
874 | - pxeconfig_module, 'event_log_pxe_request') |
875 | - self.client.get(reverse('pxeconfig'), params) |
876 | - self.assertThat( |
877 | - event_log_pxe_request, |
878 | - MockCalledOnceWith(node, node.get_boot_purpose())) |
879 | - |
880 | - def test_event_log_pxe_request_for_known_boot_purpose(self): |
881 | - purposes = [ |
882 | - ("commissioning", "commissioning"), |
883 | - ("install", "d-i install"), |
884 | - ("xinstall", "curtin install"), |
885 | - ("local", "local boot"), |
886 | - ("poweroff", "power off")] |
887 | - for purpose, description in purposes: |
888 | - node = factory.make_Node() |
889 | - event_log_pxe_request(node, purpose) |
890 | - self.assertEqual( |
891 | - description, |
892 | - Event.objects.get(node=node).description) |
893 | - |
894 | - def test_pxeconfig_sets_boot_interface_when_empty(self): |
895 | - node = factory.make_Node_with_Interface_on_Subnet() |
896 | - nic = node.get_boot_interface() |
897 | - node.boot_interface = None |
898 | - node.save() |
899 | - params = self.get_default_params() |
900 | - params['mac'] = nic.mac_address |
901 | - self.client.get(reverse('pxeconfig'), params) |
902 | - node = reload_object(node) |
903 | - self.assertEqual(nic, node.boot_interface) |
904 | - |
905 | - def test_pxeconfig_updates_boot_interface_when_changed(self): |
906 | - node = factory.make_Node_with_Interface_on_Subnet() |
907 | - node.boot_interface = node.get_boot_interface() |
908 | - node.save() |
909 | - nic = factory.make_Interface( |
910 | - INTERFACE_TYPE.PHYSICAL, node=node, vlan=node.boot_interface.vlan) |
911 | - params = self.get_default_params() |
912 | - params['mac'] = nic.mac_address |
913 | - self.client.get(reverse('pxeconfig'), params) |
914 | - node = reload_object(node) |
915 | - self.assertEqual(nic, node.boot_interface) |
916 | - |
917 | - def test_pxeconfig_doesnt_update_boot_interface_when_same(self): |
918 | - node = factory.make_Node_with_Interface_on_Subnet() |
919 | - node.boot_interface = node.get_boot_interface() |
920 | - node.save() |
921 | - params = self.get_default_params() |
922 | - params['mac'] = node.boot_interface.mac_address |
923 | - node.boot_cluster_ip = params['local'] |
924 | - node.save() |
925 | - mock_save = self.patch(Node, 'save') |
926 | - self.client.get(reverse('pxeconfig'), params) |
927 | - self.assertThat(mock_save, MockNotCalled()) |
928 | - |
929 | - def test_pxeconfig_sets_boot_cluster_ip_when_empty(self): |
930 | - node = factory.make_Node_with_Interface_on_Subnet() |
931 | - params = self.get_default_params() |
932 | - params['mac'] = node.get_boot_interface().mac_address |
933 | - self.client.get(reverse('pxeconfig'), params) |
934 | - node = reload_object(node) |
935 | - self.assertEqual(params['local'], node.boot_cluster_ip) |
936 | - |
937 | - def test_pxeconfig_updates_boot_cluster_ip_when_changed(self): |
938 | - node = factory.make_Node_with_Interface_on_Subnet() |
939 | - node.boot_cluster_ip = factory.make_ipv4_address() |
940 | - node.save() |
941 | - params = self.get_default_params() |
942 | - params['mac'] = node.get_boot_interface().mac_address |
943 | - self.client.get(reverse('pxeconfig'), params) |
944 | - node = reload_object(node) |
945 | - self.assertEqual(params['local'], node.boot_cluster_ip) |
946 | - |
947 | - def test_pxeconfig_doesnt_update_boot_cluster_ip_when_same(self): |
948 | - node = factory.make_Node_with_Interface_on_Subnet() |
949 | - node.boot_interface = node.get_boot_interface() |
950 | - params = self.get_default_params() |
951 | - params['mac'] = node.boot_interface.mac_address |
952 | - node.boot_cluster_ip = params['local'] |
953 | - node.save() |
954 | - mock_save = self.patch(Node, 'save') |
955 | - self.client.get(reverse('pxeconfig'), params) |
956 | - self.assertThat(mock_save, MockNotCalled()) |
957 | - |
958 | - def test_pxeconfig_updates_bios_boot_method(self): |
959 | - node = factory.make_Node_with_Interface_on_Subnet() |
960 | - nic = node.get_boot_interface() |
961 | - params = self.get_default_params() |
962 | - params['mac'] = nic.mac_address |
963 | - params['bios_boot_method'] = 'pxe' |
964 | - self.client.get(reverse('pxeconfig'), params) |
965 | - node = reload_object(node) |
966 | - self.assertEqual('pxe', node.bios_boot_method) |
967 | - |
968 | - def test_pxeconfig_doesnt_update_bios_boot_method_when_same(self): |
969 | - node = factory.make_Node_with_Interface_on_Subnet( |
970 | - bios_boot_method='uefi') |
971 | - nic = node.get_boot_interface() |
972 | - params = self.get_default_params() |
973 | - params['mac'] = nic.mac_address |
974 | - params['bios_boot_method'] = 'uefi' |
975 | - node.boot_interface = nic |
976 | - node.boot_cluster_ip = params['local'] |
977 | - node.save() |
978 | - mock_save = self.patch(Node, 'save') |
979 | - self.client.get(reverse('pxeconfig'), params) |
980 | - self.assertThat(mock_save, MockNotCalled()) |
981 | - |
982 | - def test_pxeconfig_returns_commissioning_os_series_for_other_oses(self): |
983 | - osystem = Config.objects.get_config('default_osystem') |
984 | - release = Config.objects.get_config('default_distro_series') |
985 | - os_image = make_rpc_boot_image(purpose='xinstall') |
986 | - architecture = '%s/%s' % ( |
987 | - os_image['architecture'], os_image['subarchitecture']) |
988 | - self.patch( |
989 | - preseed_module, |
990 | - 'get_boot_images_for').return_value = [os_image] |
991 | - self.patch( |
992 | - pxeconfig_module, |
993 | - 'get_boot_images_for').return_value = [os_image] |
994 | - rack_controller = factory.make_RackController() |
995 | - node = factory.make_Node_with_Interface_on_Subnet( |
996 | - status=NODE_STATUS.DEPLOYING, |
997 | - osystem=os_image['osystem'], |
998 | - distro_series=os_image['release'], |
999 | - architecture=architecture, |
1000 | - primary_rack=rack_controller) |
1001 | - params = self.get_default_params() |
1002 | - params['rackcontroller_id'] = rack_controller.system_id |
1003 | - params['mac'] = node.get_boot_interface().mac_address |
1004 | - params_out = self.get_pxeconfig(params) |
1005 | - self.assertEqual(osystem, params_out["osystem"]) |
1006 | - self.assertEqual(release, params_out["release"]) |
1007 | - |
1008 | - def test_pxeconfig_commissioning_node_uses_min_hwe_kernel(self): |
1009 | - node = factory.make_Node_with_Interface_on_Subnet( |
1010 | - min_hwe_kernel="hwe-v") |
1011 | - nic = node.get_boot_interface() |
1012 | - self.patch(Node, 'get_boot_purpose').return_value = "commissioning" |
1013 | - params = self.get_default_params() |
1014 | - params['mac'] = nic.mac_address |
1015 | - response = self.client.get(reverse('pxeconfig'), params) |
1016 | - self.assertEqual( |
1017 | - "hwe-v", |
1018 | - json.loads( |
1019 | - response.content.decode(settings.DEFAULT_CHARSET))["subarch"]) |
1020 | - |
1021 | - def test_pxeconfig_returns_ubuntu_os_series_for_ubuntu_xinstall(self): |
1022 | - ubuntu_image = make_rpc_boot_image( |
1023 | - osystem='ubuntu', purpose='xinstall') |
1024 | - architecture = '%s/%s' % ( |
1025 | - ubuntu_image['architecture'], ubuntu_image['subarchitecture']) |
1026 | - self.patch( |
1027 | - preseed_module, |
1028 | - 'get_boot_images_for').return_value = [ubuntu_image] |
1029 | - self.patch( |
1030 | - pxeconfig_module, |
1031 | - 'get_boot_images_for').return_value = [ubuntu_image] |
1032 | - rack_controller = factory.make_RackController() |
1033 | - node = factory.make_Node_with_Interface_on_Subnet( |
1034 | - status=NODE_STATUS.DEPLOYING, osystem='ubuntu', |
1035 | - distro_series=ubuntu_image['release'], architecture=architecture, |
1036 | - primary_rack=rack_controller) |
1037 | - params = self.get_default_params() |
1038 | - params['rackcontroller_id'] = rack_controller.system_id |
1039 | - params['mac'] = node.get_boot_interface().mac_address |
1040 | - params_out = self.get_pxeconfig(params) |
1041 | - self.assertEqual(ubuntu_image['release'], params_out["release"]) |
1042 | - |
1043 | - def test_pxeconfig_returns_commissioning_os_when_erasing_disks(self): |
1044 | - commissioning_osystem = factory.make_name("os") |
1045 | - Config.objects.set_config( |
1046 | - "commissioning_osystem", commissioning_osystem) |
1047 | - commissioning_series = factory.make_name("series") |
1048 | - Config.objects.set_config( |
1049 | - "commissioning_distro_series", commissioning_series) |
1050 | - rack_controller = factory.make_RackController() |
1051 | - node = factory.make_Node_with_Interface_on_Subnet( |
1052 | - status=NODE_STATUS.DISK_ERASING, |
1053 | - osystem=factory.make_name("centos"), |
1054 | - distro_series=factory.make_name("release"), |
1055 | - primary_rack=rack_controller) |
1056 | - params = self.get_default_params() |
1057 | - params['rackcontroller_id'] = rack_controller.system_id |
1058 | - params['mac'] = node.get_boot_interface().mac_address |
1059 | - params_out = self.get_pxeconfig(params) |
1060 | - self.assertEqual(commissioning_osystem, params_out['osystem']) |
1061 | - self.assertEqual(commissioning_series, params_out['release']) |
1062 | |
1063 | === modified file 'src/maasserver/models/node.py' |
1064 | --- src/maasserver/models/node.py 2016-03-16 07:19:41 +0000 |
1065 | +++ src/maasserver/models/node.py 2016-03-17 13:36:34 +0000 |
1066 | @@ -2585,7 +2585,7 @@ |
1067 | """Get the boot interface this node is expected to boot from. |
1068 | |
1069 | Normally, this will be the boot interface last used in a |
1070 | - pxeconfig() API request for the node, as recorded in the |
1071 | + GetBootConfig RPC request for the node, as recorded in the |
1072 | 'boot_interface' property. However, if the node hasn't booted since |
1073 | the 'boot_interface' property was added to the Node model, this will |
1074 | return the node's first interface instead. |
1075 | |
1076 | === modified file 'src/maasserver/urls_api.py' |
1077 | --- src/maasserver/urls_api.py 2016-02-03 14:34:13 +0000 |
1078 | +++ src/maasserver/urls_api.py 2016-03-17 13:36:34 +0000 |
1079 | @@ -111,7 +111,6 @@ |
1080 | PartitionHandler, |
1081 | PartitionsHandler, |
1082 | ) |
1083 | -from maasserver.api.pxeconfig import pxeconfig |
1084 | from maasserver.api.rackcontrollers import ( |
1085 | RackControllerHandler, |
1086 | RackControllersHandler, |
1087 | @@ -292,7 +291,6 @@ |
1088 | '', |
1089 | url(r'doc/$', api_doc, name='api-doc'), |
1090 | url(r'describe/$', describe, name='describe'), |
1091 | - url(r'pxeconfig/$', pxeconfig, name='pxeconfig'), |
1092 | url(r'version/$', version_handler, name='version_handler'), |
1093 | ) |
1094 | |
1095 | |
1096 | === modified file 'src/provisioningserver/config.py' |
1097 | --- src/provisioningserver/config.py 2016-02-10 20:24:35 +0000 |
1098 | +++ src/provisioningserver/config.py 2016-03-17 13:36:34 +0000 |
1099 | @@ -767,11 +767,6 @@ |
1100 | accept_python=True, if_missing=get_tentative_path( |
1101 | "/var/lib/maas/boot-resources/current"))) |
1102 | |
1103 | - @property |
1104 | - def tftp_generator_url(self): |
1105 | - """The URL at which to obtain the TFTP options for a node.""" |
1106 | - return "%s/api/2.0/pxeconfig/" % self.maas_url.rstrip("/") |
1107 | - |
1108 | # GRUB options. |
1109 | |
1110 | @property |
1111 | |
1112 | === modified file 'src/provisioningserver/tests/test_config.py' |
1113 | --- src/provisioningserver/tests/test_config.py 2016-02-10 20:24:35 +0000 |
1114 | +++ src/provisioningserver/tests/test_config.py 2016-03-17 13:36:34 +0000 |
1115 | @@ -667,26 +667,6 @@ |
1116 | self.assertEqual({"cluster_uuid": str(example_uuid)}, config.store) |
1117 | |
1118 | |
1119 | -class TestClusterConfigurationTFTPGeneratorURL(MAASTestCase): |
1120 | - """Tests for `ClusterConfiguration.tftp_generator_url`.""" |
1121 | - |
1122 | - def test__is_relative_to_maas_url(self): |
1123 | - random_url = factory.make_simple_http_url() |
1124 | - self.useFixture(ClusterConfigurationFixture(maas_url=random_url)) |
1125 | - with ClusterConfiguration.open() as configuration: |
1126 | - self.assertEqual( |
1127 | - random_url + "/api/2.0/pxeconfig/", |
1128 | - configuration.tftp_generator_url) |
1129 | - |
1130 | - def test__strips_trailing_slashes_from_maas_url(self): |
1131 | - random_url = factory.make_simple_http_url(path="foobar/") |
1132 | - self.useFixture(ClusterConfigurationFixture(maas_url=random_url)) |
1133 | - with ClusterConfiguration.open() as configuration: |
1134 | - self.assertEqual( |
1135 | - random_url.rstrip("/") + "/api/2.0/pxeconfig/", |
1136 | - configuration.tftp_generator_url) |
1137 | - |
1138 | - |
1139 | class TestClusterConfigurationGRUBRoot(MAASTestCase): |
1140 | """Tests for `ClusterConfiguration.grub_root`.""" |
1141 |
All red!!!!