Merge lp:~jtv/maas/extract-pxeconfig into lp:~maas-committers/maas/trunk
- extract-pxeconfig
- Merge into trunk
Proposed by
Jeroen T. Vermeulen
Status: | Merged |
---|---|
Approved by: | Jeroen T. Vermeulen |
Approved revision: | no longer in the source branch. |
Merged at revision: | 2736 |
Proposed branch: | lp:~jtv/maas/extract-pxeconfig |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
554 lines (+251/-213) 4 files modified
src/maasserver/api/api.py (+0/-209) src/maasserver/api/pxeconfig.py (+247/-0) src/maasserver/api/tests/test_pxeconfig.py (+3/-3) src/maasserver/urls_api.py (+1/-1) |
To merge this branch: | bzr merge lp:~jtv/maas/extract-pxeconfig |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jeroen T. Vermeulen (community) | Approve | ||
Review via email: mp+231111@code.launchpad.net |
Commit message
Extract API handler: pxeconfig.
Description of the change
For self-approval after cursory inspection.
Jeroen
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/maasserver/api/api.py' |
2 | --- src/maasserver/api/api.py 2014-08-17 01:43:43 +0000 |
3 | +++ src/maasserver/api/api.py 2014-08-17 02:07:38 +0000 |
4 | @@ -69,7 +69,6 @@ |
5 | "NodeMacHandler", |
6 | "NodeMacsHandler", |
7 | "NodesHandler", |
8 | - "pxeconfig", |
9 | "render_api_docs", |
10 | "store_node_power_parameters", |
11 | ] |
12 | @@ -127,7 +126,6 @@ |
13 | NODE_PERMISSION, |
14 | NODE_STATUS, |
15 | NODEGROUP_STATUS, |
16 | - PRESEED_TYPE, |
17 | ) |
18 | from maasserver.exceptions import ( |
19 | MAASAPIBadRequest, |
20 | @@ -156,7 +154,6 @@ |
21 | validate_config_name, |
22 | ) |
23 | from maasserver.models import ( |
24 | - BootImage, |
25 | Config, |
26 | DHCPLease, |
27 | MACAddress, |
28 | @@ -172,18 +169,10 @@ |
29 | ) |
30 | from maasserver.node_action import Commission |
31 | from maasserver.node_constraint_filter_forms import AcquireNodeForm |
32 | -from maasserver.preseed import ( |
33 | - compose_enlistment_preseed_url, |
34 | - compose_preseed_url, |
35 | - get_preseed_type_for, |
36 | - ) |
37 | -from maasserver.server_address import get_maas_facing_server_address |
38 | -from maasserver.third_party_drivers import get_third_party_driver |
39 | from maasserver.utils import ( |
40 | build_absolute_uri, |
41 | find_nodegroup, |
42 | get_local_cluster_UUID, |
43 | - strip_domain, |
44 | ) |
45 | from maasserver.utils.orm import ( |
46 | get_first, |
47 | @@ -192,7 +181,6 @@ |
48 | from metadataserver.models import NodeResult |
49 | import netaddr |
50 | from piston.utils import rc |
51 | -from provisioningserver.kernel_opts import KernelParameters |
52 | from provisioningserver.logger import get_maas_logger |
53 | from provisioningserver.power_schema import UNKNOWN_POWER_TYPE |
54 | import simplejson as json |
55 | @@ -1910,203 +1898,6 @@ |
56 | context_instance=RequestContext(request)) |
57 | |
58 | |
59 | -def get_boot_purpose(node): |
60 | - """Return a suitable "purpose" for this boot, e.g. "install".""" |
61 | - # XXX: allenap bug=1031406 2012-07-31: The boot purpose is still in |
62 | - # flux. It may be that there will just be an "ephemeral" environment and |
63 | - # an "install" environment, and the differing behaviour between, say, |
64 | - # enlistment and commissioning - both of which will use the "ephemeral" |
65 | - # environment - will be governed by varying the preseed or PXE |
66 | - # configuration. |
67 | - if node is None: |
68 | - # This node is enlisting, for which we use a commissioning image. |
69 | - return "commissioning" |
70 | - elif node.status == NODE_STATUS.COMMISSIONING: |
71 | - # It is commissioning. |
72 | - return "commissioning" |
73 | - elif node.status == NODE_STATUS.ALLOCATED: |
74 | - # Install the node if netboot is enabled, otherwise boot locally. |
75 | - if node.netboot: |
76 | - preseed_type = get_preseed_type_for(node) |
77 | - if preseed_type == PRESEED_TYPE.CURTIN: |
78 | - return "xinstall" |
79 | - else: |
80 | - return "install" |
81 | - else: |
82 | - return "local" # TODO: Investigate. |
83 | - else: |
84 | - # Just poweroff? TODO: Investigate. Perhaps even send an IPMI signal |
85 | - # to turn off power. |
86 | - return "poweroff" |
87 | - |
88 | - |
89 | -def get_node_from_mac_string(mac_string): |
90 | - """Get a Node object from a MAC address string. |
91 | - |
92 | - Returns a Node object or None if no node with the given MAC address exists. |
93 | - |
94 | - :param mac_string: MAC address string in the form "12-34-56-78-9a-bc" |
95 | - :return: Node object or None |
96 | - """ |
97 | - if mac_string is None: |
98 | - return None |
99 | - macaddress = get_one(MACAddress.objects.filter(mac_address=mac_string)) |
100 | - return macaddress.node if macaddress else None |
101 | - |
102 | - |
103 | -def find_nodegroup_for_pxeconfig_request(request): |
104 | - """Find the nodegroup responsible for a `pxeconfig` request. |
105 | - |
106 | - Looks for the `cluster_uuid` parameter in the request. If there is |
107 | - none, figures it out based on the requesting IP as a compatibility |
108 | - measure. In that case, the result may be incorrect. |
109 | - """ |
110 | - uuid = request.GET.get('cluster_uuid', None) |
111 | - if uuid is None: |
112 | - return find_nodegroup(request) |
113 | - else: |
114 | - return NodeGroup.objects.get(uuid=uuid) |
115 | - |
116 | - |
117 | -def pxeconfig(request): |
118 | - """Get the PXE configuration given a node's details. |
119 | - |
120 | - Returns a JSON object corresponding to a |
121 | - :class:`provisioningserver.kernel_opts.KernelParameters` instance. |
122 | - |
123 | - This is now fairly decoupled from pxelinux's TFTP filename encoding |
124 | - mechanism, with one notable exception. Call this function with (mac, arch, |
125 | - subarch) and it will do the right thing. If details it needs are missing |
126 | - (ie. arch/subarch missing when the MAC is supplied but unknown), then it |
127 | - will as an exception return an HTTP NO_CONTENT (204) in the expectation |
128 | - that this will be translated to a TFTP file not found and pxelinux (or an |
129 | - emulator) will fall back to default-<arch>-<subarch> (in the case of an |
130 | - alternate architecture emulator) or just straight to default (in the case |
131 | - of native pxelinux on i386 or amd64). See bug 1041092 for details and |
132 | - discussion. |
133 | - |
134 | - :param mac: MAC address to produce a boot configuration for. |
135 | - :param arch: Architecture name (in the pxelinux namespace, eg. 'arm' not |
136 | - 'armhf'). |
137 | - :param subarch: Subarchitecture name (in the pxelinux namespace). |
138 | - :param local: The IP address of the cluster controller. |
139 | - :param remote: The IP address of the booting node. |
140 | - :param cluster_uuid: UUID of the cluster responsible for this node. |
141 | - If omitted, the call will attempt to figure it out based on the |
142 | - requesting IP address, for compatibility. Passing `cluster_uuid` |
143 | - is preferred. |
144 | - """ |
145 | - node = get_node_from_mac_string(request.GET.get('mac', None)) |
146 | - |
147 | - if node is None or node.status == NODE_STATUS.COMMISSIONING: |
148 | - osystem = Config.objects.get_config('commissioning_osystem') |
149 | - series = Config.objects.get_config('commissioning_distro_series') |
150 | - else: |
151 | - osystem = node.get_osystem() |
152 | - series = node.get_distro_series() |
153 | - |
154 | - if node: |
155 | - arch, subarch = node.architecture.split('/') |
156 | - preseed_url = compose_preseed_url(node) |
157 | - # The node's hostname may include a domain, but we ignore that |
158 | - # and use the one from the nodegroup instead. |
159 | - hostname = strip_domain(node.hostname) |
160 | - nodegroup = node.nodegroup |
161 | - domain = nodegroup.name |
162 | - else: |
163 | - nodegroup = find_nodegroup_for_pxeconfig_request(request) |
164 | - preseed_url = compose_enlistment_preseed_url(nodegroup=nodegroup) |
165 | - hostname = 'maas-enlist' |
166 | - domain = Config.objects.get_config('enlistment_domain') |
167 | - |
168 | - arch = get_optional_param(request.GET, 'arch') |
169 | - if arch is None: |
170 | - if 'mac' in request.GET: |
171 | - # Request was pxelinux.cfg/01-<mac>, so attempt fall back |
172 | - # to pxelinux.cfg/default-<arch>-<subarch> for arch detection. |
173 | - return HttpResponse(status=httplib.NO_CONTENT) |
174 | - else: |
175 | - # Look in BootImage for an image that actually exists for the |
176 | - # current series. If nothing is found, fall back to i386 like |
177 | - # we used to. LP #1181334 |
178 | - image = BootImage.objects.get_default_arch_image_in_nodegroup( |
179 | - nodegroup, osystem, series, purpose='commissioning') |
180 | - if image is None: |
181 | - arch = 'i386' |
182 | - else: |
183 | - arch = image.architecture |
184 | - |
185 | - subarch = get_optional_param(request.GET, 'subarch', 'generic') |
186 | - |
187 | - # If we are booting with "xinstall", then we should always return the |
188 | - # commissioning operating system and distro_series. |
189 | - purpose = get_boot_purpose(node) |
190 | - if purpose == "xinstall": |
191 | - osystem = Config.objects.get_config('commissioning_osystem') |
192 | - series = Config.objects.get_config('commissioning_distro_series') |
193 | - |
194 | - # We use as our default label the label of the most recent image for |
195 | - # the criteria we've assembled above. If there is no latest image |
196 | - # (which should never happen in reality but may happen in tests), we |
197 | - # fall back to using 'no-such-image' as our default. |
198 | - latest_image = BootImage.objects.get_latest_image( |
199 | - nodegroup, osystem, arch, subarch, series, purpose) |
200 | - if latest_image is None: |
201 | - # XXX 2014-03-18 gmb bug=1294131: |
202 | - # We really ought to raise an exception here so that client |
203 | - # and server can handle it according to their needs. At the |
204 | - # moment, though, that breaks too many tests in awkward |
205 | - # ways. |
206 | - latest_label = 'no-such-image' |
207 | - else: |
208 | - latest_label = latest_image.label |
209 | - # subarch may be different from the request because newer images |
210 | - # support older hardware enablement, e.g. trusty/generic |
211 | - # supports trusty/hwe-s. We must override the subarch to the one |
212 | - # on the image otherwise the config path will be wrong if |
213 | - # get_latest_image() returned an image with a different subarch. |
214 | - subarch = latest_image.subarchitecture |
215 | - label = get_optional_param(request.GET, 'label', latest_label) |
216 | - |
217 | - if node is not None: |
218 | - # We don't care if the kernel opts is from the global setting or a tag, |
219 | - # just get the options |
220 | - _, effective_kernel_opts = node.get_effective_kernel_options() |
221 | - |
222 | - # Add any extra options from a third party driver. |
223 | - use_driver = Config.objects.get_config('enable_third_party_drivers') |
224 | - if use_driver: |
225 | - driver = get_third_party_driver(node) |
226 | - driver_kernel_opts = driver.get('kernel_opts', '') |
227 | - |
228 | - combined_opts = ('%s %s' % ( |
229 | - '' if effective_kernel_opts is None else effective_kernel_opts, |
230 | - driver_kernel_opts)).strip() |
231 | - if len(combined_opts): |
232 | - extra_kernel_opts = combined_opts |
233 | - else: |
234 | - extra_kernel_opts = None |
235 | - else: |
236 | - extra_kernel_opts = effective_kernel_opts |
237 | - else: |
238 | - # If there's no node defined then we must be enlisting here, but |
239 | - # we still need to return the global kernel options. |
240 | - extra_kernel_opts = Config.objects.get_config("kernel_opts") |
241 | - |
242 | - server_address = get_maas_facing_server_address(nodegroup=nodegroup) |
243 | - cluster_address = get_mandatory_param(request.GET, "local") |
244 | - |
245 | - params = KernelParameters( |
246 | - osystem=osystem, arch=arch, subarch=subarch, release=series, |
247 | - label=label, purpose=purpose, hostname=hostname, domain=domain, |
248 | - preseed_url=preseed_url, log_host=server_address, |
249 | - fs_host=cluster_address, extra_opts=extra_kernel_opts) |
250 | - |
251 | - return HttpResponse( |
252 | - json.dumps(params._asdict()), |
253 | - content_type="application/json") |
254 | - |
255 | - |
256 | class CommissioningResultsHandler(OperationsHandler): |
257 | """Read the collection of NodeResult in the MAAS.""" |
258 | api_doc_section_name = "Commissioning results" |
259 | |
260 | === added file 'src/maasserver/api/pxeconfig.py' |
261 | --- src/maasserver/api/pxeconfig.py 1970-01-01 00:00:00 +0000 |
262 | +++ src/maasserver/api/pxeconfig.py 2014-08-17 02:07:38 +0000 |
263 | @@ -0,0 +1,247 @@ |
264 | +# Copyright 2014 Canonical Ltd. This software is licensed under the |
265 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
266 | + |
267 | +"""API handler: `pxeconfig`.""" |
268 | + |
269 | +from __future__ import ( |
270 | + absolute_import, |
271 | + print_function, |
272 | + unicode_literals, |
273 | + ) |
274 | + |
275 | +str = None |
276 | + |
277 | +__metaclass__ = type |
278 | +__all__ = [ |
279 | + 'pxeconfig', |
280 | + ] |
281 | + |
282 | + |
283 | +import httplib |
284 | + |
285 | +from django.http import HttpResponse |
286 | +from maasserver.api.utils import ( |
287 | + get_mandatory_param, |
288 | + get_optional_param, |
289 | + ) |
290 | +from maasserver.enum import ( |
291 | + NODE_STATUS, |
292 | + PRESEED_TYPE, |
293 | + ) |
294 | +from maasserver.models import ( |
295 | + BootImage, |
296 | + Config, |
297 | + MACAddress, |
298 | + NodeGroup, |
299 | + ) |
300 | +from maasserver.preseed import ( |
301 | + compose_enlistment_preseed_url, |
302 | + compose_preseed_url, |
303 | + get_preseed_type_for, |
304 | + ) |
305 | +from maasserver.server_address import get_maas_facing_server_address |
306 | +from maasserver.third_party_drivers import get_third_party_driver |
307 | +from maasserver.utils import ( |
308 | + find_nodegroup, |
309 | + strip_domain, |
310 | + ) |
311 | +from maasserver.utils.orm import get_one |
312 | +from provisioningserver.kernel_opts import KernelParameters |
313 | +import simplejson as json |
314 | + |
315 | + |
316 | +def find_nodegroup_for_pxeconfig_request(request): |
317 | + """Find the nodegroup responsible for a `pxeconfig` request. |
318 | + |
319 | + Looks for the `cluster_uuid` parameter in the request. If there is |
320 | + none, figures it out based on the requesting IP as a compatibility |
321 | + measure. In that case, the result may be incorrect. |
322 | + """ |
323 | + uuid = request.GET.get('cluster_uuid', None) |
324 | + if uuid is None: |
325 | + return find_nodegroup(request) |
326 | + else: |
327 | + return NodeGroup.objects.get(uuid=uuid) |
328 | + |
329 | + |
330 | +def get_node_from_mac_string(mac_string): |
331 | + """Get a Node object from a MAC address string. |
332 | + |
333 | + Returns a Node object or None if no node with the given MAC address exists. |
334 | + |
335 | + :param mac_string: MAC address string in the form "12-34-56-78-9a-bc" |
336 | + :return: Node object or None |
337 | + """ |
338 | + if mac_string is None: |
339 | + return None |
340 | + macaddress = get_one(MACAddress.objects.filter(mac_address=mac_string)) |
341 | + return macaddress.node if macaddress else None |
342 | + |
343 | + |
344 | +def get_boot_purpose(node): |
345 | + """Return a suitable "purpose" for this boot, e.g. "install".""" |
346 | + # XXX: allenap bug=1031406 2012-07-31: The boot purpose is still in |
347 | + # flux. It may be that there will just be an "ephemeral" environment and |
348 | + # an "install" environment, and the differing behaviour between, say, |
349 | + # enlistment and commissioning - both of which will use the "ephemeral" |
350 | + # environment - will be governed by varying the preseed or PXE |
351 | + # configuration. |
352 | + if node is None: |
353 | + # This node is enlisting, for which we use a commissioning image. |
354 | + return "commissioning" |
355 | + elif node.status == NODE_STATUS.COMMISSIONING: |
356 | + # It is commissioning. |
357 | + return "commissioning" |
358 | + elif node.status == NODE_STATUS.ALLOCATED: |
359 | + # Install the node if netboot is enabled, otherwise boot locally. |
360 | + if node.netboot: |
361 | + preseed_type = get_preseed_type_for(node) |
362 | + if preseed_type == PRESEED_TYPE.CURTIN: |
363 | + return "xinstall" |
364 | + else: |
365 | + return "install" |
366 | + else: |
367 | + return "local" # TODO: Investigate. |
368 | + else: |
369 | + # Just poweroff? TODO: Investigate. Perhaps even send an IPMI signal |
370 | + # to turn off power. |
371 | + return "poweroff" |
372 | + |
373 | + |
374 | +def pxeconfig(request): |
375 | + """Get the PXE configuration given a node's details. |
376 | + |
377 | + Returns a JSON object corresponding to a |
378 | + :class:`provisioningserver.kernel_opts.KernelParameters` instance. |
379 | + |
380 | + This is now fairly decoupled from pxelinux's TFTP filename encoding |
381 | + mechanism, with one notable exception. Call this function with (mac, arch, |
382 | + subarch) and it will do the right thing. If details it needs are missing |
383 | + (ie. arch/subarch missing when the MAC is supplied but unknown), then it |
384 | + will as an exception return an HTTP NO_CONTENT (204) in the expectation |
385 | + that this will be translated to a TFTP file not found and pxelinux (or an |
386 | + emulator) will fall back to default-<arch>-<subarch> (in the case of an |
387 | + alternate architecture emulator) or just straight to default (in the case |
388 | + of native pxelinux on i386 or amd64). See bug 1041092 for details and |
389 | + discussion. |
390 | + |
391 | + :param mac: MAC address to produce a boot configuration for. |
392 | + :param arch: Architecture name (in the pxelinux namespace, eg. 'arm' not |
393 | + 'armhf'). |
394 | + :param subarch: Subarchitecture name (in the pxelinux namespace). |
395 | + :param local: The IP address of the cluster controller. |
396 | + :param remote: The IP address of the booting node. |
397 | + :param cluster_uuid: UUID of the cluster responsible for this node. |
398 | + If omitted, the call will attempt to figure it out based on the |
399 | + requesting IP address, for compatibility. Passing `cluster_uuid` |
400 | + is preferred. |
401 | + """ |
402 | + node = get_node_from_mac_string(request.GET.get('mac', None)) |
403 | + |
404 | + if node is None or node.status == NODE_STATUS.COMMISSIONING: |
405 | + osystem = Config.objects.get_config('commissioning_osystem') |
406 | + series = Config.objects.get_config('commissioning_distro_series') |
407 | + else: |
408 | + osystem = node.get_osystem() |
409 | + series = node.get_distro_series() |
410 | + |
411 | + if node: |
412 | + arch, subarch = node.architecture.split('/') |
413 | + preseed_url = compose_preseed_url(node) |
414 | + # The node's hostname may include a domain, but we ignore that |
415 | + # and use the one from the nodegroup instead. |
416 | + hostname = strip_domain(node.hostname) |
417 | + nodegroup = node.nodegroup |
418 | + domain = nodegroup.name |
419 | + else: |
420 | + nodegroup = find_nodegroup_for_pxeconfig_request(request) |
421 | + preseed_url = compose_enlistment_preseed_url(nodegroup=nodegroup) |
422 | + hostname = 'maas-enlist' |
423 | + domain = Config.objects.get_config('enlistment_domain') |
424 | + |
425 | + arch = get_optional_param(request.GET, 'arch') |
426 | + if arch is None: |
427 | + if 'mac' in request.GET: |
428 | + # Request was pxelinux.cfg/01-<mac>, so attempt fall back |
429 | + # to pxelinux.cfg/default-<arch>-<subarch> for arch detection. |
430 | + return HttpResponse(status=httplib.NO_CONTENT) |
431 | + else: |
432 | + # Look in BootImage for an image that actually exists for the |
433 | + # current series. If nothing is found, fall back to i386 like |
434 | + # we used to. LP #1181334 |
435 | + image = BootImage.objects.get_default_arch_image_in_nodegroup( |
436 | + nodegroup, osystem, series, purpose='commissioning') |
437 | + if image is None: |
438 | + arch = 'i386' |
439 | + else: |
440 | + arch = image.architecture |
441 | + |
442 | + subarch = get_optional_param(request.GET, 'subarch', 'generic') |
443 | + |
444 | + # If we are booting with "xinstall", then we should always return the |
445 | + # commissioning operating system and distro_series. |
446 | + purpose = get_boot_purpose(node) |
447 | + if purpose == "xinstall": |
448 | + osystem = Config.objects.get_config('commissioning_osystem') |
449 | + series = Config.objects.get_config('commissioning_distro_series') |
450 | + |
451 | + # We use as our default label the label of the most recent image for |
452 | + # the criteria we've assembled above. If there is no latest image |
453 | + # (which should never happen in reality but may happen in tests), we |
454 | + # fall back to using 'no-such-image' as our default. |
455 | + latest_image = BootImage.objects.get_latest_image( |
456 | + nodegroup, osystem, arch, subarch, series, purpose) |
457 | + if latest_image is None: |
458 | + # XXX 2014-03-18 gmb bug=1294131: |
459 | + # We really ought to raise an exception here so that client |
460 | + # and server can handle it according to their needs. At the |
461 | + # moment, though, that breaks too many tests in awkward |
462 | + # ways. |
463 | + latest_label = 'no-such-image' |
464 | + else: |
465 | + latest_label = latest_image.label |
466 | + # subarch may be different from the request because newer images |
467 | + # support older hardware enablement, e.g. trusty/generic |
468 | + # supports trusty/hwe-s. We must override the subarch to the one |
469 | + # on the image otherwise the config path will be wrong if |
470 | + # get_latest_image() returned an image with a different subarch. |
471 | + subarch = latest_image.subarchitecture |
472 | + label = get_optional_param(request.GET, 'label', latest_label) |
473 | + |
474 | + if node is not None: |
475 | + # We don't care if the kernel opts is from the global setting or a tag, |
476 | + # just get the options |
477 | + _, effective_kernel_opts = node.get_effective_kernel_options() |
478 | + |
479 | + # Add any extra options from a third party driver. |
480 | + use_driver = Config.objects.get_config('enable_third_party_drivers') |
481 | + if use_driver: |
482 | + driver = get_third_party_driver(node) |
483 | + driver_kernel_opts = driver.get('kernel_opts', '') |
484 | + |
485 | + combined_opts = ('%s %s' % ( |
486 | + '' if effective_kernel_opts is None else effective_kernel_opts, |
487 | + driver_kernel_opts)).strip() |
488 | + if len(combined_opts): |
489 | + extra_kernel_opts = combined_opts |
490 | + else: |
491 | + extra_kernel_opts = None |
492 | + else: |
493 | + extra_kernel_opts = effective_kernel_opts |
494 | + else: |
495 | + # If there's no node defined then we must be enlisting here, but |
496 | + # we still need to return the global kernel options. |
497 | + extra_kernel_opts = Config.objects.get_config("kernel_opts") |
498 | + |
499 | + server_address = get_maas_facing_server_address(nodegroup=nodegroup) |
500 | + cluster_address = get_mandatory_param(request.GET, "local") |
501 | + |
502 | + params = KernelParameters( |
503 | + osystem=osystem, arch=arch, subarch=subarch, release=series, |
504 | + label=label, purpose=purpose, hostname=hostname, domain=domain, |
505 | + preseed_url=preseed_url, log_host=server_address, |
506 | + fs_host=cluster_address, extra_opts=extra_kernel_opts) |
507 | + |
508 | + return HttpResponse( |
509 | + json.dumps(params._asdict()), |
510 | + content_type="application/json") |
511 | |
512 | === modified file 'src/maasserver/api/tests/test_pxeconfig.py' |
513 | --- src/maasserver/api/tests/test_pxeconfig.py 2014-08-16 05:43:33 +0000 |
514 | +++ src/maasserver/api/tests/test_pxeconfig.py 2014-08-17 02:07:38 +0000 |
515 | @@ -20,8 +20,8 @@ |
516 | from django.core.urlresolvers import reverse |
517 | from django.test.client import RequestFactory |
518 | from maasserver import server_address |
519 | -from maasserver.api import api as api_module |
520 | -from maasserver.api.api import ( |
521 | +from maasserver.api import pxeconfig as pxeconfig_module |
522 | +from maasserver.api.pxeconfig import ( |
523 | find_nodegroup_for_pxeconfig_request, |
524 | get_boot_purpose, |
525 | ) |
526 | @@ -344,7 +344,7 @@ |
527 | def test_pxeconfig_uses_boot_purpose(self): |
528 | fake_boot_purpose = factory.make_name("purpose") |
529 | self.patch( |
530 | - api_module, "get_boot_purpose" |
531 | + pxeconfig_module, "get_boot_purpose" |
532 | ).return_value = fake_boot_purpose |
533 | response = self.client.get(reverse('pxeconfig'), |
534 | self.get_default_params()) |
535 | |
536 | === modified file 'src/maasserver/urls_api.py' |
537 | --- src/maasserver/urls_api.py 2014-08-17 01:43:43 +0000 |
538 | +++ src/maasserver/urls_api.py 2014-08-17 02:07:38 +0000 |
539 | @@ -31,7 +31,6 @@ |
540 | NodeMacHandler, |
541 | NodeMacsHandler, |
542 | NodesHandler, |
543 | - pxeconfig, |
544 | VersionHandler, |
545 | ) |
546 | from maasserver.api.auth import api_auth |
547 | @@ -67,6 +66,7 @@ |
548 | NodeGroupInterfaceHandler, |
549 | NodeGroupInterfacesHandler, |
550 | ) |
551 | +from maasserver.api.pxeconfig import pxeconfig |
552 | from maasserver.api.ssh_keys import ( |
553 | SSHKeyHandler, |
554 | SSHKeysHandler, |
Looks OK. Self-approving.