Merge lp:~blake-rouse/maas/read-only-view-node-networking into lp:~maas-committers/maas/trunk

Proposed by Blake Rouse
Status: Merged
Approved by: Blake Rouse
Approved revision: no longer in the source branch.
Merged at revision: 4447
Proposed branch: lp:~blake-rouse/maas/read-only-view-node-networking
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 451 lines (+227/-13)
12 files modified
src/maasserver/api/interfaces.py (+5/-0)
src/maasserver/api/nodes.py (+1/-0)
src/maasserver/api/tests/test_interfaces.py (+11/-2)
src/maasserver/forms_interface.py (+6/-1)
src/maasserver/models/interface.py (+25/-1)
src/maasserver/models/tests/test_interface.py (+33/-1)
src/maasserver/static/js/angular/controllers/node_details_networking.js (+5/-0)
src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js (+14/-0)
src/maasserver/static/partials/node-details.html (+15/-5)
src/maasserver/tests/test_forms_interface.py (+55/-0)
src/maasserver/websockets/handlers/node.py (+16/-1)
src/maasserver/websockets/handlers/tests/test_node.py (+41/-2)
To merge this branch: bzr merge lp:~blake-rouse/maas/read-only-view-node-networking
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Approve
Review via email: mp+276315@code.launchpad.net

Commit message

Add the discovered field to the API and websocket for the interface. Use that field in the WebUI to show the current discovered IP address when the node is commissioning. Also update the WebUI to be all in one line once a node is out of edit mode.

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Here is an screenshot of what it looks like. This screenshot is a little bit older, as the subnet is also correct now in the branch.

http://imgur.com/f3ck2HN

Revision history for this message
Andres Rodriguez (andreserl) wrote :

lgtm!

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (1010.2 KiB)

The attempt to merge lp:~blake-rouse/maas/read-only-view-node-networking into lp:maas failed. Below is the output from the failed tests.

Hit http://security.ubuntu.com trusty-security InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Get:1 http://nova.clouds.archive.ubuntu.com trusty-updates InRelease [64.4 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Hit http://security.ubuntu.com trusty-security/main Sources
Hit http://security.ubuntu.com trusty-security/universe Sources
Hit http://security.ubuntu.com trusty-security/main amd64 Packages
Hit http://security.ubuntu.com trusty-security/universe amd64 Packages
Hit http://security.ubuntu.com trusty-security/main Translation-en
Get:2 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [242 kB]
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [143 kB]
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [639 kB]
Get:5 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [326 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Fetched 1,415 kB in 3s (376 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm pep8 phantomjs postgresql pyflakes python-apt python-bson python-bzrlib python-convoy python-coverage python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-jinja2 python-jsonschema python-lxml python-mock python-netaddr python-netifaces python-nose python-oauth python-openssl python-paramiko python-pexpect python-pip python-pocket-lint python-psycopg2 python-pyinotify python-pyparsing python-seamicroclient python-simplejson python-simplestreams python-sphinx python-subunit python-tempita python-testresources python-testscenarios python-testtools python-twisted python-txtftp pyt...

Revision history for this message
Andres Rodriguez (andreserl) wrote :

metadataserver.tests.test_api.TestCommissioningAPI.test_signal_power_type_lower_case_works ... ok
metadataserver.tests.test_api.TestCommissioningAPI.test_signal_power_type_stores_params ... FAIL
metadataserver.tests.test_api.TestCommissioningAPI.test_signal_refuses_bad_power_type ... ok

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (1010.0 KiB)

The attempt to merge lp:~blake-rouse/maas/read-only-view-node-networking into lp:maas failed. Below is the output from the failed tests.

Hit http://security.ubuntu.com trusty-security InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Get:1 http://nova.clouds.archive.ubuntu.com trusty-updates InRelease [64.4 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Hit http://security.ubuntu.com trusty-security/main Sources
Hit http://security.ubuntu.com trusty-security/universe Sources
Hit http://security.ubuntu.com trusty-security/main amd64 Packages
Hit http://security.ubuntu.com trusty-security/universe amd64 Packages
Get:2 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [242 kB]
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [143 kB]
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [639 kB]
Get:5 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [326 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Fetched 1,415 kB in 3s (373 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm pep8 phantomjs postgresql pyflakes python-apt python-bson python-bzrlib python-convoy python-coverage python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-jinja2 python-jsonschema python-lxml python-mock python-netaddr python-netifaces python-nose python-oauth python-openssl python-paramiko python-pexpect python-pip python-pocket-lint python-psycopg2 python-pyinotify python-pyparsing python-seamicroclient python-simplejson python-simplestreams python-sphinx python-subunit python-tempita python-testresources python-testscenarios python-testtools python-twisted python-txtftp pyt...

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (1010.0 KiB)

The attempt to merge lp:~blake-rouse/maas/read-only-view-node-networking into lp:maas failed. Below is the output from the failed tests.

Hit http://security.ubuntu.com trusty-security InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Get:1 http://nova.clouds.archive.ubuntu.com trusty-updates InRelease [64.4 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Hit http://security.ubuntu.com trusty-security/main Sources
Hit http://security.ubuntu.com trusty-security/universe Sources
Hit http://security.ubuntu.com trusty-security/main amd64 Packages
Hit http://security.ubuntu.com trusty-security/universe amd64 Packages
Get:2 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [242 kB]
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [143 kB]
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [639 kB]
Get:5 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [326 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Fetched 1,415 kB in 4s (323 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm pep8 phantomjs postgresql pyflakes python-apt python-bson python-bzrlib python-convoy python-coverage python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-jinja2 python-jsonschema python-lxml python-mock python-netaddr python-netifaces python-nose python-oauth python-openssl python-paramiko python-pexpect python-pip python-pocket-lint python-psycopg2 python-pyinotify python-pyparsing python-seamicroclient python-simplejson python-simplestreams python-sphinx python-subunit python-tempita python-testresources python-testscenarios python-testtools python-twisted python-txtftp pyt...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api/interfaces.py'
2--- src/maasserver/api/interfaces.py 2015-10-23 21:33:14 +0000
3+++ src/maasserver/api/interfaces.py 2015-10-31 23:55:40 +0000
4@@ -63,6 +63,7 @@
5 'enabled',
6 'links',
7 'params',
8+ 'discovered',
9 )
10
11
12@@ -279,6 +280,10 @@
13 def links(cls, interface):
14 return interface.get_links()
15
16+ @classmethod
17+ def discovered(cls, interface):
18+ return interface.get_discovered()
19+
20 def read(self, request, system_id, interface_id):
21 """Read interface on node.
22
23
24=== modified file 'src/maasserver/api/nodes.py'
25--- src/maasserver/api/nodes.py 2015-10-26 18:15:08 +0000
26+++ src/maasserver/api/nodes.py 2015-10-31 23:55:40 +0000
27@@ -132,6 +132,7 @@
28 'enabled',
29 'links',
30 'params',
31+ 'discovered',
32 )),
33 'routers',
34 'zone',
35
36=== modified file 'src/maasserver/api/tests/test_interfaces.py'
37--- src/maasserver/api/tests/test_interfaces.py 2015-10-23 21:57:59 +0000
38+++ src/maasserver/api/tests/test_interfaces.py 2015-10-31 23:55:40 +0000
39@@ -443,13 +443,19 @@
40 dhcp_ip = factory.make_StaticIPAddress(
41 alloc_type=IPADDRESS_TYPE.DHCP, ip="",
42 subnet=dhcp_subnet, interface=bond)
43+ discovered_ip = factory.pick_ip_in_network(
44+ dhcp_subnet.get_ipnetwork())
45+ factory.make_StaticIPAddress(
46+ alloc_type=IPADDRESS_TYPE.DISCOVERED, ip=discovered_ip,
47+ subnet=dhcp_subnet, interface=bond)
48 links.append(
49 MatchesDict({
50 "id": Equals(dhcp_ip.id),
51 "mode": Equals(INTERFACE_LINK_TYPE.DHCP),
52 "subnet": ContainsDict({
53 "id": Equals(dhcp_subnet.id)
54- })
55+ }),
56+ "ip_address": Equals(discovered_ip),
57 }))
58
59 # Second link is a STATIC ip link.
60@@ -506,7 +512,7 @@
61 "tags": Equals(bond.tags),
62 "resource_uri": Equals(get_node_interface_uri(bond)),
63 "params": Equals(bond.params),
64- }))
65+ }))
66 self.assertEquals(sorted(
67 nic.name
68 for nic in parents
69@@ -516,6 +522,9 @@
70 for nic in children
71 ), parsed_interface["children"])
72 self.assertThat(parsed_interface["links"], MatchesListwise(links))
73+ json_discovered = parsed_interface["discovered"][0]
74+ self.assertEquals(dhcp_subnet.id, json_discovered["subnet"]["id"])
75+ self.assertEquals(discovered_ip, json_discovered["ip_address"])
76
77 def test_read_404_when_invalid_id(self):
78 node = factory.make_Node()
79
80=== modified file 'src/maasserver/forms_interface.py'
81--- src/maasserver/forms_interface.py 2015-10-04 20:47:36 +0000
82+++ src/maasserver/forms_interface.py 2015-10-31 23:55:40 +0000
83@@ -346,7 +346,12 @@
84 ]
85 for bond_field in bond_fields:
86 value = self.cleaned_data.get(bond_field)
87- if value:
88+ if (value is not None and
89+ isinstance(value, (bytes, unicode)) and
90+ len(value) > 0 and not value.isspace()):
91+ interface.params[bond_field] = value
92+ elif (value is not None and
93+ not isinstance(value, (bytes, unicode))):
94 interface.params[bond_field] = value
95 elif created:
96 interface.params[bond_field] = self.fields[bond_field].initial
97
98=== modified file 'src/maasserver/models/interface.py'
99--- src/maasserver/models/interface.py 2015-10-27 17:45:01 +0000
100+++ src/maasserver/models/interface.py 2015-10-31 23:55:40 +0000
101@@ -253,7 +253,7 @@
102 {
103 "id": 1,
104 "mode": "dhcp",
105- "ip": "192.168.1.2",
106+ "ip_address": "192.168.1.2",
107 "subnet": <Subnet object>
108 }
109
110@@ -276,6 +276,30 @@
111 links.append(link)
112 return links
113
114+ def get_discovered(self):
115+ """Return the definition of discovered IP addresses belonging to this
116+ interface.
117+
118+ Example definition:
119+ {
120+ "ip_address": "192.168.1.2",
121+ "subnet": <Subnet object>
122+ }
123+ """
124+ discovered_ips = self.ip_addresses.filter(
125+ alloc_type=IPADDRESS_TYPE.DISCOVERED)
126+ if len(discovered_ips) > 0:
127+ discovered = []
128+ for discovered_ip in discovered_ips:
129+ if discovered_ip.ip is not None and discovered_ip.ip != "":
130+ discovered.append({
131+ "subnet": discovered_ip.subnet,
132+ "ip_address": "%s" % discovered_ip.ip,
133+ })
134+ return discovered
135+ else:
136+ return None
137+
138 def only_has_link_up(self):
139 """Return True if this interface is only set to LINK_UP."""
140 ip_addresses = self.ip_addresses.exclude(
141
142=== modified file 'src/maasserver/models/tests/test_interface.py'
143--- src/maasserver/models/tests/test_interface.py 2015-10-30 16:14:33 +0000
144+++ src/maasserver/models/tests/test_interface.py 2015-10-31 23:55:40 +0000
145@@ -222,6 +222,38 @@
146 }))
147 self.assertThat(interface.get_links(), MatchesListwise(links))
148
149+ def test_get_discovered_returns_None_when_empty(self):
150+ interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
151+ self.assertIsNone(interface.get_discovered())
152+
153+ def test_get_discovered_returns_discovered_address_for_ipv4_and_ipv6(self):
154+ interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
155+ discovered_ips = []
156+ network_v4 = factory.make_ipv4_network()
157+ subnet_v4 = factory.make_Subnet(cidr=unicode(network_v4.cidr))
158+ ip_v4 = factory.pick_ip_in_network(network_v4)
159+ factory.make_StaticIPAddress(
160+ alloc_type=IPADDRESS_TYPE.DISCOVERED, ip=ip_v4,
161+ subnet=subnet_v4, interface=interface)
162+ discovered_ips.append(
163+ MatchesDict({
164+ "ip_address": Equals(ip_v4),
165+ "subnet": Equals(subnet_v4),
166+ }))
167+ network_v6 = factory.make_ipv6_network()
168+ subnet_v6 = factory.make_Subnet(cidr=unicode(network_v6.cidr))
169+ ip_v6 = factory.pick_ip_in_network(network_v6)
170+ factory.make_StaticIPAddress(
171+ alloc_type=IPADDRESS_TYPE.DISCOVERED, ip=ip_v6,
172+ subnet=subnet_v6, interface=interface)
173+ discovered_ips.append(
174+ MatchesDict({
175+ "ip_address": Equals(ip_v6),
176+ "subnet": Equals(subnet_v6),
177+ }))
178+ self.assertThat(
179+ interface.get_discovered(), MatchesListwise(discovered_ips))
180+
181 def test_delete_deletes_related_ip_addresses(self):
182 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
183 discovered_ip = factory.make_StaticIPAddress(
184@@ -806,7 +838,7 @@
185 "Unknown interfaces should have been deleted.")
186 self.assertEqual(num_connections, interface.ip_addresses.count())
187 for i in range(num_connections):
188- ip = interface.ip_addresses.all()[i]
189+ ip = interface.ip_addresses.order_by('id')[i]
190 self.assertThat(ip, MatchesStructure.byEquality(
191 alloc_type=IPADDRESS_TYPE.DISCOVERED, subnet=subnet_list[i],
192 ip=unicode(IPNetwork(cidr_list[i]).ip)))
193
194=== modified file 'src/maasserver/static/js/angular/controllers/node_details_networking.js'
195--- src/maasserver/static/js/angular/controllers/node_details_networking.js 2015-10-30 16:35:25 +0000
196+++ src/maasserver/static/js/angular/controllers/node_details_networking.js 2015-10-31 23:55:40 +0000
197@@ -557,6 +557,11 @@
198 }
199 };
200
201+ // Get the subnet from its ID.
202+ $scope.getSubnet = function(subnetId) {
203+ return SubnetsManager.getItemFromList(subnetId);
204+ };
205+
206 // Toggle showing or hiding the members of the interface.
207 $scope.toggleMembers = function(nic) {
208 var idx = $scope.showingMembers.indexOf(nic.id);
209
210=== modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js'
211--- src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js 2015-10-30 16:35:25 +0000
212+++ src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js 2015-10-31 23:55:40 +0000
213@@ -1035,6 +1035,20 @@
214 });
215 });
216
217+ describe("getSubnet", function() {
218+
219+ it("calls SubnetsManager.getItemFromList", function() {
220+ var controller = makeController();
221+ var subnetId = makeInteger(0, 100);
222+ var subnet = {};
223+ spyOn(SubnetsManager, "getItemFromList").and.returnValue(subnet);
224+
225+ expect($scope.getSubnet(subnetId)).toBe(subnet);
226+ expect(SubnetsManager.getItemFromList).toHaveBeenCalledWith(
227+ subnetId);
228+ });
229+ });
230+
231 describe("toggleMembers", function() {
232
233 it("adds interface id to showingMembers", function() {
234
235=== modified file 'src/maasserver/static/partials/node-details.html'
236--- src/maasserver/static/partials/node-details.html 2015-10-30 16:43:21 +0000
237+++ src/maasserver/static/partials/node-details.html 2015-10-31 23:55:40 +0000
238@@ -415,14 +415,18 @@
239 </div>
240 <div class="table__data table__column--18">
241 <select class="table__input" name="subnet" id="subnet"
242+ data-ng-hide="isAllNetworkingDisabled() && interface.discovered[0].subnet_id"
243 data-ng-model="interface.subnet"
244 data-ng-change="subnetChanged(interface)"
245 data-ng-options="subnet as getSubnetText(subnet) for subnet in subnets | filterByVLAN:interface.vlan">
246 <option value="" data-ng-hide="interface.links.length > 1">Unconfigured</option>
247 </select>
248+ <span class="ng-hide" data-ng-show="isAllNetworkingDisabled() && interface.discovered[0].subnet_id">
249+ {$ getSubnetText(getSubnet(interface.discovered[0].subnet_id)) $}
250+ </span>
251 </div>
252- <div class="table__data table__column--14">
253- <ul class="no-bullets">
254+ <div class="table__data table__column--21">
255+ <ul class="no-bullets" data-ng-hide="isAllNetworkingDisabled()">
256 <li>
257 <select class="table__input" name="link-mode" id="link-mode"
258 data-ng-model="interface.mode"
259@@ -441,8 +445,14 @@
260 data-ng-disabled="interface.mode != 'static'">
261 </li>
262 </ul>
263+ <span class="ng-hide" data-ng-show="isAllNetworkingDisabled() && !interface.discovered[0].ip_address">
264+ {$ interface.ip_address $} ({$ getLinkModeText(interface) $})
265+ </span>
266+ <span class="ng-hide" data-ng-show="isAllNetworkingDisabled() && interface.discovered[0].ip_address">
267+ {$ interface.discovered[0].ip_address $} (DHCP)
268+ </span>
269 </div>
270- <div class="table__data table__column--13">
271+ <div class="table__data table__column--6">
272 <div class="table__controls align-right">
273 <a class="icon add"
274 data-ng-click="quickAdd(interface)"
275@@ -500,14 +510,14 @@
276 <option value="" data-ng-hide="newInterface.type === 'alias'">Unconfigured</option>
277 </select>
278 </div>
279- <div class="table__data table__column--14">
280+ <div class="table__data table__column--21">
281 <select class="table__input" name="link-mode" id="link-mode"
282 data-ng-model="newInterface.mode"
283 data-ng-disabled="isLinkModeDisabled(newInterface)"
284 data-ng-options="mode.mode as mode.text for mode in modes | filterLinkModes:newInterface">
285 </select>
286 </div>
287- <div class="table__data table_column--13"></div>
288+ <div class="table__data table_column--6"></div>
289 </div>
290 <div class="table__row table__dropdown-row">
291 <div class="ng-hide" data-ng-show="isShowingInterfaceOptions()">
292
293=== modified file 'src/maasserver/tests/test_forms_interface.py'
294--- src/maasserver/tests/test_forms_interface.py 2015-09-16 20:14:09 +0000
295+++ src/maasserver/tests/test_forms_interface.py 2015-10-31 23:55:40 +0000
296@@ -806,3 +806,58 @@
297 "bond_lacp_rate": new_bond_lacp_rate,
298 "bond_xmit_hash_policy": new_bond_xmit_hash_policy,
299 }, interface.params)
300+
301+ def test__edit_allows_zero_params(self):
302+ parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
303+ parent2 = factory.make_Interface(
304+ INTERFACE_TYPE.PHYSICAL, node=parent1.node)
305+ interface = factory.make_Interface(
306+ INTERFACE_TYPE.BOND,
307+ parents=[parent1, parent2])
308+ bond_mode = factory.pick_choice(BOND_MODE_CHOICES)
309+ bond_miimon = random.randint(0, 1000)
310+ bond_downdelay = random.randint(0, 1000)
311+ bond_updelay = random.randint(0, 1000)
312+ bond_lacp_rate = factory.pick_choice(BOND_LACP_RATE_CHOICES)
313+ bond_xmit_hash_policy = factory.pick_choice(
314+ BOND_XMIT_HASH_POLICY_CHOICES)
315+ interface.params = {
316+ "bond_mode": bond_mode,
317+ "bond_miimon": bond_miimon,
318+ "bond_downdelay": bond_downdelay,
319+ "bond_updelay": bond_updelay,
320+ "bond_lacp_rate": bond_lacp_rate,
321+ "bond_xmit_hash_policy": bond_xmit_hash_policy,
322+ }
323+ interface.save()
324+ new_vlan = factory.make_VLAN(vid=33)
325+ new_name = factory.make_name()
326+ new_bond_mode = factory.pick_choice(BOND_MODE_CHOICES)
327+ new_bond_miimon = 0
328+ new_bond_downdelay = 0
329+ new_bond_updelay = 0
330+ new_bond_lacp_rate = factory.pick_choice(BOND_LACP_RATE_CHOICES)
331+ new_bond_xmit_hash_policy = factory.pick_choice(
332+ BOND_XMIT_HASH_POLICY_CHOICES)
333+ form = BondInterfaceForm(
334+ instance=interface,
335+ data={
336+ 'vlan': new_vlan.id,
337+ 'name': new_name,
338+ 'bond_mode': new_bond_mode,
339+ 'bond_miimon': new_bond_miimon,
340+ 'bond_downdelay': new_bond_downdelay,
341+ 'bond_updelay': new_bond_updelay,
342+ 'bond_lacp_rate': new_bond_lacp_rate,
343+ 'bond_xmit_hash_policy': new_bond_xmit_hash_policy,
344+ })
345+ self.assertTrue(form.is_valid(), form.errors)
346+ interface = form.save()
347+ self.assertEquals({
348+ "bond_mode": new_bond_mode,
349+ "bond_miimon": new_bond_miimon,
350+ "bond_downdelay": new_bond_downdelay,
351+ "bond_updelay": new_bond_updelay,
352+ "bond_lacp_rate": new_bond_lacp_rate,
353+ "bond_xmit_hash_policy": new_bond_xmit_hash_policy,
354+ }, interface.params)
355
356=== modified file 'src/maasserver/websockets/handlers/node.py'
357--- src/maasserver/websockets/handlers/node.py 2015-10-29 20:10:11 +0000
358+++ src/maasserver/websockets/handlers/node.py 2015-10-31 23:55:40 +0000
359@@ -498,7 +498,7 @@
360 subnet = link.pop("subnet", None)
361 if subnet is not None:
362 link["subnet_id"] = subnet.id
363- return {
364+ data = {
365 "id": interface.id,
366 "type": interface.type,
367 "name": interface.get_name(),
368@@ -517,6 +517,21 @@
369 "links": links,
370 }
371
372+ # When the node is commissioning display the discovered IP address for
373+ # this interface. This will only be shown on interfaces that are
374+ # connected to a MAAS managed subnet.
375+ if obj.status == NODE_STATUS.COMMISSIONING:
376+ discovereds = interface.get_discovered()
377+ if discovereds is not None:
378+ for discovered in discovereds:
379+ # Replace the subnet object with the subnet_id. The client
380+ # will use this information to pull the subnet information
381+ # from the websocket.
382+ discovered["subnet_id"] = discovered.pop("subnet").id
383+ data["discovered"] = discovereds
384+
385+ return data
386+
387 def dehydrate_summary_output(self, obj, data):
388 """Dehydrate the machine summary output."""
389 # Produce a "clean" composite details document.
390
391=== modified file 'src/maasserver/websockets/handlers/tests/test_node.py'
392--- src/maasserver/websockets/handlers/tests/test_node.py 2015-10-29 20:10:11 +0000
393+++ src/maasserver/websockets/handlers/tests/test_node.py 2015-10-31 23:55:40 +0000
394@@ -557,9 +557,9 @@
395 filesystem.fstype in FILESYSTEM_FORMAT_TYPE_CHOICES_DICT),
396 }, handler.dehydrate_filesystem(filesystem))
397
398- def test_dehydrate_interface(self):
399+ def test_dehydrate_interface_for_ready_node(self):
400 owner = factory.make_User()
401- node = factory.make_Node(owner=owner)
402+ node = factory.make_Node(owner=owner, status=NODE_STATUS.READY)
403 handler = NodeHandler(owner, {})
404 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)
405 factory.make_StaticIPAddress(
406@@ -587,6 +587,45 @@
407 "links": expected_links,
408 }, handler.dehydrate_interface(interface, node))
409
410+ def test_dehydrate_interface_for_commissioning_node(self):
411+ owner = factory.make_User()
412+ node = factory.make_Node(owner=owner, status=NODE_STATUS.COMMISSIONING)
413+ handler = NodeHandler(owner, {})
414+ interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)
415+ factory.make_StaticIPAddress(
416+ alloc_type=IPADDRESS_TYPE.AUTO, ip="",
417+ subnet=factory.make_Subnet(), interface=interface)
418+ expected_links = interface.get_links()
419+ for link in expected_links:
420+ link["subnet_id"] = link.pop("subnet").id
421+ discovered_subnet = factory.make_Subnet()
422+ factory.make_StaticIPAddress(
423+ alloc_type=IPADDRESS_TYPE.DISCOVERED,
424+ ip=factory.pick_ip_in_network(discovered_subnet.get_ipnetwork()),
425+ subnet=discovered_subnet, interface=interface)
426+ expected_discovered = interface.get_discovered()
427+ for discovered in expected_discovered:
428+ discovered["subnet_id"] = discovered.pop("subnet").id
429+ self.assertEquals({
430+ "id": interface.id,
431+ "type": interface.type,
432+ "name": interface.get_name(),
433+ "enabled": interface.is_enabled(),
434+ "is_boot": interface == node.boot_interface,
435+ "mac_address": "%s" % interface.mac_address,
436+ "vlan_id": interface.vlan_id,
437+ "parents": [
438+ nic.id
439+ for nic in interface.parents.all()
440+ ],
441+ "children": [
442+ nic.child.id
443+ for nic in interface.children_relationships.all()
444+ ],
445+ "links": expected_links,
446+ "discovered": expected_discovered,
447+ }, handler.dehydrate_interface(interface, node))
448+
449 def test_dehydrate_summary_output_returns_None(self):
450 owner = factory.make_User()
451 node = factory.make_Node(owner=owner)