Merge lp:~dimitern/maas/1.2-node-view-shows-kernel-params into lp:~maas-committers/maas/trunk

Proposed by Dimiter Naydenov
Status: Rejected
Rejected by: Dimiter Naydenov
Proposed branch: lp:~dimitern/maas/1.2-node-view-shows-kernel-params
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 566 lines (+305/-31) (has conflicts)
15 files modified
src/maasserver/forms.py (+20/-0)
src/maasserver/static/css/components/blocks.css (+3/-0)
src/maasserver/templates/maasserver/form_field.html (+1/-1)
src/maasserver/templates/maasserver/node_list.html (+5/-0)
src/maasserver/templates/maasserver/node_view.html (+12/-0)
src/maasserver/templates/maasserver/settings.html (+15/-0)
src/maasserver/templates/maasserver/tag_view.html (+6/-0)
src/maasserver/tests/test_api.py (+45/-0)
src/maasserver/tests/test_forms.py (+75/-30)
src/maasserver/tests/test_views_nodes.py (+60/-0)
src/maasserver/views/nodes.py (+8/-0)
src/maasserver/views/settings.py (+9/-0)
src/provisioningserver/boot_images.py (+4/-0)
src/provisioningserver/tasks.py (+16/-0)
src/provisioningserver/tests/test_tasks.py (+26/-0)
Text conflict in src/maasserver/forms.py
Text conflict in src/maasserver/templates/maasserver/node_list.html
Text conflict in src/maasserver/tests/test_api.py
Text conflict in src/maasserver/tests/test_forms.py
Text conflict in src/maasserver/tests/test_views_nodes.py
Text conflict in src/provisioningserver/boot_images.py
Text conflict in src/provisioningserver/tasks.py
Text conflict in src/provisioningserver/tests/test_tasks.py
To merge this branch: bzr merge lp:~dimitern/maas/1.2-node-view-shows-kernel-params
Reviewer Review Type Date Requested Status
Huw Wilkins ui Pending
Review via email: mp+132911@code.launchpad.net

Description of the change

To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 11/5/2012 7:26 PM, Dimiter Naydenov wrote:
> Dimiter Naydenov has proposed merging
> lp:~dimitern/maas/1.2-node-view-shows-kernel-params into lp:maas.
>
> Requested reviews: Huw Wilkins (huwshimi): ui Related bugs: Bug
> #1044503 in MAAS: "kernel command line is not easily customizable"
> https://bugs.launchpad.net/maas/+bug/1044503
>
> For more details, see:
> https://code.launchpad.net/~dimitern/maas/1.2-node-view-shows-kernel-params/+merge/132911
>
> I'd like to request a UI review for the changes I'm about to make
> to show kernel boot parameters in the node view, tag view and
> settings page.
>
> http://people.canonical.com/~dimitern/maas-node-view-kernel-opts-tag.png
>
>
http://people.canonical.com/~dimitern/maas-node-view-kernel-opts-global.png
> http://people.canonical.com/~dimitern/maas-tag-view-kernel-opts.png
>
>
http://people.canonical.com/~dimitern/maas-settings-global-kernel-opts.png
>

- From my PoV, the only one I would change is probably making the global
parameter entry box a single line box, just extra wide, rather than
multiline. Though I'm not set on that.

John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.12 (Cygwin)
Comment: Using GnuPG with Mozilla - http://www.enigmail.net/

iEYEARECAAYFAlCX4UAACgkQJdeBCYSNAAMMdQCfRNFY5QAegoULhXQzY+VhLyx4
8isAoJEnHffo8/FA0JuTFI6XcKMVMna8
=lx+H
-----END PGP SIGNATURE-----

Revision history for this message
Nick Veitch (evilnick) wrote :

>http://people.canonical.com/~dimitern/maas-settings-global-kernel-opts.png

yeah, this one takes up quite a bit of vertical space on a page which is only going to get bigger - wider and shorter is the way to go...

Nick

Revision history for this message
Martin Packman (gz) wrote :

View and template changes look good, though the real branch will want proposing against lp:maas/1.2 rather than trunk.

Revision history for this message
Dimiter Naydenov (dimitern) wrote :

Thanks for the review and comments. I did the changes needed and now I'm rejecting this MP and recreating it, since it targets trunk, rather than 1.2.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'etc/celeryconfig_common.py'
2=== modified file 'src/maasserver/api.py'
3=== modified file 'src/maasserver/forms.py'
4--- src/maasserver/forms.py 2012-11-01 15:13:10 +0000
5+++ src/maasserver/forms.py 2012-11-07 10:56:25 +0000
6@@ -394,6 +394,7 @@
7 node.save()
8 for mac in self.cleaned_data['mac_addresses']:
9 node.add_mac_address(mac)
10+<<<<<<< TREE
11 hostname = self.cleaned_data['hostname']
12 stripped_hostname = strip_domain(hostname)
13 # Generate a hostname for this node if the provided hostname is
14@@ -404,6 +405,18 @@
15 IP_BASED_HOSTNAME_REGEXP.match(stripped_hostname) != None)
16 if generate_hostname:
17 node.set_random_hostname()
18+=======
19+ hostname = self.cleaned_data['hostname']
20+ stripped_hostname = strip_domain(hostname)
21+ # Generate a hostname for this node if the provided hostname is
22+ # IP-based (because this means that this name comes from a DNS
23+ # reverse query to the MAAS DNS) or an empty string.
24+ generate_hostname = (
25+ hostname == "" or
26+ IP_BASED_HOSTNAME_REGEXP.match(stripped_hostname) != None)
27+ if generate_hostname:
28+ node.set_mac_based_hostname(self.cleaned_data['mac_addresses'][0])
29+>>>>>>> MERGE-SOURCE
30 return node
31
32
33@@ -646,6 +659,13 @@
34 self._load_initials()
35
36
37+class GlobalKernelOptsForm(ConfigForm):
38+ """Settings page, Global Kernel Parameters section."""
39+ global_kernel_opts = forms.CharField(
40+ label="Boot parameters to pass to the kernel by default",
41+ required=False)
42+
43+
44 hostname_error_msg = "Enter a valid hostname (e.g. host.example.com)."
45
46
47
48=== modified file 'src/maasserver/models/node.py'
49=== modified file 'src/maasserver/static/css/components/blocks.css'
50--- src/maasserver/static/css/components/blocks.css 2012-10-03 03:59:53 +0000
51+++ src/maasserver/static/css/components/blocks.css 2012-11-07 10:56:25 +0000
52@@ -82,3 +82,6 @@
53 .size12 {
54 width: 720px;
55 }
56+.size12 input {
57+ width: 100%;
58+ }
59
60=== modified file 'src/maasserver/templates/maasserver/form_field.html'
61--- src/maasserver/templates/maasserver/form_field.html 2012-08-03 16:36:26 +0000
62+++ src/maasserver/templates/maasserver/form_field.html 2012-11-07 10:56:25 +0000
63@@ -1,5 +1,5 @@
64 {% load field_type %}
65-<li class="{{ field.html_name }}{% if field.errors %} error{% endif %}">
66+<li class="{{ field.html_name }}{% if css_class %} {{ css_class }}{% endif %}{% if field.errors %} error{% endif %}">
67 <label for="id_{{ field.html_name }}">
68 {% ifequal field|field_type "CheckboxInput" %}
69 {{ field }}
70
71=== modified file 'src/maasserver/templates/maasserver/node_list.html'
72--- src/maasserver/templates/maasserver/node_list.html 2012-10-24 12:51:10 +0000
73+++ src/maasserver/templates/maasserver/node_list.html 2012-11-07 10:56:25 +0000
74@@ -38,8 +38,13 @@
75 {% if input_query_error %}
76 <p class="form-errors">{{input_query_error}}</p>
77 {% endif %}
78+<<<<<<< TREE
79 {% include "maasserver/nodes_listing.html" with sorting="true" %}
80 {% include "maasserver/pagination.html" %}
81+=======
82+ {% include "maasserver/nodes_listing.html" %}
83+ {% include "maasserver/pagination.html" %}
84+>>>>>>> MERGE-SOURCE
85 <a id="addnode" href="#" class="button right space-top">+ Add node</a>
86 <div class="clear"></div>
87 <a class="right space-top" href="{% url "enlist-preseed-view" %}">View enlistment preseed</a>
88
89=== modified file 'src/maasserver/templates/maasserver/node_view.html'
90--- src/maasserver/templates/maasserver/node_view.html 2012-10-05 17:42:12 +0000
91+++ src/maasserver/templates/maasserver/node_view.html 2012-11-07 10:56:25 +0000
92@@ -85,6 +85,18 @@
93 {% endif %}
94 </span>
95 </li>
96+ <li class="block size10 first">
97+ <h4>Kernel Parameters
98+ {% if kernel_opts.is_global %}
99+ - from: <a href="{% url 'settings' %}">Global Kernel Parameters</a>
100+ {% elif kernel_opts.is_tag %}
101+ - from tag: <span><a href="{% url 'tag-view' kernel_opts.tag.name %}">{{ kernel_opts.tag.name }}</a></span>
102+ {% endif %}
103+ </h4>
104+ <span id="node_kernel_opts">
105+ {{ kernel_opts.value }}
106+ </span>
107+ </li>
108 {% if error_text %}
109 <li class="block first">
110 <h4>Error output</h4>
111
112=== modified file 'src/maasserver/templates/maasserver/settings.html'
113--- src/maasserver/templates/maasserver/settings.html 2012-10-04 07:50:22 +0000
114+++ src/maasserver/templates/maasserver/settings.html 2012-11-07 10:56:25 +0000
115@@ -125,6 +125,21 @@
116 <div class="clear"></div>
117 </div>
118 <div class="divider"></div>
119+ <div id="global_kernel_opts" class="block size7 first">
120+ <h2>Global Kernel Parameters</h2>
121+ <form action="{% url "settings" %}" method="post">
122+ {% csrf_token %}
123+ <ul>
124+ {% with field=kernelopts_form.global_kernel_opts %}
125+ {% include "maasserver/form_field.html" with css_class="size12" %}
126+ {% endwith %}
127+ </ul>
128+ <input type="hidden" name="kernelopts_submit" value="1" />
129+ <input type="submit" class="button right" value="Save" />
130+ </form>
131+ <div class="clear"></div>
132+ </div>
133+ <div class="divider"></div>
134 <div id="maas_and_network" class="block size7 first">
135 <h2>Network Configuration</h2>
136 <form action="{% url "settings" %}" method="post">
137
138=== modified file 'src/maasserver/templates/maasserver/tag_view.html'
139--- src/maasserver/templates/maasserver/tag_view.html 2012-10-24 12:51:10 +0000
140+++ src/maasserver/templates/maasserver/tag_view.html 2012-11-07 10:56:25 +0000
141@@ -19,6 +19,12 @@
142 <h4>Definition</h4>
143 <span>{{ tag.definition }}</span>
144 </li>
145+ {% if tag.kernel_params %}
146+ <li class="block size10">
147+ <h4>Kernel Parameters</h4>
148+ <span>{{ tag.kernel_params }}</span>
149+ </li>
150+ {% endif %}
151 {% if error_text %}
152 <li class="block first">
153 <h4>Error output</h4>
154
155=== modified file 'src/maasserver/testing/factory.py'
156=== modified file 'src/maasserver/tests/test_api.py'
157--- src/maasserver/tests/test_api.py 2012-11-06 18:35:35 +0000
158+++ src/maasserver/tests/test_api.py 2012-11-07 10:56:25 +0000
159@@ -4267,6 +4267,7 @@
160 (response.status_code, response.content))
161
162 def test_report_boot_images_warns_if_no_images_found(self):
163+<<<<<<< TREE
164 nodegroup = NodeGroup.objects.ensure_master()
165 factory.make_node_group() # Second nodegroup with no images.
166 recorder = self.patch(api, 'register_persistent_error')
167@@ -4310,6 +4311,50 @@
168 response = self.report_images(nodegroup, [image], client=client)
169 self.assertEqual(httplib.OK, response.status_code)
170 self.assertEqual(0, recorder.call_count)
171+=======
172+ nodegroup = NodeGroup.objects.ensure_master()
173+ factory.make_node_group() # Second nodegroup with no images.
174+ recorder = self.patch(api, 'register_persistent_error')
175+ client = make_worker_client(nodegroup)
176+ response = self.report_images(nodegroup, [], client=client)
177+ self.assertEqual(
178+ (httplib.OK, "OK"),
179+ (response.status_code, response.content))
180+
181+ self.assertIn(
182+ COMPONENT.IMPORT_PXE_FILES,
183+ [args[0][0] for args in recorder.call_args_list])
184+ # Check that the persistent error message contains a link to the
185+ # clusters listing.
186+ self.assertIn(
187+ "/settings/#accepted-clusters", recorder.call_args_list[0][0][1])
188+
189+ def test_report_boot_images_warns_if_any_nodegroup_has_no_images(self):
190+ nodegroup = NodeGroup.objects.ensure_master()
191+ # Second nodegroup with no images.
192+ factory.make_node_group(status=NODEGROUP_STATUS.ACCEPTED)
193+ recorder = self.patch(api, 'register_persistent_error')
194+ client = make_worker_client(nodegroup)
195+ image = make_boot_image_params()
196+ response = self.report_images(nodegroup, [image], client=client)
197+ self.assertEqual(
198+ (httplib.OK, "OK"),
199+ (response.status_code, response.content))
200+
201+ self.assertIn(
202+ COMPONENT.IMPORT_PXE_FILES,
203+ [args[0][0] for args in recorder.call_args_list])
204+
205+ def test_report_boot_images_ignores_non_accepted_groups(self):
206+ nodegroup = factory.make_node_group(status=NODEGROUP_STATUS.ACCEPTED)
207+ factory.make_node_group(status=NODEGROUP_STATUS.PENDING)
208+ factory.make_node_group(status=NODEGROUP_STATUS.REJECTED)
209+ recorder = self.patch(api, 'register_persistent_error')
210+ client = make_worker_client(nodegroup)
211+ image = make_boot_image_params()
212+ response = self.report_images(nodegroup, [image], client=client)
213+ self.assertEqual(0, recorder.call_count)
214+>>>>>>> MERGE-SOURCE
215
216 def test_report_boot_images_removes_warning_if_images_found(self):
217 self.patch(api, 'register_persistent_error')
218
219=== modified file 'src/maasserver/tests/test_dhcplease.py'
220=== modified file 'src/maasserver/tests/test_forms.py'
221--- src/maasserver/tests/test_forms.py 2012-11-01 11:11:48 +0000
222+++ src/maasserver/tests/test_forms.py 2012-11-07 10:56:25 +0000
223@@ -350,36 +350,81 @@
224 after_commissioning_action, node.after_commissioning_action)
225 self.assertEqual(power_type, node.power_type)
226
227- def test_AdminNodeForm_refuses_to_update_hostname_on_allocated_node(self):
228- old_name = factory.make_name('old-hostname')
229- new_name = factory.make_name('new-hostname')
230- node = factory.make_node(
231- hostname=old_name, status=NODE_STATUS.ALLOCATED)
232- form = AdminNodeForm(
233- data={
234- 'hostname': new_name,
235- 'architecture': node.architecture,
236- },
237- instance=node)
238- self.assertFalse(form.is_valid())
239- self.assertEqual(
240- ["Can't change hostname to %s: node is in use." % new_name],
241- form._errors['hostname'])
242-
243- def test_AdminNodeForm_accepts_unchanged_hostname_on_allocated_node(self):
244- old_name = factory.make_name('old-hostname')
245- node = factory.make_node(
246- hostname=old_name, status=NODE_STATUS.ALLOCATED)
247- form = AdminNodeForm(
248- data={
249- 'hostname': old_name,
250- 'architecture': node.architecture,
251- },
252- instance=node)
253- self.assertTrue(form.is_valid(), form._errors)
254- form.save()
255- self.assertEqual(old_name, reload_object(node).hostname)
256-
257+<<<<<<< TREE
258+ def test_AdminNodeForm_refuses_to_update_hostname_on_allocated_node(self):
259+ old_name = factory.make_name('old-hostname')
260+ new_name = factory.make_name('new-hostname')
261+ node = factory.make_node(
262+ hostname=old_name, status=NODE_STATUS.ALLOCATED)
263+ form = AdminNodeForm(
264+ data={
265+ 'hostname': new_name,
266+ 'architecture': node.architecture,
267+ },
268+ instance=node)
269+ self.assertFalse(form.is_valid())
270+ self.assertEqual(
271+ ["Can't change hostname to %s: node is in use." % new_name],
272+ form._errors['hostname'])
273+
274+ def test_AdminNodeForm_accepts_unchanged_hostname_on_allocated_node(self):
275+ old_name = factory.make_name('old-hostname')
276+ node = factory.make_node(
277+ hostname=old_name, status=NODE_STATUS.ALLOCATED)
278+ form = AdminNodeForm(
279+ data={
280+ 'hostname': old_name,
281+ 'architecture': node.architecture,
282+ },
283+ instance=node)
284+ self.assertTrue(form.is_valid(), form._errors)
285+ form.save()
286+ self.assertEqual(old_name, reload_object(node).hostname)
287+
288+=======
289+ def test_AdminNodeForm_refuses_to_update_hostname_on_allocated_node(self):
290+ old_name = factory.make_name('old-hostname')
291+ new_name = factory.make_name('new-hostname')
292+ node = factory.make_node(
293+ hostname=old_name, status=NODE_STATUS.ALLOCATED)
294+ form = AdminNodeForm(
295+ data={
296+ 'hostname': new_name,
297+ 'architecture': node.architecture,
298+ },
299+ instance=node)
300+ self.assertFalse(form.is_valid())
301+ self.assertEqual(
302+ ["Can't change hostname to %s: node is in use." % new_name],
303+ form._errors['hostname'])
304+
305+ def test_AdminNodeForm_accepts_unchanged_hostname_on_allocated_node(self):
306+ old_name = factory.make_name('old-hostname')
307+ node = factory.make_node(
308+ hostname=old_name, status=NODE_STATUS.ALLOCATED)
309+ form = AdminNodeForm(
310+ data={
311+ 'hostname': old_name,
312+ 'architecture': node.architecture,
313+ },
314+ instance=node)
315+ self.assertTrue(form.is_valid(), form._errors)
316+ form.save()
317+ self.assertEqual(old_name, reload_object(node).hostname)
318+
319+ def test_AdminNodeForm_accepts_omitted_hostname_on_allocated_node(self):
320+ node = factory.make_node(status=NODE_STATUS.ALLOCATED)
321+ old_name = node.hostname
322+ form = AdminNodeForm(
323+ data={
324+ 'architecture': node.architecture,
325+ },
326+ instance=node)
327+ self.assertTrue(form.is_valid())
328+ form.save()
329+ self.assertEqual(old_name, reload_object(node).hostname)
330+
331+>>>>>>> MERGE-SOURCE
332 def test_remove_None_values_removes_None_values_in_dict(self):
333 random_input = factory.getRandomString()
334 self.assertEqual(
335
336=== modified file 'src/maasserver/tests/test_node.py'
337=== modified file 'src/maasserver/tests/test_views_nodes.py'
338--- src/maasserver/tests/test_views_nodes.py 2012-11-01 15:59:22 +0000
339+++ src/maasserver/tests/test_views_nodes.py 2012-11-07 10:56:25 +0000
340@@ -532,6 +532,7 @@
341 "//div[@id='nodes']/table//td/a/@href")
342 self.assertEqual([node2_link], node_links)
343
344+<<<<<<< TREE
345 def test_node_list_paginates(self):
346 """Node listing is split across multiple pages with links"""
347 # Set a very small page size to save creating lots of nodes
348@@ -591,6 +592,65 @@
349 [(a.text.lower(), a.get("href"))
350 for a in document.xpath("//div[@class='pagination']//a")])
351
352+=======
353+ def test_node_list_paginates(self):
354+ """Node listing is split across multiple pages with links"""
355+ # Set a very small page size to save creating lots of nodes
356+ page_size = 2
357+ self.patch(nodes_views.NodeListView, 'paginate_by', page_size)
358+ nodes = [factory.make_node(created="2012-10-12 12:00:%02d" % i)
359+ for i in range(page_size * 2 + 1)]
360+ # Order node links with newest first as the view is expected to
361+ node_links = [reverse('node-view', args=[node.system_id])
362+ for node in reversed(nodes)]
363+ expr_node_links = XPath("//div[@id='nodes']/table//a/@href")
364+ expr_page_anchors = XPath("//div[@class='pagination']//a")
365+ # Fetch first page, should link newest two nodes and page 2
366+ response = self.client.get(reverse('node-list'))
367+ page1 = fromstring(response.content)
368+ self.assertEqual(node_links[:page_size], expr_node_links(page1))
369+ self.assertEqual([("next", "?page=2"), ("last", "?page=3")],
370+ [(a.text.lower(), a.get("href"))
371+ for a in expr_page_anchors(page1)])
372+ # Fetch second page, should link next nodes and adjacent pages
373+ response = self.client.get(reverse('node-list'), {"page": 2})
374+ page2 = fromstring(response.content)
375+ self.assertEqual(node_links[page_size:page_size * 2],
376+ expr_node_links(page2))
377+ self.assertEqual([("first", "."), ("previous", "."),
378+ ("next", "?page=3"), ("last", "?page=3")],
379+ [(a.text.lower(), a.get("href"))
380+ for a in expr_page_anchors(page2)])
381+ # Fetch third page, should link oldest node and node list page
382+ response = self.client.get(reverse('node-list'), {"page": 3})
383+ page3 = fromstring(response.content)
384+ self.assertEqual(node_links[page_size * 2:], expr_node_links(page3))
385+ self.assertEqual([("first", "."), ("previous", "?page=2")],
386+ [(a.text.lower(), a.get("href"))
387+ for a in expr_page_anchors(page3)])
388+
389+ def test_node_list_query_paginates(self):
390+ """Node list query subset is split across multiple pages with links"""
391+ # Set a very small page size to save creating lots of nodes
392+ self.patch(nodes_views.NodeListView, 'paginate_by', 2)
393+ nodes = [factory.make_node(created="2012-10-12 12:00:%02d" % i)
394+ for i in range(10)]
395+ tag = factory.make_tag("odd")
396+ for node in nodes[::2]:
397+ node.tags = [tag]
398+ last_node_link = reverse('node-view', args=[nodes[0].system_id])
399+ response = self.client.get(reverse('node-list'),
400+ {"query": "maas-tags=odd", "page": 3})
401+ document = fromstring(response.content)
402+ self.assertIn("5 matching nodes", document.xpath("string(//h1)"))
403+ self.assertEqual([last_node_link],
404+ document.xpath("//div[@id='nodes']/table//a/@href"))
405+ self.assertEqual([("first", "?query=maas-tags%3Dodd"),
406+ ("previous", "?query=maas-tags%3Dodd&page=2")],
407+ [(a.text.lower(), a.get("href"))
408+ for a in document.xpath("//div[@class='pagination']//a")])
409+
410+>>>>>>> MERGE-SOURCE
411
412 class NodePreseedViewTest(LoggedInTestCase):
413
414
415=== modified file 'src/maasserver/views/nodes.py'
416--- src/maasserver/views/nodes.py 2012-10-24 15:57:18 +0000
417+++ src/maasserver/views/nodes.py 2012-11-07 10:56:25 +0000
418@@ -56,6 +56,7 @@
419 from maasserver.models import (
420 MACAddress,
421 Node,
422+ Tag,
423 )
424 from maasserver.models.node import CONSTRAINTS_JUJU_MAP
425 from maasserver.models.node_constraint_filter import constrain_nodes
426@@ -245,6 +246,13 @@
427 node.error if node.status == NODE_STATUS.FAILED_TESTS else None)
428 context['status_text'] = (
429 node.error if node.status != NODE_STATUS.FAILED_TESTS else None)
430+ kernel_opts = node.get_effective_kernel_options()
431+ context['kernel_opts'] = {
432+ 'is_global': kernel_opts[0] is None,
433+ 'is_tag': isinstance(kernel_opts[0], Tag),
434+ 'tag': kernel_opts[0],
435+ 'value': kernel_opts[1]
436+ }
437 return context
438
439 def dispatch(self, *args, **kwargs):
440
441=== modified file 'src/maasserver/views/settings.py'
442--- src/maasserver/views/settings.py 2012-10-03 15:48:11 +0000
443+++ src/maasserver/views/settings.py 2012-11-07 10:56:25 +0000
444@@ -46,6 +46,7 @@
445 MAASAndNetworkForm,
446 NewUserCreationForm,
447 UbuntuForm,
448+ GlobalKernelOptsForm,
449 )
450 from maasserver.models import (
451 NodeGroup,
452@@ -177,6 +178,13 @@
453 if response is not None:
454 return response
455
456+ # Process the Global Kernel Opts form.
457+ kernelopts_form, response = process_form(
458+ request, GlobalKernelOptsForm, reverse('settings'), 'kernelopts',
459+ "Configuration updated.")
460+ if response is not None:
461+ return response
462+
463 # Process accept clusters en masse.
464 if 'mass_accept_submit' in request.POST:
465 number = NodeGroup.objects.accept_all_pending()
466@@ -207,6 +215,7 @@
467 'maas_and_network_form': maas_and_network_form,
468 'commissioning_form': commissioning_form,
469 'ubuntu_form': ubuntu_form,
470+ 'kernelopts_form': kernelopts_form,
471 },
472 context_instance=RequestContext(request))
473
474
475=== modified file 'src/provisioningserver/boot_images.py'
476--- src/provisioningserver/boot_images.py 2012-10-25 07:27:24 +0000
477+++ src/provisioningserver/boot_images.py 2012-11-07 10:56:25 +0000
478@@ -32,10 +32,14 @@
479 )
480 from provisioningserver.config import Config
481 from provisioningserver.pxe import tftppath
482+<<<<<<< TREE
483 from provisioningserver.start_cluster_controller import get_cluster_uuid
484
485
486 task_logger = get_task_logger(name=__name__)
487+=======
488+from provisioningserver.start_cluster_controller import get_cluster_uuid
489+>>>>>>> MERGE-SOURCE
490
491
492 def get_cached_knowledge():
493
494=== modified file 'src/provisioningserver/dns/config.py'
495=== modified file 'src/provisioningserver/dns/tests/test_config.py'
496=== modified file 'src/provisioningserver/tasks.py'
497--- src/provisioningserver/tasks.py 2012-11-05 11:54:01 +0000
498+++ src/provisioningserver/tasks.py 2012-11-07 10:56:25 +0000
499@@ -369,6 +369,7 @@
500 exc=exc, countdown=UPDATE_NODE_TAGS_RETRY_DELAY)
501 else:
502 raise
503+<<<<<<< TREE
504
505
506 # =====================================================================
507@@ -382,3 +383,18 @@
508 env['http_proxy'] = http_proxy
509 env['https_proxy'] = http_proxy
510 check_call(['sudo', '-n', 'maas-import-pxe-files'], env=env)
511+=======
512+
513+
514+# =====================================================================
515+# Image importing-related tasks
516+# =====================================================================
517+
518+@task
519+def import_pxe_files(http_proxy=None):
520+ env = dict(os.environ)
521+ if http_proxy is not None:
522+ env['http_proxy'] = http_proxy
523+ env['https_proxy'] = http_proxy
524+ check_call(['maas-import-pxe-files'], env=env)
525+>>>>>>> MERGE-SOURCE
526
527=== modified file 'src/provisioningserver/tests/test_tasks.py'
528--- src/provisioningserver/tests/test_tasks.py 2012-11-05 11:54:01 +0000
529+++ src/provisioningserver/tests/test_tasks.py 2012-11-07 10:56:25 +0000
530@@ -536,6 +536,7 @@
531 self.assertRaises(
532 MissingCredentials, update_node_tags.delay, tag,
533 '//node', retry=True)
534+<<<<<<< TREE
535
536
537 class TestImportPxeFiles(PservTestCase):
538@@ -560,3 +561,28 @@
539 expected_env = dict(os.environ, http_proxy=proxy, https_proxy=proxy)
540 recorder.assert_called_once_with(
541 ['sudo', '-n', 'maas-import-pxe-files'], env=expected_env)
542+=======
543+
544+
545+class TestImportPxeFiles(PservTestCase):
546+
547+ def test_import_pxe_files(self):
548+ recorder = self.patch(tasks, 'check_call', Mock())
549+ import_pxe_files()
550+ recorder.assert_called_once_with(['maas-import-pxe-files'], env=ANY)
551+ self.assertIsInstance(import_pxe_files, Task)
552+
553+ def test_import_pxe_files_preserves_environment(self):
554+ recorder = self.patch(tasks, 'check_call', Mock())
555+ import_pxe_files()
556+ recorder.assert_called_once_with(
557+ ['maas-import-pxe-files'], env=os.environ)
558+
559+ def test_import_pxe_files_sets_proxy(self):
560+ recorder = self.patch(tasks, 'check_call', Mock())
561+ proxy = factory.getRandomString()
562+ import_pxe_files(http_proxy=proxy)
563+ expected_env = dict(os.environ, http_proxy=proxy, https_proxy=proxy)
564+ recorder.assert_called_once_with(
565+ ['maas-import-pxe-files'], env=expected_env)
566+>>>>>>> MERGE-SOURCE