Merge lp:~blake-rouse/maas/vlan-relay into lp:~maas-committers/maas/trunk
- vlan-relay
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Blake Rouse |
Approved revision: | no longer in the source branch. |
Merged at revision: | 5585 |
Proposed branch: | lp:~blake-rouse/maas/vlan-relay |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
1628 lines (+770/-208) 22 files modified
src/maasserver/api/tests/test_vlans.py (+40/-0) src/maasserver/api/vlans.py (+8/-1) src/maasserver/dhcp.py (+41/-61) src/maasserver/exceptions.py (+0/-4) src/maasserver/forms_vlan.py (+25/-0) src/maasserver/migrations/builtin/maasserver/0056_add_description_to_fabric_and_space.py (+1/-1) src/maasserver/migrations/builtin/maasserver/0095_vlan_relay_vlan.py (+23/-0) src/maasserver/migrations/builtin/maasserver/0096_set_default_vlan_field.py (+24/-0) src/maasserver/models/tests/test_vlan.py (+8/-0) src/maasserver/models/vlan.py (+5/-0) src/maasserver/static/js/angular/controllers/tests/test_vlan_details.js (+56/-3) src/maasserver/static/js/angular/controllers/vlan_details.js (+126/-18) src/maasserver/static/js/angular/factories/tests/test_vlans.js (+6/-3) src/maasserver/static/js/angular/factories/vlans.js (+12/-7) src/maasserver/static/partials/vlan-details.html (+97/-22) src/maasserver/testing/factory.py (+3/-2) src/maasserver/tests/test_dhcp.py (+27/-80) src/maasserver/tests/test_forms_vlan.py (+70/-0) src/maasserver/triggers/system.py (+56/-0) src/maasserver/triggers/tests/test_system_listener.py (+114/-0) src/maasserver/websockets/handlers/tests/test_vlan.py (+15/-0) src/maasserver/websockets/handlers/vlan.py (+13/-6) |
To merge this branch: | bzr merge lp:~blake-rouse/maas/vlan-relay |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Mike Pontillo (community) | Approve | ||
Review via email: mp+312165@code.launchpad.net |
Commit message
Support the ability for a VLAN to act as a relay for another VLAN.
Description of the change
Blake Rouse (blake-rouse) wrote : | # |
Mike Pontillo (mpontillo) wrote : | # |
Looks good; I have a few questions and comments below before this lands.
Don't we also need a validation to ensure you cannot enable DHCP on a VLAN with a relay?
Mike Pontillo (mpontillo) wrote : | # |
Also, can you link bug #1602412 to this branch with --fixes?
Blake Rouse (blake-rouse) wrote : | # |
> Looks good; I have a few questions and comments below before this lands.
>
> Don't we also need a validation to ensure you cannot enable DHCP on a VLAN
> with a relay?
I did not want to block this. So if they enable DHCP it clears the relay_vlan and shows a warning in the UI when they go to enable.
Blake Rouse (blake-rouse) : | # |
Mike Pontillo (mpontillo) : | # |
Blake Rouse (blake-rouse) wrote : | # |
I am not making that change in my branch. That is not even related to the
change I am making. File a bug if its causing an issue.
On Wed, Nov 30, 2016 at 4:14 PM, Mike Pontillo <email address hidden>
wrote:
>
>
> Diff comments:
>
> >
> > === added file 'src/maasserver
> default_
> > --- src/maasserver/
> 1970-01-01 00:00:00 +0000
> > +++ src/maasserver/
> 2016-11-30 16:44:24 +0000
> > @@ -0,0 +1,24 @@
> > +# -*- coding: utf-8 -*-
> > +from __future__ import unicode_literals
> > +
> > +from django.db import (
> > + migrations,
> > + models,
> > +)
> > +import django.
> > +import maasserver.
> > +
> > +
> > +class Migration(
> > +
> > + dependencies = [
> > + ('maasserver', '0094_vlan_
> > + ]
> > +
> > + operations = [
> > + migrations.
> > + model_name=
> > + name='vlan',
> > + field=models.
> default=
> on_delete=
>
> Ah, my mistake. But I think there might still be an issue here, just not
> exactly where I pointed it out.
>
> According to the Django docs, CASCADE is the default when no `on_delete`
> option is set. Wouldn't that cause severe unintended consequences in the
> case of the `relay_vlan` field? I think we should use `on_delete=
> on the `relay_vlan` field.
>
> > + ),
> > + ]
>
>
> --
> https:/
> You are the owner of lp:~blake-rouse/maas/vlan-relay.
>
Mike Pontillo (mpontillo) : | # |
Blake Rouse (blake-rouse) wrote : | # |
Ah thanks for clarifying. I have fixed the issue and included a test to check.
Mike Pontillo (mpontillo) wrote : | # |
Thanks for the fixes. I found one other issue below; please at least fix the text. I'll leave it up to you what action to take in that scenario.
Preview Diff
1 | === modified file 'src/maasserver/api/tests/test_vlans.py' | |||
2 | --- src/maasserver/api/tests/test_vlans.py 2016-05-24 21:29:53 +0000 | |||
3 | +++ src/maasserver/api/tests/test_vlans.py 2016-12-06 08:04:41 +0000 | |||
4 | @@ -84,6 +84,29 @@ | |||
5 | 84 | self.assertEqual(vid, response_data['vid']) | 84 | self.assertEqual(vid, response_data['vid']) |
6 | 85 | self.assertEqual(mtu, response_data['mtu']) | 85 | self.assertEqual(mtu, response_data['mtu']) |
7 | 86 | 86 | ||
8 | 87 | def test_create_with_relay_vlan(self): | ||
9 | 88 | self.become_admin() | ||
10 | 89 | fabric = factory.make_Fabric() | ||
11 | 90 | vlan_name = factory.make_name("fabric") | ||
12 | 91 | vid = random.randint(1, 1000) | ||
13 | 92 | mtu = random.randint(552, 1500) | ||
14 | 93 | relay_vlan = factory.make_VLAN() | ||
15 | 94 | uri = get_vlans_uri(fabric) | ||
16 | 95 | response = self.client.post(uri, { | ||
17 | 96 | "name": vlan_name, | ||
18 | 97 | "vid": vid, | ||
19 | 98 | "mtu": mtu, | ||
20 | 99 | "relay_vlan": relay_vlan.id, | ||
21 | 100 | }) | ||
22 | 101 | self.assertEqual( | ||
23 | 102 | http.client.OK, response.status_code, response.content) | ||
24 | 103 | response_data = json.loads( | ||
25 | 104 | response.content.decode(settings.DEFAULT_CHARSET)) | ||
26 | 105 | self.assertEqual(vlan_name, response_data['name']) | ||
27 | 106 | self.assertEqual(vid, response_data['vid']) | ||
28 | 107 | self.assertEqual(mtu, response_data['mtu']) | ||
29 | 108 | self.assertEqual(relay_vlan.vid, response_data['relay_vlan']['vid']) | ||
30 | 109 | |||
31 | 87 | def test_create_admin_only(self): | 110 | def test_create_admin_only(self): |
32 | 88 | fabric = factory.make_Fabric() | 111 | fabric = factory.make_Fabric() |
33 | 89 | vlan_name = factory.make_name("fabric") | 112 | vlan_name = factory.make_name("fabric") |
34 | @@ -182,6 +205,23 @@ | |||
35 | 182 | self.assertEqual(new_vid, parsed_vlan['vid']) | 205 | self.assertEqual(new_vid, parsed_vlan['vid']) |
36 | 183 | self.assertEqual(new_vid, vlan.vid) | 206 | self.assertEqual(new_vid, vlan.vid) |
37 | 184 | 207 | ||
38 | 208 | def test_update_sets_relay_vlan(self): | ||
39 | 209 | self.become_admin() | ||
40 | 210 | fabric = factory.make_Fabric() | ||
41 | 211 | vlan = factory.make_VLAN(fabric=fabric) | ||
42 | 212 | uri = get_vlan_uri(vlan) | ||
43 | 213 | relay_vlan = factory.make_VLAN() | ||
44 | 214 | response = self.client.put(uri, { | ||
45 | 215 | "relay_vlan": relay_vlan.id, | ||
46 | 216 | }) | ||
47 | 217 | self.assertEqual( | ||
48 | 218 | http.client.OK, response.status_code, response.content) | ||
49 | 219 | parsed_vlan = json.loads( | ||
50 | 220 | response.content.decode(settings.DEFAULT_CHARSET)) | ||
51 | 221 | vlan = reload_object(vlan) | ||
52 | 222 | self.assertEqual(relay_vlan.vid, parsed_vlan['relay_vlan']['vid']) | ||
53 | 223 | self.assertEqual(relay_vlan, vlan.relay_vlan) | ||
54 | 224 | |||
55 | 185 | def test_update_with_fabric(self): | 225 | def test_update_with_fabric(self): |
56 | 186 | self.become_admin() | 226 | self.become_admin() |
57 | 187 | fabric = factory.make_Fabric() | 227 | fabric = factory.make_Fabric() |
58 | 188 | 228 | ||
59 | === modified file 'src/maasserver/api/vlans.py' | |||
60 | --- src/maasserver/api/vlans.py 2016-04-27 20:40:24 +0000 | |||
61 | +++ src/maasserver/api/vlans.py 2016-12-06 08:04:41 +0000 | |||
62 | @@ -26,6 +26,7 @@ | |||
63 | 26 | 'secondary_rack', | 26 | 'secondary_rack', |
64 | 27 | 'dhcp_on', | 27 | 'dhcp_on', |
65 | 28 | 'external_dhcp', | 28 | 'external_dhcp', |
66 | 29 | 'relay_vlan', | ||
67 | 29 | ) | 30 | ) |
68 | 30 | 31 | ||
69 | 31 | 32 | ||
70 | @@ -165,12 +166,18 @@ | |||
71 | 165 | :type vid: integer | 166 | :type vid: integer |
72 | 166 | :param mtu: The MTU to use on the VLAN. | 167 | :param mtu: The MTU to use on the VLAN. |
73 | 167 | :type mtu: integer | 168 | :type mtu: integer |
75 | 168 | :Param dhcp_on: Whether or not DHCP should be managed on the VLAN. | 169 | :param dhcp_on: Whether or not DHCP should be managed on the VLAN. |
76 | 169 | :type dhcp_on: boolean | 170 | :type dhcp_on: boolean |
77 | 170 | :param primary_rack: The primary rack controller managing the VLAN. | 171 | :param primary_rack: The primary rack controller managing the VLAN. |
78 | 171 | :type primary_rack: system_id | 172 | :type primary_rack: system_id |
79 | 172 | :param secondary_rack: The secondary rack controller manging the VLAN. | 173 | :param secondary_rack: The secondary rack controller manging the VLAN. |
80 | 173 | :type secondary_rack: system_id | 174 | :type secondary_rack: system_id |
81 | 175 | :param relay_vlan: Only set when this VLAN will be using a DHCP relay | ||
82 | 176 | to forward DHCP requests to another VLAN that MAAS is or will run | ||
83 | 177 | the DHCP server. MAAS will not run the DHCP relay itself, it must | ||
84 | 178 | be configured to proxy reqests to the primary and/or secondary | ||
85 | 179 | rack controller interfaces for the VLAN specified in this field. | ||
86 | 180 | :type relay_vlan: ID of VLAN | ||
87 | 174 | 181 | ||
88 | 175 | Returns 404 if the fabric or VLAN is not found. | 182 | Returns 404 if the fabric or VLAN is not found. |
89 | 176 | """ | 183 | """ |
90 | 177 | 184 | ||
91 | === modified file 'src/maasserver/dhcp.py' | |||
92 | --- src/maasserver/dhcp.py 2016-12-03 16:33:44 +0000 | |||
93 | +++ src/maasserver/dhcp.py 2016-12-06 08:04:41 +0000 | |||
94 | @@ -29,10 +29,7 @@ | |||
95 | 29 | IPRANGE_TYPE, | 29 | IPRANGE_TYPE, |
96 | 30 | SERVICE_STATUS, | 30 | SERVICE_STATUS, |
97 | 31 | ) | 31 | ) |
102 | 32 | from maasserver.exceptions import ( | 32 | from maasserver.exceptions import UnresolvableHost |
99 | 33 | DHCPConfigurationError, | ||
100 | 34 | UnresolvableHost, | ||
101 | 35 | ) | ||
103 | 36 | from maasserver.models import ( | 33 | from maasserver.models import ( |
104 | 37 | Config, | 34 | Config, |
105 | 38 | DHCPSnippet, | 35 | DHCPSnippet, |
106 | @@ -194,18 +191,19 @@ | |||
107 | 194 | return [] | 191 | return [] |
108 | 195 | 192 | ||
109 | 196 | 193 | ||
112 | 197 | def get_managed_vlans_for(rack_controller): | 194 | def gen_managed_vlans_for(rack_controller): |
113 | 198 | """Return list of `VLAN` for the `rack_controller` when DHCP is enabled and | 195 | """Yeilds each `VLAN` for the `rack_controller` when DHCP is enabled and |
114 | 199 | `rack_controller` is either the `primary_rack` or the `secondary_rack`. | 196 | `rack_controller` is either the `primary_rack` or the `secondary_rack`. |
115 | 200 | """ | 197 | """ |
116 | 201 | interfaces = rack_controller.interface_set.filter( | 198 | interfaces = rack_controller.interface_set.filter( |
117 | 202 | Q(vlan__dhcp_on=True) & ( | 199 | Q(vlan__dhcp_on=True) & ( |
118 | 203 | Q(vlan__primary_rack=rack_controller) | | 200 | Q(vlan__primary_rack=rack_controller) | |
124 | 204 | Q(vlan__secondary_rack=rack_controller))).select_related("vlan") | 201 | Q(vlan__secondary_rack=rack_controller))) |
125 | 205 | return { | 202 | interfaces = interfaces.prefetch_related("vlan__relay_vlans") |
126 | 206 | interface.vlan | 203 | for interface in interfaces: |
127 | 207 | for interface in interfaces | 204 | yield interface.vlan |
128 | 208 | } | 205 | for relayed_vlan in interface.vlan.relay_vlans.all(): |
129 | 206 | yield relayed_vlan | ||
130 | 209 | 207 | ||
131 | 210 | 208 | ||
132 | 211 | def ip_is_on_vlan(ip_address, vlan): | 209 | def ip_is_on_vlan(ip_address, vlan): |
133 | @@ -460,12 +458,6 @@ | |||
134 | 460 | interfaces = get_interfaces_with_ip_on_vlan( | 458 | interfaces = get_interfaces_with_ip_on_vlan( |
135 | 461 | rack_controller, vlan, ip_version) | 459 | rack_controller, vlan, ip_version) |
136 | 462 | interface = get_best_interface(interfaces) | 460 | interface = get_best_interface(interfaces) |
137 | 463 | if interface is None: | ||
138 | 464 | raise DHCPConfigurationError( | ||
139 | 465 | "No IPv%d interface on rack controller '%s' has an IP address on " | ||
140 | 466 | "any subnet on VLAN '%s.%d'." % ( | ||
141 | 467 | ip_version, rack_controller.hostname, vlan.fabric.name, | ||
142 | 468 | vlan.vid)) | ||
143 | 469 | 461 | ||
144 | 470 | # Generate the failover peer for this VLAN. | 462 | # Generate the failover peer for this VLAN. |
145 | 471 | if vlan.secondary_rack_id is not None: | 463 | if vlan.secondary_rack_id is not None: |
146 | @@ -497,7 +489,7 @@ | |||
147 | 497 | hosts = make_hosts_for_subnets(subnets, nodes_dhcp_snippets) | 489 | hosts = make_hosts_for_subnets(subnets, nodes_dhcp_snippets) |
148 | 498 | return ( | 490 | return ( |
149 | 499 | peer_config, sorted(subnet_configs, key=itemgetter("subnet")), | 491 | peer_config, sorted(subnet_configs, key=itemgetter("subnet")), |
151 | 500 | hosts, interface.name) | 492 | hosts, None if interface is None else interface.name) |
152 | 501 | 493 | ||
153 | 502 | 494 | ||
154 | 503 | @synchronous | 495 | @synchronous |
155 | @@ -506,7 +498,7 @@ | |||
156 | 506 | """Return tuple with IPv4 and IPv6 configurations for the | 498 | """Return tuple with IPv4 and IPv6 configurations for the |
157 | 507 | rack controller.""" | 499 | rack controller.""" |
158 | 508 | # Get list of all vlans that are being managed by the rack controller. | 500 | # Get list of all vlans that are being managed by the rack controller. |
160 | 509 | vlans = get_managed_vlans_for(rack_controller) | 501 | vlans = gen_managed_vlans_for(rack_controller) |
161 | 510 | 502 | ||
162 | 511 | # Group the subnets on each VLAN into IPv4 and IPv6 subnets. | 503 | # Group the subnets on each VLAN into IPv4 and IPv6 subnets. |
163 | 512 | vlan_subnets = { | 504 | vlan_subnets = { |
164 | @@ -562,52 +554,40 @@ | |||
165 | 562 | for vlan, (subnets_v4, subnets_v6) in vlan_subnets.items(): | 554 | for vlan, (subnets_v4, subnets_v6) in vlan_subnets.items(): |
166 | 563 | # IPv4 | 555 | # IPv4 |
167 | 564 | if len(subnets_v4) > 0: | 556 | if len(subnets_v4) > 0: |
189 | 565 | try: | 557 | config = get_dhcp_configure_for( |
190 | 566 | config = get_dhcp_configure_for( | 558 | 4, rack_controller, vlan, subnets_v4, ntp_servers, |
191 | 567 | 4, rack_controller, vlan, subnets_v4, ntp_servers, | 559 | default_domain, dhcp_snippets) |
192 | 568 | default_domain, dhcp_snippets) | 560 | failover_peer, subnets, hosts, interface = config |
193 | 569 | except DHCPConfigurationError: | 561 | if failover_peer is not None: |
194 | 570 | # XXX bug #1602412: this silently breaks DHCPv4, but we cannot | 562 | failover_peers_v4.append(failover_peer) |
195 | 571 | # allow it to crash here since DHCPv6 might be able to run. | 563 | shared_networks_v4.append({ |
196 | 572 | # This error may be irrelevant if there is an IPv4 network in | 564 | "name": "vlan-%d" % vlan.id, |
197 | 573 | # the MAAS model which is not configured on the rack, and the | 565 | "subnets": subnets, |
198 | 574 | # user only wants to serve DHCPv6. But it is still something | 566 | }) |
199 | 575 | # worth noting, so log it and continue. | 567 | hosts_v4.extend(hosts) |
200 | 576 | log.err(None, "Failure configuring DHCPv4.") | 568 | if interface is not None: |
180 | 577 | else: | ||
181 | 578 | failover_peer, subnets, hosts, interface = config | ||
182 | 579 | if failover_peer is not None: | ||
183 | 580 | failover_peers_v4.append(failover_peer) | ||
184 | 581 | shared_networks_v4.append({ | ||
185 | 582 | "name": "vlan-%d" % vlan.id, | ||
186 | 583 | "subnets": subnets, | ||
187 | 584 | }) | ||
188 | 585 | hosts_v4.extend(hosts) | ||
201 | 586 | interfaces_v4.add(interface) | 569 | interfaces_v4.add(interface) |
202 | 587 | # IPv6 | 570 | # IPv6 |
203 | 588 | if len(subnets_v6) > 0: | 571 | if len(subnets_v6) > 0: |
225 | 589 | try: | 572 | config = get_dhcp_configure_for( |
226 | 590 | config = get_dhcp_configure_for( | 573 | 6, rack_controller, vlan, subnets_v6, |
227 | 591 | 6, rack_controller, vlan, subnets_v6, | 574 | ntp_servers, default_domain, dhcp_snippets) |
228 | 592 | ntp_servers, default_domain, dhcp_snippets) | 575 | failover_peer, subnets, hosts, interface = config |
229 | 593 | except DHCPConfigurationError: | 576 | if failover_peer is not None: |
230 | 594 | # XXX bug #1602412: this silently breaks DHCPv6, but we cannot | 577 | failover_peers_v6.append(failover_peer) |
231 | 595 | # allow it to crash here since DHCPv4 might be able to run. | 578 | shared_networks_v6.append({ |
232 | 596 | # This error may be irrelevant if there is an IPv6 network in | 579 | "name": "vlan-%d" % vlan.id, |
233 | 597 | # the MAAS model which is not configured on the rack, and the | 580 | "subnets": subnets, |
234 | 598 | # user only wants to serve DHCPv4. But it is still something | 581 | }) |
235 | 599 | # worth noting, so log it and continue. | 582 | hosts_v6.extend(hosts) |
236 | 600 | log.err(None, "Failure configuring DHCPv6.") | 583 | if interface is not None: |
216 | 601 | else: | ||
217 | 602 | failover_peer, subnets, hosts, interface = config | ||
218 | 603 | if failover_peer is not None: | ||
219 | 604 | failover_peers_v6.append(failover_peer) | ||
220 | 605 | shared_networks_v6.append({ | ||
221 | 606 | "name": "vlan-%d" % vlan.id, | ||
222 | 607 | "subnets": subnets, | ||
223 | 608 | }) | ||
224 | 609 | hosts_v6.extend(hosts) | ||
237 | 610 | interfaces_v6.add(interface) | 584 | interfaces_v6.add(interface) |
238 | 585 | # When no interfaces exist for each IP version clear the shared networks | ||
239 | 586 | # as DHCP server cannot be started and needs to be stopped. | ||
240 | 587 | if len(interfaces_v4) == 0: | ||
241 | 588 | shared_networks_v4 = {} | ||
242 | 589 | if len(interfaces_v6) == 0: | ||
243 | 590 | shared_networks_v6 = {} | ||
244 | 611 | return DHCPConfigurationForRack( | 591 | return DHCPConfigurationForRack( |
245 | 612 | failover_peers_v4, shared_networks_v4, hosts_v4, interfaces_v4, | 592 | failover_peers_v4, shared_networks_v4, hosts_v4, interfaces_v4, |
246 | 613 | failover_peers_v6, shared_networks_v6, hosts_v6, interfaces_v6, | 593 | failover_peers_v6, shared_networks_v6, hosts_v6, interfaces_v6, |
247 | 614 | 594 | ||
248 | === modified file 'src/maasserver/exceptions.py' | |||
249 | --- src/maasserver/exceptions.py 2016-03-28 13:54:47 +0000 | |||
250 | +++ src/maasserver/exceptions.py 2016-12-06 08:04:41 +0000 | |||
251 | @@ -199,7 +199,3 @@ | |||
252 | 199 | information. | 199 | information. |
253 | 200 | """ | 200 | """ |
254 | 201 | api_error = int(http.client.SERVICE_UNAVAILABLE) | 201 | api_error = int(http.client.SERVICE_UNAVAILABLE) |
255 | 202 | |||
256 | 203 | |||
257 | 204 | class DHCPConfigurationError(MAASException): | ||
258 | 205 | """Raised when the configuration of DHCP hits a problem.""" | ||
259 | 206 | 202 | ||
260 | === modified file 'src/maasserver/forms_vlan.py' | |||
261 | --- src/maasserver/forms_vlan.py 2016-04-27 20:38:06 +0000 | |||
262 | +++ src/maasserver/forms_vlan.py 2016-12-06 08:04:41 +0000 | |||
263 | @@ -31,6 +31,7 @@ | |||
264 | 31 | 'dhcp_on', | 31 | 'dhcp_on', |
265 | 32 | 'primary_rack', | 32 | 'primary_rack', |
266 | 33 | 'secondary_rack', | 33 | 'secondary_rack', |
267 | 34 | 'relay_vlan', | ||
268 | 34 | ) | 35 | ) |
269 | 35 | 36 | ||
270 | 36 | def __init__(self, *args, **kwargs): | 37 | def __init__(self, *args, **kwargs): |
271 | @@ -40,6 +41,7 @@ | |||
272 | 40 | if instance is None and self.fabric is None: | 41 | if instance is None and self.fabric is None: |
273 | 41 | raise ValueError("Form requires either a instance or a fabric.") | 42 | raise ValueError("Form requires either a instance or a fabric.") |
274 | 42 | self._set_up_rack_fields() | 43 | self._set_up_rack_fields() |
275 | 44 | self._set_up_relay_vlan() | ||
276 | 43 | 45 | ||
277 | 44 | def _set_up_rack_fields(self): | 46 | def _set_up_rack_fields(self): |
278 | 45 | qs = RackController.objects.filter_by_vids([self.instance.vid]) | 47 | qs = RackController.objects.filter_by_vids([self.instance.vid]) |
279 | @@ -61,6 +63,22 @@ | |||
280 | 61 | secondary_rack = RackController.objects.get(id=secondary_rack_id) | 63 | secondary_rack = RackController.objects.get(id=secondary_rack_id) |
281 | 62 | self.initial['secondary_rack'] = secondary_rack.system_id | 64 | self.initial['secondary_rack'] = secondary_rack.system_id |
282 | 63 | 65 | ||
283 | 66 | def _set_up_relay_vlan(self): | ||
284 | 67 | # Configure the relay_vlan fields to include only VLAN's that are | ||
285 | 68 | # not already on a relay_vlan. If this is an update then it cannot | ||
286 | 69 | # be itself or never set when dhcp_on is True. | ||
287 | 70 | possible_relay_vlans = VLAN.objects.filter(relay_vlan__isnull=True) | ||
288 | 71 | if self.instance is not None: | ||
289 | 72 | possible_relay_vlans = possible_relay_vlans.exclude( | ||
290 | 73 | id=self.instance.id) | ||
291 | 74 | if self.instance.dhcp_on: | ||
292 | 75 | possible_relay_vlans = VLAN.objects.none() | ||
293 | 76 | if self.instance.relay_vlan is not None: | ||
294 | 77 | possible_relay_vlans = VLAN.objects.filter( | ||
295 | 78 | id=self.instance.relay_vlan.id) | ||
296 | 79 | self.fields['relay_vlan'] = forms.ModelChoiceField( | ||
297 | 80 | queryset=possible_relay_vlans, required=False) | ||
298 | 81 | |||
299 | 64 | def clean(self): | 82 | def clean(self): |
300 | 65 | cleaned_data = super(VLANForm, self).clean() | 83 | cleaned_data = super(VLANForm, self).clean() |
301 | 66 | # Automatically promote the secondary rack controller to the primary | 84 | # Automatically promote the secondary rack controller to the primary |
302 | @@ -120,5 +138,12 @@ | |||
303 | 120 | interface = super(VLANForm, self).save(commit=False) | 138 | interface = super(VLANForm, self).save(commit=False) |
304 | 121 | if self.fabric is not None: | 139 | if self.fabric is not None: |
305 | 122 | interface.fabric = self.fabric | 140 | interface.fabric = self.fabric |
306 | 141 | if ('relay_vlan' in self.data and | ||
307 | 142 | not self.cleaned_data.get('relay_vlan')): | ||
308 | 143 | # relay_vlan is being cleared. | ||
309 | 144 | interface.relay_vlan = None | ||
310 | 145 | if interface.dhcp_on: | ||
311 | 146 | # relay_vlan cannot be set when dhcp is on. | ||
312 | 147 | interface.relay_vlan = None | ||
313 | 123 | interface.save() | 148 | interface.save() |
314 | 124 | return interface | 149 | return interface |
315 | 125 | 150 | ||
316 | === modified file 'src/maasserver/migrations/builtin/maasserver/0056_add_description_to_fabric_and_space.py' | |||
317 | --- src/maasserver/migrations/builtin/maasserver/0056_add_description_to_fabric_and_space.py 2016-07-30 01:17:54 +0000 | |||
318 | +++ src/maasserver/migrations/builtin/maasserver/0056_add_description_to_fabric_and_space.py 2016-12-06 08:04:41 +0000 | |||
319 | @@ -44,6 +44,6 @@ | |||
320 | 44 | migrations.AlterField( | 44 | migrations.AlterField( |
321 | 45 | model_name='subnet', | 45 | model_name='subnet', |
322 | 46 | name='vlan', | 46 | name='vlan', |
324 | 47 | field=models.ForeignKey(to='maasserver.VLAN', default=maasserver.models.subnet.get_default_vlan, on_delete=django.db.models.deletion.PROTECT), | 47 | field=models.ForeignKey(to='maasserver.VLAN', default=None, on_delete=django.db.models.deletion.PROTECT), |
325 | 48 | ), | 48 | ), |
326 | 49 | ] | 49 | ] |
327 | 50 | 50 | ||
328 | === added file 'src/maasserver/migrations/builtin/maasserver/0095_vlan_relay_vlan.py' | |||
329 | --- src/maasserver/migrations/builtin/maasserver/0095_vlan_relay_vlan.py 1970-01-01 00:00:00 +0000 | |||
330 | +++ src/maasserver/migrations/builtin/maasserver/0095_vlan_relay_vlan.py 2016-12-06 08:04:41 +0000 | |||
331 | @@ -0,0 +1,23 @@ | |||
332 | 1 | # -*- coding: utf-8 -*- | ||
333 | 2 | from __future__ import unicode_literals | ||
334 | 3 | |||
335 | 4 | from django.db import ( | ||
336 | 5 | migrations, | ||
337 | 6 | models, | ||
338 | 7 | ) | ||
339 | 8 | import django.db.models.deletion | ||
340 | 9 | |||
341 | 10 | |||
342 | 11 | class Migration(migrations.Migration): | ||
343 | 12 | |||
344 | 13 | dependencies = [ | ||
345 | 14 | ('maasserver', '0094_add_unmanaged_subnets'), | ||
346 | 15 | ] | ||
347 | 16 | |||
348 | 17 | operations = [ | ||
349 | 18 | migrations.AddField( | ||
350 | 19 | model_name='vlan', | ||
351 | 20 | name='relay_vlan', | ||
352 | 21 | field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, null=True, blank=True, related_name='relay_vlans', to='maasserver.VLAN'), | ||
353 | 22 | ), | ||
354 | 23 | ] | ||
355 | 0 | 24 | ||
356 | === added file 'src/maasserver/migrations/builtin/maasserver/0096_set_default_vlan_field.py' | |||
357 | --- src/maasserver/migrations/builtin/maasserver/0096_set_default_vlan_field.py 1970-01-01 00:00:00 +0000 | |||
358 | +++ src/maasserver/migrations/builtin/maasserver/0096_set_default_vlan_field.py 2016-12-06 08:04:41 +0000 | |||
359 | @@ -0,0 +1,24 @@ | |||
360 | 1 | # -*- coding: utf-8 -*- | ||
361 | 2 | from __future__ import unicode_literals | ||
362 | 3 | |||
363 | 4 | from django.db import ( | ||
364 | 5 | migrations, | ||
365 | 6 | models, | ||
366 | 7 | ) | ||
367 | 8 | import django.db.models.deletion | ||
368 | 9 | import maasserver.models.subnet | ||
369 | 10 | |||
370 | 11 | |||
371 | 12 | class Migration(migrations.Migration): | ||
372 | 13 | |||
373 | 14 | dependencies = [ | ||
374 | 15 | ('maasserver', '0095_vlan_relay_vlan'), | ||
375 | 16 | ] | ||
376 | 17 | |||
377 | 18 | operations = [ | ||
378 | 19 | migrations.AlterField( | ||
379 | 20 | model_name='subnet', | ||
380 | 21 | name='vlan', | ||
381 | 22 | field=models.ForeignKey(to='maasserver.VLAN', default=maasserver.models.subnet.get_default_vlan, on_delete=django.db.models.deletion.PROTECT), | ||
382 | 23 | ), | ||
383 | 24 | ] | ||
384 | 0 | 25 | ||
385 | === modified file 'src/maasserver/models/tests/test_vlan.py' | |||
386 | --- src/maasserver/models/tests/test_vlan.py 2016-10-19 18:06:01 +0000 | |||
387 | +++ src/maasserver/models/tests/test_vlan.py 2016-12-06 08:04:41 +0000 | |||
388 | @@ -88,6 +88,14 @@ | |||
389 | 88 | 88 | ||
390 | 89 | class TestVLAN(MAASServerTestCase): | 89 | class TestVLAN(MAASServerTestCase): |
391 | 90 | 90 | ||
392 | 91 | def test_delete_relay_vlan_doesnt_delete_vlan(self): | ||
393 | 92 | relay_vlan = factory.make_VLAN() | ||
394 | 93 | vlan = factory.make_VLAN(relay_vlan=relay_vlan) | ||
395 | 94 | relay_vlan.delete() | ||
396 | 95 | vlan = reload_object(vlan) | ||
397 | 96 | self.assertIsNotNone(vlan) | ||
398 | 97 | self.assertIsNone(vlan.relay_vlan) | ||
399 | 98 | |||
400 | 91 | def test_get_name_for_default_vlan_is_untagged(self): | 99 | def test_get_name_for_default_vlan_is_untagged(self): |
401 | 92 | fabric = factory.make_Fabric() | 100 | fabric = factory.make_Fabric() |
402 | 93 | self.assertEqual("untagged", fabric.get_default_vlan().get_name()) | 101 | self.assertEqual("untagged", fabric.get_default_vlan().get_name()) |
403 | 94 | 102 | ||
404 | === modified file 'src/maasserver/models/vlan.py' | |||
405 | --- src/maasserver/models/vlan.py 2016-10-20 19:39:48 +0000 | |||
406 | +++ src/maasserver/models/vlan.py 2016-12-06 08:04:41 +0000 | |||
407 | @@ -14,6 +14,7 @@ | |||
408 | 14 | from django.db.models import ( | 14 | from django.db.models import ( |
409 | 15 | BooleanField, | 15 | BooleanField, |
410 | 16 | CharField, | 16 | CharField, |
411 | 17 | deletion, | ||
412 | 17 | ForeignKey, | 18 | ForeignKey, |
413 | 18 | IntegerField, | 19 | IntegerField, |
414 | 19 | Manager, | 20 | Manager, |
415 | @@ -169,6 +170,10 @@ | |||
416 | 169 | 'RackController', null=True, blank=True, editable=True, | 170 | 'RackController', null=True, blank=True, editable=True, |
417 | 170 | related_name='+') | 171 | related_name='+') |
418 | 171 | 172 | ||
419 | 173 | relay_vlan = ForeignKey( | ||
420 | 174 | 'self', null=True, blank=True, editable=True, | ||
421 | 175 | related_name='relay_vlans', on_delete=deletion.SET_NULL) | ||
422 | 176 | |||
423 | 172 | def __str__(self): | 177 | def __str__(self): |
424 | 173 | return "%s.%s" % (self.fabric.get_name(), self.get_name()) | 178 | return "%s.%s" % (self.fabric.get_name(), self.get_name()) |
425 | 174 | 179 | ||
426 | 175 | 180 | ||
427 | === modified file 'src/maasserver/static/js/angular/controllers/tests/test_vlan_details.js' | |||
428 | --- src/maasserver/static/js/angular/controllers/tests/test_vlan_details.js 2016-10-19 18:06:01 +0000 | |||
429 | +++ src/maasserver/static/js/angular/controllers/tests/test_vlan_details.js 2016-12-06 08:04:41 +0000 | |||
430 | @@ -427,6 +427,42 @@ | |||
431 | 427 | expect(controller.actionError).toBe(null); | 427 | expect(controller.actionError).toBe(null); |
432 | 428 | }); | 428 | }); |
433 | 429 | 429 | ||
434 | 430 | it("performAction for relay_dhcp called with all params", function() { | ||
435 | 431 | var controller = makeControllerResolveSetActiveItem(); | ||
436 | 432 | controller.actionOption = controller.RELAY_DHCP_ACTION; | ||
437 | 433 | // This will populate the default values for the racks with | ||
438 | 434 | // the current values from the mock objects. | ||
439 | 435 | controller.actionOptionChanged(); | ||
440 | 436 | controller.provideDHCPAction.subnet = 1; | ||
441 | 437 | controller.provideDHCPAction.gatewayIP = "192.168.0.1"; | ||
442 | 438 | controller.provideDHCPAction.startIP = "192.168.0.2"; | ||
443 | 439 | controller.provideDHCPAction.endIP = "192.168.0.254"; | ||
444 | 440 | var relay = { | ||
445 | 441 | id: makeInteger(5001, 6000) | ||
446 | 442 | }; | ||
447 | 443 | VLANsManager._items = [relay]; | ||
448 | 444 | controller.provideDHCPAction.relayVLAN = relay; | ||
449 | 445 | var defer = $q.defer(); | ||
450 | 446 | spyOn(VLANsManager, "configureDHCP").and.returnValue( | ||
451 | 447 | defer.promise); | ||
452 | 448 | controller.actionGo(); | ||
453 | 449 | defer.resolve(); | ||
454 | 450 | $scope.$digest(); | ||
455 | 451 | expect(VLANsManager.configureDHCP).toHaveBeenCalledWith( | ||
456 | 452 | controller.vlan, | ||
457 | 453 | [], | ||
458 | 454 | { | ||
459 | 455 | subnet: 1, | ||
460 | 456 | gateway: "192.168.0.1", | ||
461 | 457 | start: "192.168.0.2", | ||
462 | 458 | end: "192.168.0.254" | ||
463 | 459 | }, | ||
464 | 460 | relay.id | ||
465 | 461 | ); | ||
466 | 462 | expect(controller.actionOption).toBe(null); | ||
467 | 463 | expect(controller.actionError).toBe(null); | ||
468 | 464 | }); | ||
469 | 465 | |||
470 | 430 | it("performAction for disable_dhcp called with all params", function() { | 466 | it("performAction for disable_dhcp called with all params", function() { |
471 | 431 | var controller = makeControllerResolveSetActiveItem(); | 467 | var controller = makeControllerResolveSetActiveItem(); |
472 | 432 | controller.actionOption = controller.DISABLE_DHCP_ACTION; | 468 | controller.actionOption = controller.DISABLE_DHCP_ACTION; |
473 | @@ -457,6 +493,7 @@ | |||
474 | 457 | controller.actionOptionChanged(); | 493 | controller.actionOptionChanged(); |
475 | 458 | expect(controller.provideDHCPAction).toEqual({ | 494 | expect(controller.provideDHCPAction).toEqual({ |
476 | 459 | subnet: subnet.id, | 495 | subnet: subnet.id, |
477 | 496 | relayVLAN: null, | ||
478 | 460 | primaryRack: "p1", | 497 | primaryRack: "p1", |
479 | 461 | secondaryRack: "p2", | 498 | secondaryRack: "p2", |
480 | 462 | maxIPs: 0, | 499 | maxIPs: 0, |
481 | @@ -488,6 +525,7 @@ | |||
482 | 488 | controller.actionOptionChanged(); | 525 | controller.actionOptionChanged(); |
483 | 489 | expect(controller.provideDHCPAction).toEqual({ | 526 | expect(controller.provideDHCPAction).toEqual({ |
484 | 490 | subnet: subnet.id, | 527 | subnet: subnet.id, |
485 | 528 | relayVLAN: null, | ||
486 | 491 | primaryRack: "p1", | 529 | primaryRack: "p1", |
487 | 492 | secondaryRack: "p2", | 530 | secondaryRack: "p2", |
488 | 493 | maxIPs: 26, | 531 | maxIPs: 26, |
489 | @@ -551,30 +589,45 @@ | |||
490 | 551 | expect(controller.actionOptions).toEqual([]); | 589 | expect(controller.actionOptions).toEqual([]); |
491 | 552 | }); | 590 | }); |
492 | 553 | 591 | ||
494 | 554 | it("returns enable_dhcp and delete when dhcp is off", | 592 | it("returns enable_dhcp, relay_dhcp and delete when dhcp is off", |
495 | 555 | function() { | 593 | function() { |
496 | 556 | vlan.dhcp_on = false; | 594 | vlan.dhcp_on = false; |
497 | 557 | UsersManager._authUser = {is_superuser: true}; | 595 | UsersManager._authUser = {is_superuser: true}; |
498 | 558 | var controller = makeControllerResolveSetActiveItem(); | 596 | var controller = makeControllerResolveSetActiveItem(); |
499 | 559 | expect(controller.actionOptions).toEqual([ | 597 | expect(controller.actionOptions).toEqual([ |
500 | 560 | controller.PROVIDE_DHCP_ACTION, | 598 | controller.PROVIDE_DHCP_ACTION, |
501 | 599 | controller.RELAY_DHCP_ACTION, | ||
502 | 561 | controller.DELETE_ACTION | 600 | controller.DELETE_ACTION |
503 | 562 | ]); | 601 | ]); |
504 | 563 | expect(controller.PROVIDE_DHCP_ACTION.title).toBe("Provide DHCP"); | 602 | expect(controller.PROVIDE_DHCP_ACTION.title).toBe("Provide DHCP"); |
505 | 564 | }); | 603 | }); |
506 | 565 | 604 | ||
508 | 566 | it("returns disable_dhcp, enable_dhcp (with new title) and delete "+ | 605 | it("returns enable_dhcp (with new title), disable_dhcp and delete "+ |
509 | 567 | "when dhcp is on", function() { | 606 | "when dhcp is on", function() { |
510 | 568 | vlan.dhcp_on = true; | 607 | vlan.dhcp_on = true; |
511 | 569 | UsersManager._authUser = {is_superuser: true}; | 608 | UsersManager._authUser = {is_superuser: true}; |
512 | 570 | var controller = makeControllerResolveSetActiveItem(); | 609 | var controller = makeControllerResolveSetActiveItem(); |
513 | 571 | expect(controller.actionOptions).toEqual([ | 610 | expect(controller.actionOptions).toEqual([ |
514 | 611 | controller.PROVIDE_DHCP_ACTION, | ||
515 | 572 | controller.DISABLE_DHCP_ACTION, | 612 | controller.DISABLE_DHCP_ACTION, |
516 | 573 | controller.PROVIDE_DHCP_ACTION, | ||
517 | 574 | controller.DELETE_ACTION | 613 | controller.DELETE_ACTION |
518 | 575 | ]); | 614 | ]); |
519 | 576 | expect(controller.PROVIDE_DHCP_ACTION.title).toBe( | 615 | expect(controller.PROVIDE_DHCP_ACTION.title).toBe( |
520 | 577 | "Reconfigure DHCP"); | 616 | "Reconfigure DHCP"); |
521 | 578 | }); | 617 | }); |
522 | 618 | |||
523 | 619 | it("returns relay_dhcp (with new title), disable_dhcp and delete "+ | ||
524 | 620 | "when relay_vlan is set", function() { | ||
525 | 621 | vlan.relay_vlan = 5001; | ||
526 | 622 | UsersManager._authUser = {is_superuser: true}; | ||
527 | 623 | var controller = makeControllerResolveSetActiveItem(); | ||
528 | 624 | expect(controller.actionOptions).toEqual([ | ||
529 | 625 | controller.RELAY_DHCP_ACTION, | ||
530 | 626 | controller.DISABLE_DHCP_ACTION, | ||
531 | 627 | controller.DELETE_ACTION | ||
532 | 628 | ]); | ||
533 | 629 | expect(controller.RELAY_DHCP_ACTION.title).toBe( | ||
534 | 630 | "Reconfigure DHCP relay"); | ||
535 | 631 | }); | ||
536 | 579 | }); | 632 | }); |
537 | 580 | }); | 633 | }); |
538 | 581 | 634 | ||
539 | === modified file 'src/maasserver/static/js/angular/controllers/vlan_details.js' | |||
540 | --- src/maasserver/static/js/angular/controllers/vlan_details.js 2016-10-19 18:06:01 +0000 | |||
541 | +++ src/maasserver/static/js/angular/controllers/vlan_details.js 2016-12-06 08:04:41 +0000 | |||
542 | @@ -4,6 +4,18 @@ | |||
543 | 4 | * MAAS VLAN Details Controller | 4 | * MAAS VLAN Details Controller |
544 | 5 | */ | 5 | */ |
545 | 6 | 6 | ||
546 | 7 | angular.module('MAAS').filter('ignoreSelf', function () { | ||
547 | 8 | return function(objects, self) { | ||
548 | 9 | var filtered = []; | ||
549 | 10 | angular.forEach(objects, function(obj) { | ||
550 | 11 | if(obj !== self) { | ||
551 | 12 | filtered.push(obj); | ||
552 | 13 | } | ||
553 | 14 | }); | ||
554 | 15 | return filtered; | ||
555 | 16 | }; | ||
556 | 17 | }); | ||
557 | 18 | |||
558 | 7 | angular.module('MAAS').controller('VLANDetailsController', [ | 19 | angular.module('MAAS').controller('VLANDetailsController', [ |
559 | 8 | '$scope', '$rootScope', '$routeParams', '$filter', '$location', | 20 | '$scope', '$rootScope', '$routeParams', '$filter', '$location', |
560 | 9 | 'VLANsManager', 'SubnetsManager', 'SpacesManager', 'FabricsManager', | 21 | 'VLANsManager', 'SubnetsManager', 'SpacesManager', 'FabricsManager', |
561 | @@ -27,10 +39,15 @@ | |||
562 | 27 | $rootScope.page = "networks"; | 39 | $rootScope.page = "networks"; |
563 | 28 | 40 | ||
564 | 29 | vm.PROVIDE_DHCP_ACTION = { | 41 | vm.PROVIDE_DHCP_ACTION = { |
566 | 30 | // Note: 'title' is setubndynamically depending on whether or not | 42 | // Note: 'title' is set dynamically depending on whether or not |
567 | 31 | // DHCP is already enabled on this VLAN. | 43 | // DHCP is already enabled on this VLAN. |
568 | 32 | name: "enable_dhcp" | 44 | name: "enable_dhcp" |
569 | 33 | }; | 45 | }; |
570 | 46 | vm.RELAY_DHCP_ACTION = { | ||
571 | 47 | // Note: 'title' is set ndynamically depending on whether or not | ||
572 | 48 | // DHCP relay is already enabled on this VLAN. | ||
573 | 49 | name: "relay_dhcp" | ||
574 | 50 | }; | ||
575 | 34 | vm.DISABLE_DHCP_ACTION = { | 51 | vm.DISABLE_DHCP_ACTION = { |
576 | 35 | name: "disable_dhcp", | 52 | name: "disable_dhcp", |
577 | 36 | title: "Disable DHCP" | 53 | title: "Disable DHCP" |
578 | @@ -47,6 +64,7 @@ | |||
579 | 47 | vm.actionOption = null; | 64 | vm.actionOption = null; |
580 | 48 | vm.actionOptions = []; | 65 | vm.actionOptions = []; |
581 | 49 | vm.vlanManager = VLANsManager; | 66 | vm.vlanManager = VLANsManager; |
582 | 67 | vm.vlans = VLANsManager.getItems(); | ||
583 | 50 | vm.subnets = SubnetsManager.getItems(); | 68 | vm.subnets = SubnetsManager.getItems(); |
584 | 51 | vm.spaces = SpacesManager.getItems(); | 69 | vm.spaces = SpacesManager.getItems(); |
585 | 52 | vm.fabrics = FabricsManager.getItems(); | 70 | vm.fabrics = FabricsManager.getItems(); |
586 | @@ -78,10 +96,15 @@ | |||
587 | 78 | // Initialize the provideDHCPAction structure with the current primary | 96 | // Initialize the provideDHCPAction structure with the current primary |
588 | 79 | // and secondary rack, plus an indication regarding whether or not | 97 | // and secondary rack, plus an indication regarding whether or not |
589 | 80 | // adding a dynamic IP range is required. | 98 | // adding a dynamic IP range is required. |
591 | 81 | vm.initProvideDHCP = function() { | 99 | vm.initProvideDHCP = function(forRelay) { |
592 | 82 | vm.provideDHCPAction = {}; | 100 | vm.provideDHCPAction = {}; |
593 | 83 | var dhcp = vm.provideDHCPAction; | 101 | var dhcp = vm.provideDHCPAction; |
594 | 84 | dhcp.subnet = null; | 102 | dhcp.subnet = null; |
595 | 103 | dhcp.relayVLAN = null; | ||
596 | 104 | if (angular.isNumber(vm.vlan.relay_vlan)) { | ||
597 | 105 | dhcp.relayVLAN = VLANsManager.getItemFromList( | ||
598 | 106 | vm.vlan.relay_vlan); | ||
599 | 107 | } | ||
600 | 85 | if (angular.isObject(vm.primaryRack)) { | 108 | if (angular.isObject(vm.primaryRack)) { |
601 | 86 | dhcp.primaryRack = vm.primaryRack.system_id; | 109 | dhcp.primaryRack = vm.primaryRack.system_id; |
602 | 87 | } else if(vm.relatedControllers.length > 0) { | 110 | } else if(vm.relatedControllers.length > 0) { |
603 | @@ -140,15 +163,19 @@ | |||
604 | 140 | } | 163 | } |
605 | 141 | // Since we are setting default values for these three options, | 164 | // Since we are setting default values for these three options, |
606 | 142 | // ensure all the appropriate updates occur. | 165 | // ensure all the appropriate updates occur. |
610 | 143 | vm.updatePrimaryRack(); | 166 | if(!forRelay) { |
611 | 144 | vm.updateSecondaryRack(); | 167 | vm.updatePrimaryRack(); |
612 | 145 | vm.updateSubnet(); | 168 | vm.updateSecondaryRack(); |
613 | 169 | } | ||
614 | 170 | vm.updateSubnet(forRelay); | ||
615 | 146 | }; | 171 | }; |
616 | 147 | 172 | ||
617 | 148 | // Called when the actionOption has changed. | 173 | // Called when the actionOption has changed. |
618 | 149 | vm.actionOptionChanged = function() { | 174 | vm.actionOptionChanged = function() { |
619 | 150 | if(vm.actionOption.name === "enable_dhcp") { | 175 | if(vm.actionOption.name === "enable_dhcp") { |
621 | 151 | vm.initProvideDHCP(); | 176 | vm.initProvideDHCP(false); |
622 | 177 | } else if(vm.actionOption.name === "relay_dhcp") { | ||
623 | 178 | vm.initProvideDHCP(true); | ||
624 | 152 | } | 179 | } |
625 | 153 | // Clear the action error. | 180 | // Clear the action error. |
626 | 154 | vm.actionError = null; | 181 | vm.actionError = null; |
627 | @@ -200,7 +227,7 @@ | |||
628 | 200 | }; | 227 | }; |
629 | 201 | 228 | ||
630 | 202 | // Called from the Provide DHCP form when the subnet selection changes. | 229 | // Called from the Provide DHCP form when the subnet selection changes. |
632 | 203 | vm.updateSubnet = function() { | 230 | vm.updateSubnet = function(forRelay) { |
633 | 204 | var dhcp = vm.provideDHCPAction; | 231 | var dhcp = vm.provideDHCPAction; |
634 | 205 | var subnet = SubnetsManager.getItemFromList(dhcp.subnet); | 232 | var subnet = SubnetsManager.getItemFromList(dhcp.subnet); |
635 | 206 | if(angular.isObject(subnet)) { | 233 | if(angular.isObject(subnet)) { |
636 | @@ -212,10 +239,17 @@ | |||
637 | 212 | } | 239 | } |
638 | 213 | if(angular.isObject(iprange) && iprange.num_addresses > 0) { | 240 | if(angular.isObject(iprange) && iprange.num_addresses > 0) { |
639 | 214 | dhcp.maxIPs = iprange.num_addresses; | 241 | dhcp.maxIPs = iprange.num_addresses; |
644 | 215 | dhcp.startIP = iprange.start; | 242 | if(forRelay) { |
645 | 216 | dhcp.endIP = iprange.end; | 243 | dhcp.startIP = ""; |
646 | 217 | dhcp.startPlaceholder = iprange.start; | 244 | dhcp.endIP = ""; |
647 | 218 | dhcp.endPlaceholder = iprange.end; | 245 | dhcp.startPlaceholder = iprange.start + "( optional)"; |
648 | 246 | dhcp.endPlaceholder = iprange.end + " (optional)"; | ||
649 | 247 | } else { | ||
650 | 248 | dhcp.startIP = iprange.start; | ||
651 | 249 | dhcp.endIP = iprange.end; | ||
652 | 250 | dhcp.startPlaceholder = iprange.start; | ||
653 | 251 | dhcp.endPlaceholder = iprange.end; | ||
654 | 252 | } | ||
655 | 219 | } else { | 253 | } else { |
656 | 220 | // Need to add a dynamic range, but according to our data, | 254 | // Need to add a dynamic range, but according to our data, |
657 | 221 | // there is no room on the subnet for a dynamic range. | 255 | // there is no room on the subnet for a dynamic range. |
658 | @@ -226,8 +260,14 @@ | |||
659 | 226 | dhcp.endPlaceholder = "(no available IPs)"; | 260 | dhcp.endPlaceholder = "(no available IPs)"; |
660 | 227 | } | 261 | } |
661 | 228 | if(angular.isString(suggested_gateway)) { | 262 | if(angular.isString(suggested_gateway)) { |
664 | 229 | dhcp.gatewayIP = suggested_gateway; | 263 | if(forRelay) { |
665 | 230 | dhcp.gatewayPlaceholder = suggested_gateway; | 264 | dhcp.gatewayIP = ""; |
666 | 265 | dhcp.gatewayPlaceholder = ( | ||
667 | 266 | suggested_gateway + " (optional)"); | ||
668 | 267 | } else { | ||
669 | 268 | dhcp.gatewayIP = suggested_gateway; | ||
670 | 269 | dhcp.gatewayPlaceholder = suggested_gateway; | ||
671 | 270 | } | ||
672 | 231 | } else { | 271 | } else { |
673 | 232 | // This means the subnet already has a gateway, so don't | 272 | // This means the subnet already has a gateway, so don't |
674 | 233 | // bother populating it. | 273 | // bother populating it. |
675 | @@ -261,8 +301,24 @@ | |||
676 | 261 | vm.actionError = null; | 301 | vm.actionError = null; |
677 | 262 | }; | 302 | }; |
678 | 263 | 303 | ||
679 | 304 | // Return True if the current action can be performed. | ||
680 | 305 | vm.canPerformAction = function() { | ||
681 | 306 | if(vm.actionOption.name === "enable_dhcp") { | ||
682 | 307 | return vm.relatedSubnets.length > 0; | ||
683 | 308 | } else if(vm.actionOption.name === "relay_dhcp") { | ||
684 | 309 | return angular.isObject(vm.provideDHCPAction.relayVLAN); | ||
685 | 310 | } else { | ||
686 | 311 | return true; | ||
687 | 312 | } | ||
688 | 313 | }; | ||
689 | 314 | |||
690 | 264 | // Perform the action. | 315 | // Perform the action. |
691 | 265 | vm.actionGo = function() { | 316 | vm.actionGo = function() { |
692 | 317 | // Do nothing if action cannot be performed. | ||
693 | 318 | if(!vm.canPerformAction()) { | ||
694 | 319 | return; | ||
695 | 320 | } | ||
696 | 321 | |||
697 | 266 | if(vm.actionOption.name === "enable_dhcp") { | 322 | if(vm.actionOption.name === "enable_dhcp") { |
698 | 267 | var dhcp = vm.provideDHCPAction; | 323 | var dhcp = vm.provideDHCPAction; |
699 | 268 | var controllers = []; | 324 | var controllers = []; |
700 | @@ -294,6 +350,23 @@ | |||
701 | 294 | vm.actionError = result.error; | 350 | vm.actionError = result.error; |
702 | 295 | vm.actionOption = vm.PROVIDE_DHCP_ACTION; | 351 | vm.actionOption = vm.PROVIDE_DHCP_ACTION; |
703 | 296 | }); | 352 | }); |
704 | 353 | } else if(vm.actionOption.name === "relay_dhcp") { | ||
705 | 354 | // These will be undefined if they don't exist, and the region | ||
706 | 355 | // will simply get an empty dictionary. | ||
707 | 356 | var extraDHCP = {}; | ||
708 | 357 | extraDHCP.subnet = vm.provideDHCPAction.subnet; | ||
709 | 358 | extraDHCP.start = vm.provideDHCPAction.startIP; | ||
710 | 359 | extraDHCP.end = vm.provideDHCPAction.endIP; | ||
711 | 360 | extraDHCP.gateway = vm.provideDHCPAction.gatewayIP; | ||
712 | 361 | var relay = vm.provideDHCPAction.relayVLAN.id; | ||
713 | 362 | VLANsManager.configureDHCP( | ||
714 | 363 | vm.vlan, [], extraDHCP, relay).then(function() { | ||
715 | 364 | vm.actionOption = null; | ||
716 | 365 | vm.actionError = null; | ||
717 | 366 | }, function(result) { | ||
718 | 367 | vm.actionError = result.error; | ||
719 | 368 | vm.actionOption = vm.RELAY_DHCP_ACTION; | ||
720 | 369 | }); | ||
721 | 297 | } else if(vm.actionOption.name === "disable_dhcp") { | 370 | } else if(vm.actionOption.name === "disable_dhcp") { |
722 | 298 | VLANsManager.disableDHCP(vm.vlan).then(function() { | 371 | VLANsManager.disableDHCP(vm.vlan).then(function() { |
723 | 299 | vm.actionOption = null; | 372 | vm.actionOption = null; |
724 | @@ -319,6 +392,30 @@ | |||
725 | 319 | return vm.actionError !== null; | 392 | return vm.actionError !== null; |
726 | 320 | }; | 393 | }; |
727 | 321 | 394 | ||
728 | 395 | // Return the name of the VLAN. | ||
729 | 396 | vm.getFullVLANName = function(vlan_id) { | ||
730 | 397 | var vlan = VLANsManager.getItemFromList(vlan_id); | ||
731 | 398 | var fabric = FabricsManager.getItemFromList(vlan.fabric); | ||
732 | 399 | return ( | ||
733 | 400 | FabricsManager.getName(fabric) + "." + | ||
734 | 401 | VLANsManager.getName(vlan)); | ||
735 | 402 | }; | ||
736 | 403 | |||
737 | 404 | // Return the current DHCP status. | ||
738 | 405 | vm.getDHCPStatus = function() { | ||
739 | 406 | if(vm.vlan) { | ||
740 | 407 | if(vm.vlan.dhcp_on) { | ||
741 | 408 | return "Enabled"; | ||
742 | 409 | } else if(vm.vlan.relay_vlan) { | ||
743 | 410 | return "Relayed via " + vm.getFullVLANName(vm.vlan.relay_vlan); | ||
744 | 411 | } else { | ||
745 | 412 | return "Disabled"; | ||
746 | 413 | } | ||
747 | 414 | } else { | ||
748 | 415 | return ""; | ||
749 | 416 | } | ||
750 | 417 | }; | ||
751 | 418 | |||
752 | 322 | // Updates the page title. | 419 | // Updates the page title. |
753 | 323 | function updateTitle() { | 420 | function updateTitle() { |
754 | 324 | var vlan = vm.vlan; | 421 | var vlan = vm.vlan; |
755 | @@ -413,13 +510,22 @@ | |||
756 | 413 | // object, since it's watched from $scope.) | 510 | // object, since it's watched from $scope.) |
757 | 414 | vm.actionOptions.length = 0; | 511 | vm.actionOptions.length = 0; |
758 | 415 | if(UsersManager.isSuperUser()) { | 512 | if(UsersManager.isSuperUser()) { |
762 | 416 | if(vlan.dhcp_on === true) { | 513 | if(!vlan.relay_vlan) { |
763 | 417 | vm.actionOptions.push(vm.DISABLE_DHCP_ACTION); | 514 | if(vlan.dhcp_on === true) { |
764 | 418 | vm.PROVIDE_DHCP_ACTION.title = "Reconfigure DHCP"; | 515 | vm.PROVIDE_DHCP_ACTION.title = "Reconfigure DHCP"; |
765 | 516 | vm.actionOptions.push(vm.PROVIDE_DHCP_ACTION); | ||
766 | 517 | vm.actionOptions.push(vm.DISABLE_DHCP_ACTION); | ||
767 | 518 | } else { | ||
768 | 519 | vm.PROVIDE_DHCP_ACTION.title = "Provide DHCP"; | ||
769 | 520 | vm.RELAY_DHCP_ACTION.title = "Relay DHCP"; | ||
770 | 521 | vm.actionOptions.push(vm.PROVIDE_DHCP_ACTION); | ||
771 | 522 | vm.actionOptions.push(vm.RELAY_DHCP_ACTION); | ||
772 | 523 | } | ||
773 | 419 | } else { | 524 | } else { |
775 | 420 | vm.PROVIDE_DHCP_ACTION.title = "Provide DHCP"; | 525 | vm.actionOptions.push(vm.RELAY_DHCP_ACTION); |
776 | 526 | vm.actionOptions.push(vm.DISABLE_DHCP_ACTION); | ||
777 | 527 | vm.RELAY_DHCP_ACTION.title = "Reconfigure DHCP relay"; | ||
778 | 421 | } | 528 | } |
779 | 422 | vm.actionOptions.push(vm.PROVIDE_DHCP_ACTION); | ||
780 | 423 | if(!vm.isFabricDefault) { | 529 | if(!vm.isFabricDefault) { |
781 | 424 | vm.actionOptions.push(vm.DELETE_ACTION); | 530 | vm.actionOptions.push(vm.DELETE_ACTION); |
782 | 425 | } | 531 | } |
783 | @@ -467,6 +573,8 @@ | |||
784 | 467 | $scope.$watch("vlanDetails.vlan.name", updateTitle); | 573 | $scope.$watch("vlanDetails.vlan.name", updateTitle); |
785 | 468 | $scope.$watch("vlanDetails.vlan.vid", updateTitle); | 574 | $scope.$watch("vlanDetails.vlan.vid", updateTitle); |
786 | 469 | $scope.$watch("vlanDetails.vlan.dhcp_on", updatePossibleActions); | 575 | $scope.$watch("vlanDetails.vlan.dhcp_on", updatePossibleActions); |
787 | 576 | $scope.$watch( | ||
788 | 577 | "vlanDetails.vlan.relay_vlan", updatePossibleActions); | ||
789 | 470 | $scope.$watch("vlanDetails.fabric.name", updateTitle); | 578 | $scope.$watch("vlanDetails.fabric.name", updateTitle); |
790 | 471 | $scope.$watch( | 579 | $scope.$watch( |
791 | 472 | "vlanDetails.vlan.primary_rack", updateManagementRacks); | 580 | "vlanDetails.vlan.primary_rack", updateManagementRacks); |
792 | 473 | 581 | ||
793 | === modified file 'src/maasserver/static/js/angular/factories/tests/test_vlans.js' | |||
794 | --- src/maasserver/static/js/angular/factories/tests/test_vlans.js 2016-05-11 19:01:48 +0000 | |||
795 | +++ src/maasserver/static/js/angular/factories/tests/test_vlans.js 2016-12-06 08:04:41 +0000 | |||
796 | @@ -38,14 +38,16 @@ | |||
797 | 38 | var result = {}; | 38 | var result = {}; |
798 | 39 | var controllers = ["a", "b"]; | 39 | var controllers = ["a", "b"]; |
799 | 40 | var extra = {"c": "d"}; | 40 | var extra = {"c": "d"}; |
800 | 41 | var relay = makeInteger(1, 500); | ||
801 | 41 | spyOn(RegionConnection, "callMethod").and.returnValue(result); | 42 | spyOn(RegionConnection, "callMethod").and.returnValue(result); |
802 | 42 | expect(VLANsManager.configureDHCP( | 43 | expect(VLANsManager.configureDHCP( |
804 | 43 | obj, controllers, extra)).toBe(result); | 44 | obj, controllers, extra, relay)).toBe(result); |
805 | 44 | expect(RegionConnection.callMethod).toHaveBeenCalledWith( | 45 | expect(RegionConnection.callMethod).toHaveBeenCalledWith( |
806 | 45 | "vlan.configure_dhcp", { | 46 | "vlan.configure_dhcp", { |
807 | 46 | id: obj.id, | 47 | id: obj.id, |
808 | 47 | controllers: controllers, | 48 | controllers: controllers, |
810 | 48 | extra: extra | 49 | extra: extra, |
811 | 50 | relay_vlan: relay | ||
812 | 49 | }, true); | 51 | }, true); |
813 | 50 | }); | 52 | }); |
814 | 51 | }); | 53 | }); |
815 | @@ -60,7 +62,8 @@ | |||
816 | 60 | expect(RegionConnection.callMethod).toHaveBeenCalledWith( | 62 | expect(RegionConnection.callMethod).toHaveBeenCalledWith( |
817 | 61 | "vlan.configure_dhcp", { | 63 | "vlan.configure_dhcp", { |
818 | 62 | id: obj.id, | 64 | id: obj.id, |
820 | 63 | controllers: [] | 65 | controllers: [], |
821 | 66 | relay_vlan: null | ||
822 | 64 | }, true); | 67 | }, true); |
823 | 65 | }); | 68 | }); |
824 | 66 | }); | 69 | }); |
825 | 67 | 70 | ||
826 | === modified file 'src/maasserver/static/js/angular/factories/vlans.js' | |||
827 | --- src/maasserver/static/js/angular/factories/vlans.js 2016-05-11 19:01:48 +0000 | |||
828 | +++ src/maasserver/static/js/angular/factories/vlans.js 2016-12-06 08:04:41 +0000 | |||
829 | @@ -53,13 +53,17 @@ | |||
830 | 53 | 53 | ||
831 | 54 | // Configure DHCP on the VLAN | 54 | // Configure DHCP on the VLAN |
832 | 55 | VLANsManager.prototype.configureDHCP = function( | 55 | VLANsManager.prototype.configureDHCP = function( |
834 | 56 | vlan, controllers, extra) { | 56 | vlan, controllers, extra, relay_vlan) { |
835 | 57 | var params = { | ||
836 | 58 | "id": vlan.id, | ||
837 | 59 | "controllers": controllers, | ||
838 | 60 | "extra": extra | ||
839 | 61 | }; | ||
840 | 62 | if(relay_vlan === null || angular.isNumber(relay_vlan)) { | ||
841 | 63 | params.relay_vlan = relay_vlan; | ||
842 | 64 | } | ||
843 | 57 | return RegionConnection.callMethod( | 65 | return RegionConnection.callMethod( |
849 | 58 | "vlan.configure_dhcp", { | 66 | "vlan.configure_dhcp", params, true); |
845 | 59 | "id": vlan.id, | ||
846 | 60 | "controllers": controllers, | ||
847 | 61 | "extra": extra | ||
848 | 62 | }, true); | ||
850 | 63 | }; | 67 | }; |
851 | 64 | 68 | ||
852 | 65 | // Disable DHCP on the VLAN | 69 | // Disable DHCP on the VLAN |
853 | @@ -67,7 +71,8 @@ | |||
854 | 67 | return RegionConnection.callMethod( | 71 | return RegionConnection.callMethod( |
855 | 68 | "vlan.configure_dhcp", { | 72 | "vlan.configure_dhcp", { |
856 | 69 | "id": vlan.id, | 73 | "id": vlan.id, |
858 | 70 | "controllers": [] | 74 | "controllers": [], |
859 | 75 | "relay_vlan": null | ||
860 | 71 | }, true); | 76 | }, true); |
861 | 72 | }; | 77 | }; |
862 | 73 | 78 | ||
863 | 74 | 79 | ||
864 | === modified file 'src/maasserver/static/partials/vlan-details.html' | |||
865 | --- src/maasserver/static/partials/vlan-details.html 2016-11-17 11:46:08 +0000 | |||
866 | +++ src/maasserver/static/partials/vlan-details.html 2016-12-06 08:04:41 +0000 | |||
867 | @@ -1,27 +1,27 @@ | |||
869 | 1 | <div data-ng-hide="vlanDetails.loaded"> | 1 | <div data-ng-if="!vlanDetails.loaded"> |
870 | 2 | <header class="page-header" sticky> | 2 | <header class="page-header" sticky> |
871 | 3 | <div class="wrapper--inner"> | 3 | <div class="wrapper--inner"> |
872 | 4 | <h1 class="page-header__title">Loading...</h1> | 4 | <h1 class="page-header__title">Loading...</h1> |
873 | 5 | </div> | 5 | </div> |
874 | 6 | </header> | 6 | </header> |
875 | 7 | </div> | 7 | </div> |
877 | 8 | <div class="ng-hide" data-ng-show="vlanDetails.loaded"> | 8 | <div data-ng-if="vlanDetails.loaded"> |
878 | 9 | <header class="page-header" sticky> | 9 | <header class="page-header" sticky> |
879 | 10 | <div class="wrapper--inner"> | 10 | <div class="wrapper--inner"> |
880 | 11 | <h1 class="page-header__title">{$ vlanDetails.title $}</h1> | 11 | <h1 class="page-header__title">{$ vlanDetails.title $}</h1> |
881 | 12 | <!-- "Take action" dropdown --> | 12 | <!-- "Take action" dropdown --> |
883 | 13 | <div class="page-header__controls" data-ng-show="vlanDetails.actionOptions.length"> | 13 | <div class="page-header__controls" data-ng-if="vlanDetails.actionOptions.length"> |
884 | 14 | <div data-maas-cta="vlanDetails.actionOptions" | 14 | <div data-maas-cta="vlanDetails.actionOptions" |
885 | 15 | data-ng-model="vlanDetails.actionOption" | 15 | data-ng-model="vlanDetails.actionOption" |
886 | 16 | data-ng-change="vlanDetails.actionOptionChanged()"> | 16 | data-ng-change="vlanDetails.actionOptionChanged()"> |
887 | 17 | </div> | 17 | </div> |
888 | 18 | </div> | 18 | </div> |
892 | 19 | <div class="page-header__dropdown" data-ng-class="{ 'is-open': vlanDetails.actionOption }"> | 19 | <div class="page-header__dropdown" data-ng-class="{ 'is-open': vlanDetails.actionOption }" data-ng-if="vlanDetails.actionOption"> |
893 | 20 | <section class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="vlanDetails.actionOption.name === 'enable_dhcp'"> | 20 | <section class="page-header__section twelve-col u-margin--bottom-none" data-ng-if="vlanDetails.actionOption.name === 'enable_dhcp'"> |
894 | 21 | <h3 class="page-header__dropdown-title" data-ng-show="vlanDetails.actionOption.name === 'enable_dhcp'">Provide DHCP</h3> | 21 | <h3 class="page-header__dropdown-title">Provide DHCP</h3> |
895 | 22 | <form class="form form--stack"> | 22 | <form class="form form--stack"> |
896 | 23 | <!-- This is just for visual reasons, since we need an additional border to begin the form if there is no error. --> | 23 | <!-- This is just for visual reasons, since we need an additional border to begin the form if there is no error. --> |
898 | 24 | <fieldset class="form__fieldset six-col" data-ng-show="vlanDetails.actionOption.name === 'enable_dhcp'"> | 24 | <fieldset class="form__fieldset six-col"> |
899 | 25 | <div class="form__group" data-ng-hide="vlanDetails.relatedSubnets.length === 0"> | 25 | <div class="form__group" data-ng-hide="vlanDetails.relatedSubnets.length === 0"> |
900 | 26 | <label for="primary-rack" class="form__group-label two-col">{$ vlanDetails.relatedControllers.length <= 1 ? "Rack controller" : "Primary controller" $}</label> | 26 | <label for="primary-rack" class="form__group-label two-col">{$ vlanDetails.relatedControllers.length <= 1 ? "Rack controller" : "Primary controller" $}</label> |
901 | 27 | <div class="form__group-input three-col"> | 27 | <div class="form__group-input three-col"> |
902 | @@ -32,7 +32,7 @@ | |||
903 | 32 | <option value="" | 32 | <option value="" |
904 | 33 | disabled="disabled" | 33 | disabled="disabled" |
905 | 34 | selected="selected" | 34 | selected="selected" |
907 | 35 | data-ng-show="vlanDetails.provideDHCPAction.primaryRack === ''">Choose primary controller</option> | 35 | data-ng-if="vlanDetails.provideDHCPAction.primaryRack === ''">Choose primary controller</option> |
908 | 36 | </select> | 36 | </select> |
909 | 37 | </div> | 37 | </div> |
910 | 38 | </div> | 38 | </div> |
911 | @@ -40,14 +40,14 @@ | |||
912 | 40 | <label for="secondary-rack" class="form__group-label two-col">Secondary controller</label> | 40 | <label for="secondary-rack" class="form__group-label two-col">Secondary controller</label> |
913 | 41 | <div class="form__group-input three-col"> | 41 | <div class="form__group-input three-col"> |
914 | 42 | <select name="secondary-rack" | 42 | <select name="secondary-rack" |
916 | 43 | data-ng-show="vlanDetails.relatedControllers.length > 1" | 43 | data-ng-if="vlanDetails.relatedControllers.length > 1" |
917 | 44 | data-ng-disabled="!vlanDetails.provideDHCPAction.primaryRack && vlanDetails.relatedControllers.length > 1" | 44 | data-ng-disabled="!vlanDetails.provideDHCPAction.primaryRack && vlanDetails.relatedControllers.length > 1" |
918 | 45 | data-ng-model="vlanDetails.provideDHCPAction.secondaryRack" | 45 | data-ng-model="vlanDetails.provideDHCPAction.secondaryRack" |
919 | 46 | data-ng-change="vlanDetails.updateSecondaryRack()" | 46 | data-ng-change="vlanDetails.updateSecondaryRack()" |
920 | 47 | data-ng-options="rack.system_id as rack.hostname for rack in vlanDetails.relatedControllers | filter:vlanDetails.filterPrimaryRack"> | 47 | data-ng-options="rack.system_id as rack.hostname for rack in vlanDetails.relatedControllers | filter:vlanDetails.filterPrimaryRack"> |
921 | 48 | <option value="" | 48 | <option value="" |
922 | 49 | selected="selected" | 49 | selected="selected" |
924 | 50 | data-ng-show="vlanDetails.relatedControllers.length >= 2"></option> | 50 | data-ng-if="vlanDetails.relatedControllers.length >= 2"></option> |
925 | 51 | </select> | 51 | </select> |
926 | 52 | </div> | 52 | </div> |
927 | 53 | </div> | 53 | </div> |
928 | @@ -57,7 +57,7 @@ | |||
929 | 57 | <div class="form__group-input three-col"> | 57 | <div class="form__group-input three-col"> |
930 | 58 | <select name="subnet" | 58 | <select name="subnet" |
931 | 59 | data-ng-model="vlanDetails.provideDHCPAction.subnet" | 59 | data-ng-model="vlanDetails.provideDHCPAction.subnet" |
933 | 60 | data-ng-change="vlanDetails.updateSubnet()" | 60 | data-ng-change="vlanDetails.updateSubnet(false)" |
934 | 61 | data-ng-options="row.subnet.id as row.subnet.cidr for row in vlanDetails.relatedSubnets"> | 61 | data-ng-options="row.subnet.id as row.subnet.cidr for row in vlanDetails.relatedSubnets"> |
935 | 62 | <option value="" disabled="disabled" selected="selected">Choose subnet</option> | 62 | <option value="" disabled="disabled" selected="selected">Choose subnet</option> |
936 | 63 | <option value="" data-ng-if=""></option> | 63 | <option value="" data-ng-if=""></option> |
937 | @@ -65,7 +65,7 @@ | |||
938 | 65 | </div> | 65 | </div> |
939 | 66 | </div> | 66 | </div> |
940 | 67 | </fieldset> | 67 | </fieldset> |
942 | 68 | <fieldset class="form__fieldset six-col last-col" data-ng-show="vlanDetails.actionOption.name === 'enable_dhcp'"> | 68 | <fieldset class="form__fieldset six-col last-col" data-ng-if="vlanDetails.actionOption.name === 'enable_dhcp'"> |
943 | 69 | <div class="form__group" | 69 | <div class="form__group" |
944 | 70 | data-ng-hide="vlanDetails.provideDHCPAction.needsDynamicRange === false || vlanDetails.relatedSubnets.length === 0"> | 70 | data-ng-hide="vlanDetails.provideDHCPAction.needsDynamicRange === false || vlanDetails.relatedSubnets.length === 0"> |
945 | 71 | <label for="start-ip" class="form__group-label two-col">Dynamic range start IP</label> | 71 | <label for="start-ip" class="form__group-label two-col">Dynamic range start IP</label> |
946 | @@ -114,26 +114,101 @@ | |||
947 | 114 | </fieldset> | 114 | </fieldset> |
948 | 115 | </form> | 115 | </form> |
949 | 116 | </section> | 116 | </section> |
951 | 117 | <section class="page-header__section twelve-col u-margin--bottom-none" data-ng-hide="vlanDetails.isActionError()"> | 117 | <section class="page-header__section twelve-col u-margin--bottom-none" data-ng-if="vlanDetails.actionOption.name === 'relay_dhcp'"> |
952 | 118 | <h3 class="page-header__dropdown-title">Relay DHCP</h3> | ||
953 | 119 | <form class="form form--stack"> | ||
954 | 120 | <!-- This is just for visual reasons, since we need an additional border to begin the form if there is no error. --> | ||
955 | 121 | <fieldset class="form__fieldset six-col"> | ||
956 | 122 | <div class="form__group"> | ||
957 | 123 | <label for="relay_vlan" class="form__group-label two-col">Relay VLAN</label> | ||
958 | 124 | <div class="form__group-input three-col"> | ||
959 | 125 | <select name="relay_vlan" | ||
960 | 126 | data-ng-model="vlanDetails.provideDHCPAction.relayVLAN" | ||
961 | 127 | data-ng-options="vlan as vlanDetails.getFullVLANName(vlan.id) for vlan in vlanDetails.vlans | ignoreSelf:vlanDetails.vlan"> | ||
962 | 128 | <option value="" disabled="disabled" selected="selected">Choose relay VLAN</option> | ||
963 | 129 | </select> | ||
964 | 130 | </div> | ||
965 | 131 | </div> | ||
966 | 132 | <div class="form__group" | ||
967 | 133 | data-ng-hide="vlanDetails.relatedSubnets.length === 0 || (vlanDetails.provideDHCPAction.needsDynamicRange === false && vlanDetails.provideDHCPAction.needsGatewayIP === false)"> | ||
968 | 134 | <label for="subnet" class="form__group-label two-col">Subnet</label> | ||
969 | 135 | <div class="form__group-input three-col"> | ||
970 | 136 | <select name="subnet" | ||
971 | 137 | data-ng-model="vlanDetails.provideDHCPAction.subnet" | ||
972 | 138 | data-ng-change="vlanDetails.updateSubnet(true)" | ||
973 | 139 | data-ng-options="row.subnet.id as row.subnet.cidr for row in vlanDetails.relatedSubnets"> | ||
974 | 140 | <option value="" disabled="disabled" selected="selected">Choose subnet</option> | ||
975 | 141 | <option value="" data-ng-if=""></option> | ||
976 | 142 | </select> | ||
977 | 143 | </div> | ||
978 | 144 | </div> | ||
979 | 145 | </fieldset> | ||
980 | 146 | <fieldset class="form__fieldset six-col last-col"> | ||
981 | 147 | <div class="form__group" | ||
982 | 148 | data-ng-hide="vlanDetails.provideDHCPAction.needsDynamicRange === false || vlanDetails.relatedSubnets.length === 0"> | ||
983 | 149 | <label for="start-ip" class="form__group-label two-col">Dynamic range start IP</label> | ||
984 | 150 | <div class="form__group-input three-col"> | ||
985 | 151 | <input type="text" | ||
986 | 152 | name="start-ip" | ||
987 | 153 | size="39" | ||
988 | 154 | data-ng-placeholder="vlanDetails.provideDHCPAction.startPlaceholder" | ||
989 | 155 | data-ng-model="vlanDetails.provideDHCPAction.startIP" | ||
990 | 156 | data-ng-disabled="!vlanDetails.provideDHCPAction.subnet" | ||
991 | 157 | data-ng-change="vlanDetails.updateStartIP()"> | ||
992 | 158 | </div> | ||
993 | 159 | </div> | ||
994 | 160 | <div class="form__group" data-ng-hide="vlanDetails.provideDHCPAction.needsDynamicRange === false || vlanDetails.relatedSubnets.length === 0"> | ||
995 | 161 | <label for="end-ip" class="form__group-label two-col">Dynamic range end IP</label> | ||
996 | 162 | <div class="form__group-input three-col"> | ||
997 | 163 | <input type="text" | ||
998 | 164 | name="end-ip" | ||
999 | 165 | size="39" | ||
1000 | 166 | data-ng-placeholder="vlanDetails.provideDHCPAction.endPlaceholder" | ||
1001 | 167 | data-ng-model="vlanDetails.provideDHCPAction.endIP" | ||
1002 | 168 | data-ng-disabled="!vlanDetails.provideDHCPAction.subnet" | ||
1003 | 169 | data-ng-change="vlanDetails.updateEndIP()"> | ||
1004 | 170 | </div> | ||
1005 | 171 | </div> | ||
1006 | 172 | <div class="form__group" | ||
1007 | 173 | data-ng-hide="vlanDetails.provideDHCPAction.needsGatewayIP === false || vlanDetails.provideDHCPAction.subnetMissingGatewayIP === false || vlanDetails.relatedSubnets.length === 0"> | ||
1008 | 174 | <label for="gateway-ip" class="form__group-label two-col">Gateway IP</label> | ||
1009 | 175 | <div class="form__group-input three-col"> | ||
1010 | 176 | <input type="text" | ||
1011 | 177 | name="gateway-ip" | ||
1012 | 178 | size="39" | ||
1013 | 179 | data-ng-placeholder="vlanDetails.provideDHCPAction.gatewayPlaceholder" | ||
1014 | 180 | data-ng-model="vlanDetails.provideDHCPAction.gatewayIP" | ||
1015 | 181 | data-ng-disabled="!vlanDetails.provideDHCPAction.subnet" | ||
1016 | 182 | data-ng-change="vlanDetails.updatendIP()"> | ||
1017 | 183 | </div> | ||
1018 | 184 | </div> | ||
1019 | 185 | </fieldset> | ||
1020 | 186 | </form> | ||
1021 | 187 | </section> | ||
1022 | 188 | <section class="page-header__section twelve-col u-margin--bottom-none" data-ng-if="!vlanDetails.isActionError()"> | ||
1023 | 118 | <p class="page-header__message page-header__message--warning" | 189 | <p class="page-header__message page-header__message--warning" |
1025 | 119 | data-ng-show="vlanDetails.actionOption.name === 'disable_dhcp'"> | 190 | data-ng-if="vlanDetails.actionOption.name === 'disable_dhcp' && vlanDetails.vlan.dhcp_on"> |
1026 | 120 | Are you sure you want to disable DHCP on this VLAN? All subnets on this VLAN will be affected. | 191 | Are you sure you want to disable DHCP on this VLAN? All subnets on this VLAN will be affected. |
1027 | 121 | </p> | 192 | </p> |
1028 | 193 | <p class="page-header__message page-header__message--warning" | ||
1029 | 194 | data-ng-if="vlanDetails.actionOption.name === 'disable_dhcp' && vlanDetails.vlan.relay_vlan"> | ||
1030 | 195 | Are you sure you want to disable DHCP relay on this VLAN? All subnets on this VLAN will be affected. | ||
1031 | 196 | </p> | ||
1032 | 122 | <p class="page-header__message page-header__message--error" | 197 | <p class="page-header__message page-header__message--error" |
1034 | 123 | data-ng-show="vlanDetails.actionOption.name === 'enable_dhcp' && vlanDetails.relatedSubnets.length === 0"> | 198 | data-ng-if="vlanDetails.actionOption.name === 'enable_dhcp' && vlanDetails.relatedSubnets.length === 0"> |
1035 | 124 | No subnets are available on this VLAN. DHCP cannot be enabled. | 199 | No subnets are available on this VLAN. DHCP cannot be enabled. |
1036 | 125 | </p> | 200 | </p> |
1037 | 126 | <p class="page-header__message page-header__message--warning" | 201 | <p class="page-header__message page-header__message--warning" |
1039 | 127 | data-ng-show="vlanDetails.actionOption.name === 'delete'"> | 202 | data-ng-if="vlanDetails.actionOption.name === 'delete'"> |
1040 | 128 | Are you sure you want to delete this VLAN? | 203 | Are you sure you want to delete this VLAN? |
1041 | 129 | </p> | 204 | </p> |
1042 | 130 | <div class="page-header__controls"> | 205 | <div class="page-header__controls"> |
1043 | 131 | <a href="" class="button--base button--inline" data-ng-click="vlanDetails.actionCancel()">Cancel</a> | 206 | <a href="" class="button--base button--inline" data-ng-click="vlanDetails.actionCancel()">Cancel</a> |
1045 | 132 | <button class="button--primary button--inline" data-ng-click="vlanDetails.actionGo()" data-ng-disabled="vlanDetails.actionOption.name === 'enable_dhcp' && vlanDetails.relatedSubnets.length === 0">{$ vlanDetails.actionOption.title $}</button> | 207 | <button class="button--primary button--inline" data-ng-click="vlanDetails.actionGo()" data-ng-disabled="!vlanDetails.canPerformAction()">{$ vlanDetails.actionOption.title $}</button> |
1046 | 133 | </div> | 208 | </div> |
1047 | 134 | </section> | 209 | </section> |
1050 | 135 | <section class="page-header__section twelve-col u-margin--bottom-none ng-hide" | 210 | <section class="page-header__section twelve-col u-margin--bottom-none" |
1051 | 136 | data-ng-show="vlanDetails.isActionError()"> | 211 | data-ng-if="vlanDetails.isActionError()"> |
1052 | 137 | <p class="page-header__message page-header__message--error">{$ vlanDetails.actionError $}</p> | 212 | <p class="page-header__message page-header__message--error">{$ vlanDetails.actionError $}</p> |
1053 | 138 | <div class="page-header__controls"> | 213 | <div class="page-header__controls"> |
1054 | 139 | <a href="" class="button--base button--inline u-margin--right" data-ng-click="vlanDetails.actionCancel()">Cancel</a> | 214 | <a href="" class="button--base button--inline u-margin--right" data-ng-click="vlanDetails.actionCancel()">Cancel</a> |
1055 | @@ -166,17 +241,17 @@ | |||
1056 | 166 | <dd class="four-col last-col"> | 241 | <dd class="four-col last-col"> |
1057 | 167 | <a href="#/fabric/{$ vlanDetails.fabric.id $}">{$ vlanDetails.fabric.name $}</a> | 242 | <a href="#/fabric/{$ vlanDetails.fabric.id $}">{$ vlanDetails.fabric.name $}</a> |
1058 | 168 | </dd> | 243 | </dd> |
1060 | 169 | <dt class="two-col">DHCP</dt><dd class="four-col last-col">{$ vlanDetails.vlan.dhcp_on ? "Enabled" : "Disabled" $}</dd> | 244 | <dt class="two-col">DHCP</dt><dd class="four-col last-col">{$ vlanDetails.getDHCPStatus() $}</dd> |
1061 | 170 | <div data-ng-if="vlanDetails.vlan.external_dhcp"> | 245 | <div data-ng-if="vlanDetails.vlan.external_dhcp"> |
1062 | 171 | <dt class="two-col">External DHCP</dt> | 246 | <dt class="two-col">External DHCP</dt> |
1063 | 172 | <dd class="four-col last-col">{$ vlanDetails.vlan.external_dhcp $} | 247 | <dd class="four-col last-col">{$ vlanDetails.vlan.external_dhcp $} |
1064 | 173 | <i class="icon icon--warning tooltip" aria-label="Another DHCP server has been discovered on this VLAN. Enabling DHCP is not recommended."></i> | 248 | <i class="icon icon--warning tooltip" aria-label="Another DHCP server has been discovered on this VLAN. Enabling DHCP is not recommended."></i> |
1065 | 174 | </dd> | 249 | </dd> |
1066 | 175 | </div> | 250 | </div> |
1068 | 176 | <div class="ng-hide" data-ng-show="vlanDetails.primaryRack"> | 251 | <div data-ng-if="vlanDetails.primaryRack"> |
1069 | 177 | <dt class="two-col">Primary controller <span class="icon icon--help tooltip" aria-label="The rack controller where DHCP service runs on."></span></dt><dd class="four-col last-col">{$ vlanDetails.primaryRack.hostname $}</dd> | 252 | <dt class="two-col">Primary controller <span class="icon icon--help tooltip" aria-label="The rack controller where DHCP service runs on."></span></dt><dd class="four-col last-col">{$ vlanDetails.primaryRack.hostname $}</dd> |
1070 | 178 | </div> | 253 | </div> |
1072 | 179 | <div class="ng-hide" data-ng-show="vlanDetails.secondaryRack"> | 254 | <div data-ng-if="vlanDetails.secondaryRack"> |
1073 | 180 | <dt class="two-col">Secondary controller <span class="icon icon--help tooltip" aria-label="The rack controller that will take over DHCP services if the primary fails."></span></dt><dd class="four-col last-col">{$ vlanDetails.secondaryRack.hostname $}</dd> | 255 | <dt class="two-col">Secondary controller <span class="icon icon--help tooltip" aria-label="The rack controller that will take over DHCP services if the primary fails."></span></dt><dd class="four-col last-col">{$ vlanDetails.secondaryRack.hostname $}</dd> |
1074 | 181 | </div> | 256 | </div> |
1075 | 182 | <dt class="two-col">Rack controllers <span class="icon icon--help tooltip" aria-label="A rack controller controls hosts and images and runs network services
like DHCP for connected VLANs."></span></dt> | 257 | <dt class="two-col">Rack controllers <span class="icon icon--help tooltip" aria-label="A rack controller controls hosts and images and runs network services
like DHCP for connected VLANs."></span></dt> |
1076 | 183 | 258 | ||
1077 | === modified file 'src/maasserver/testing/factory.py' | |||
1078 | --- src/maasserver/testing/factory.py 2016-11-29 08:19:43 +0000 | |||
1079 | +++ src/maasserver/testing/factory.py 2016-12-06 08:04:41 +0000 | |||
1080 | @@ -1022,7 +1022,7 @@ | |||
1081 | 1022 | 1022 | ||
1082 | 1023 | def make_VLAN( | 1023 | def make_VLAN( |
1083 | 1024 | self, name=None, vid=None, fabric=None, dhcp_on=False, | 1024 | self, name=None, vid=None, fabric=None, dhcp_on=False, |
1085 | 1025 | primary_rack=None, secondary_rack=None): | 1025 | primary_rack=None, secondary_rack=None, relay_vlan=None): |
1086 | 1026 | assert vid != 0, "VID=0 VLANs are auto-created" | 1026 | assert vid != 0, "VID=0 VLANs are auto-created" |
1087 | 1027 | if fabric is None: | 1027 | if fabric is None: |
1088 | 1028 | fabric = Fabric.objects.get_default_fabric() | 1028 | fabric = Fabric.objects.get_default_fabric() |
1089 | @@ -1031,7 +1031,8 @@ | |||
1090 | 1031 | vid = self._get_available_vid(fabric) | 1031 | vid = self._get_available_vid(fabric) |
1091 | 1032 | vlan = VLAN( | 1032 | vlan = VLAN( |
1092 | 1033 | name=name, vid=vid, fabric=fabric, dhcp_on=dhcp_on, | 1033 | name=name, vid=vid, fabric=fabric, dhcp_on=dhcp_on, |
1094 | 1034 | primary_rack=primary_rack, secondary_rack=secondary_rack) | 1034 | primary_rack=primary_rack, secondary_rack=secondary_rack, |
1095 | 1035 | relay_vlan=relay_vlan) | ||
1096 | 1035 | vlan.save() | 1036 | vlan.save() |
1097 | 1036 | return vlan | 1037 | return vlan |
1098 | 1037 | 1038 | ||
1099 | 1038 | 1039 | ||
1100 | === modified file 'src/maasserver/tests/test_dhcp.py' | |||
1101 | --- src/maasserver/tests/test_dhcp.py 2016-12-03 16:33:44 +0000 | |||
1102 | +++ src/maasserver/tests/test_dhcp.py 2016-12-06 08:04:41 +0000 | |||
1103 | @@ -17,7 +17,6 @@ | |||
1104 | 17 | IPADDRESS_TYPE, | 17 | IPADDRESS_TYPE, |
1105 | 18 | SERVICE_STATUS, | 18 | SERVICE_STATUS, |
1106 | 19 | ) | 19 | ) |
1107 | 20 | from maasserver.exceptions import DHCPConfigurationError | ||
1108 | 21 | from maasserver.models import ( | 20 | from maasserver.models import ( |
1109 | 22 | Config, | 21 | Config, |
1110 | 23 | DHCPSnippet, | 22 | DHCPSnippet, |
1111 | @@ -45,7 +44,6 @@ | |||
1112 | 45 | from maastesting.twisted import ( | 44 | from maastesting.twisted import ( |
1113 | 46 | always_fail_with, | 45 | always_fail_with, |
1114 | 47 | always_succeed_with, | 46 | always_succeed_with, |
1115 | 48 | TwistedLoggerFixture, | ||
1116 | 49 | ) | 47 | ) |
1117 | 50 | from netaddr import ( | 48 | from netaddr import ( |
1118 | 51 | IPAddress, | 49 | IPAddress, |
1119 | @@ -422,8 +420,8 @@ | |||
1120 | 422 | rack_controller, vlan, subnet.get_ipnetwork().version)) | 420 | rack_controller, vlan, subnet.get_ipnetwork().version)) |
1121 | 423 | 421 | ||
1122 | 424 | 422 | ||
1125 | 425 | class TestGetManagedVLANsFor(MAASServerTestCase): | 423 | class TestGenManagedVLANsFor(MAASServerTestCase): |
1126 | 426 | """Tests for `get_managed_vlans_for`.""" | 424 | """Tests for `gen_managed_vlans_for`.""" |
1127 | 427 | 425 | ||
1128 | 428 | def test__returns_all_managed_vlans(self): | 426 | def test__returns_all_managed_vlans(self): |
1129 | 429 | rack_controller = factory.make_RackController() | 427 | rack_controller = factory.make_RackController() |
1130 | @@ -509,7 +507,31 @@ | |||
1131 | 509 | self.assertEquals({ | 507 | self.assertEquals({ |
1132 | 510 | vlan_one, | 508 | vlan_one, |
1133 | 511 | vlan_two, | 509 | vlan_two, |
1135 | 512 | }, dhcp.get_managed_vlans_for(rack_controller)) | 510 | }, set(dhcp.gen_managed_vlans_for(rack_controller))) |
1136 | 511 | |||
1137 | 512 | def test__returns_managed_vlan_with_relay_vlans(self): | ||
1138 | 513 | rack_controller = factory.make_RackController() | ||
1139 | 514 | vlan_one = factory.make_VLAN( | ||
1140 | 515 | dhcp_on=True, primary_rack=rack_controller, name="1") | ||
1141 | 516 | primary_interface = factory.make_Interface( | ||
1142 | 517 | INTERFACE_TYPE.PHYSICAL, node=rack_controller, vlan=vlan_one) | ||
1143 | 518 | managed_ipv4_subnet = factory.make_Subnet( | ||
1144 | 519 | cidr=str(factory.make_ipv4_network().cidr), vlan=vlan_one) | ||
1145 | 520 | factory.make_StaticIPAddress( | ||
1146 | 521 | alloc_type=IPADDRESS_TYPE.STICKY, subnet=managed_ipv4_subnet, | ||
1147 | 522 | interface=primary_interface) | ||
1148 | 523 | |||
1149 | 524 | # Relay VLANs atteched to the vlan. | ||
1150 | 525 | relay_vlans = { | ||
1151 | 526 | factory.make_VLAN(relay_vlan=vlan_one) | ||
1152 | 527 | for _ in range(3) | ||
1153 | 528 | } | ||
1154 | 529 | |||
1155 | 530 | # Should only contain the subnets that are managed by the rack | ||
1156 | 531 | # controller and the best interface should have been selected. | ||
1157 | 532 | self.assertEquals( | ||
1158 | 533 | relay_vlans.union(set([vlan_one])), | ||
1159 | 534 | set(dhcp.gen_managed_vlans_for(rack_controller))) | ||
1160 | 513 | 535 | ||
1161 | 514 | 536 | ||
1162 | 515 | class TestIPIsOnVLAN(MAASServerTestCase): | 537 | class TestIPIsOnVLAN(MAASServerTestCase): |
1163 | @@ -1300,31 +1322,6 @@ | |||
1164 | 1300 | class TestGetDHCPConfigureFor(MAASServerTestCase): | 1322 | class TestGetDHCPConfigureFor(MAASServerTestCase): |
1165 | 1301 | """Tests for `get_dhcp_configure_for`.""" | 1323 | """Tests for `get_dhcp_configure_for`.""" |
1166 | 1302 | 1324 | ||
1167 | 1303 | def test__raises_DHCPConfigurationError_for_ipv4(self): | ||
1168 | 1304 | primary_rack = factory.make_RackController() | ||
1169 | 1305 | secondary_rack = factory.make_RackController() | ||
1170 | 1306 | |||
1171 | 1307 | # VLAN for primary that has a secondary with multiple subnets. | ||
1172 | 1308 | ha_vlan = factory.make_VLAN( | ||
1173 | 1309 | dhcp_on=True, primary_rack=primary_rack, | ||
1174 | 1310 | secondary_rack=secondary_rack) | ||
1175 | 1311 | ha_subnet = factory.make_ipv4_Subnet_with_IPRanges(vlan=ha_vlan) | ||
1176 | 1312 | factory.make_Interface( | ||
1177 | 1313 | INTERFACE_TYPE.PHYSICAL, node=primary_rack, vlan=ha_vlan) | ||
1178 | 1314 | secondary_interface = factory.make_Interface( | ||
1179 | 1315 | INTERFACE_TYPE.PHYSICAL, node=secondary_rack, vlan=ha_vlan) | ||
1180 | 1316 | factory.make_StaticIPAddress( | ||
1181 | 1317 | alloc_type=IPADDRESS_TYPE.AUTO, subnet=ha_subnet, | ||
1182 | 1318 | interface=secondary_interface) | ||
1183 | 1319 | other_subnet = factory.make_ipv4_Subnet_with_IPRanges(vlan=ha_vlan) | ||
1184 | 1320 | |||
1185 | 1321 | ntp_servers = [factory.make_name("ntp")] | ||
1186 | 1322 | default_domain = Domain.objects.get_default_domain() | ||
1187 | 1323 | self.assertRaises( | ||
1188 | 1324 | DHCPConfigurationError, dhcp.get_dhcp_configure_for, | ||
1189 | 1325 | 4, primary_rack, ha_vlan, [ha_subnet, other_subnet], | ||
1190 | 1326 | ntp_servers, default_domain) | ||
1191 | 1327 | |||
1192 | 1328 | def test__returns_for_ipv4(self): | 1325 | def test__returns_for_ipv4(self): |
1193 | 1329 | primary_rack = factory.make_RackController() | 1326 | primary_rack = factory.make_RackController() |
1194 | 1330 | secondary_rack = factory.make_RackController() | 1327 | secondary_rack = factory.make_RackController() |
1195 | @@ -1423,36 +1420,6 @@ | |||
1196 | 1423 | dhcp.make_hosts_for_subnets([ha_subnet]), observed_hosts) | 1420 | dhcp.make_hosts_for_subnets([ha_subnet]), observed_hosts) |
1197 | 1424 | self.assertEqual(primary_interface.name, observed_interface) | 1421 | self.assertEqual(primary_interface.name, observed_interface) |
1198 | 1425 | 1422 | ||
1199 | 1426 | def test__raises_DHCPConfigurationError_for_ipv6(self): | ||
1200 | 1427 | primary_rack = factory.make_RackController() | ||
1201 | 1428 | secondary_rack = factory.make_RackController() | ||
1202 | 1429 | |||
1203 | 1430 | # VLAN for primary that has a secondary with multiple subnets. | ||
1204 | 1431 | ha_vlan = factory.make_VLAN( | ||
1205 | 1432 | dhcp_on=True, primary_rack=primary_rack, | ||
1206 | 1433 | secondary_rack=secondary_rack) | ||
1207 | 1434 | ha_subnet = factory.make_Subnet( | ||
1208 | 1435 | vlan=ha_vlan, cidr="fd38:c341:27da:c831::/64") | ||
1209 | 1436 | factory.make_IPRange( | ||
1210 | 1437 | ha_subnet, "fd38:c341:27da:c831:0:1::", | ||
1211 | 1438 | "fd38:c341:27da:c831:0:1:ffff:0") | ||
1212 | 1439 | factory.make_Interface( | ||
1213 | 1440 | INTERFACE_TYPE.PHYSICAL, node=primary_rack, vlan=ha_vlan) | ||
1214 | 1441 | secondary_interface = factory.make_Interface( | ||
1215 | 1442 | INTERFACE_TYPE.PHYSICAL, node=secondary_rack, vlan=ha_vlan) | ||
1216 | 1443 | factory.make_StaticIPAddress( | ||
1217 | 1444 | alloc_type=IPADDRESS_TYPE.AUTO, subnet=ha_subnet, | ||
1218 | 1445 | interface=secondary_interface) | ||
1219 | 1446 | other_subnet = factory.make_Subnet( | ||
1220 | 1447 | vlan=ha_vlan, cidr="fd38:c341:27da:c832::/64") | ||
1221 | 1448 | |||
1222 | 1449 | ntp_servers = [factory.make_name("ntp")] | ||
1223 | 1450 | default_domain = Domain.objects.get_default_domain() | ||
1224 | 1451 | self.assertRaises( | ||
1225 | 1452 | DHCPConfigurationError, dhcp.get_dhcp_configure_for, | ||
1226 | 1453 | 6, primary_rack, ha_vlan, [ha_subnet, other_subnet], | ||
1227 | 1454 | ntp_servers, default_domain) | ||
1228 | 1455 | |||
1229 | 1456 | def test__returns_for_ipv6(self): | 1423 | def test__returns_for_ipv6(self): |
1230 | 1457 | primary_rack = factory.make_RackController() | 1424 | primary_rack = factory.make_RackController() |
1231 | 1458 | secondary_rack = factory.make_RackController() | 1425 | secondary_rack = factory.make_RackController() |
1232 | @@ -1747,26 +1714,6 @@ | |||
1233 | 1747 | 1714 | ||
1234 | 1748 | @wait_for_reactor | 1715 | @wait_for_reactor |
1235 | 1749 | @inlineCallbacks | 1716 | @inlineCallbacks |
1236 | 1750 | def test__logs_DHCPConfigurationError_ipv4(self): | ||
1237 | 1751 | self.patch(dhcp.settings, "DHCP_CONNECT", True) | ||
1238 | 1752 | with TwistedLoggerFixture() as logger: | ||
1239 | 1753 | yield deferToDatabase( | ||
1240 | 1754 | self.create_rack_controller, missing_ipv4=True) | ||
1241 | 1755 | self.assertDocTestMatches( | ||
1242 | 1756 | "...No IPv4 interface...", logger.output) | ||
1243 | 1757 | |||
1244 | 1758 | @wait_for_reactor | ||
1245 | 1759 | @inlineCallbacks | ||
1246 | 1760 | def test__logs_DHCPConfigurationError_ipv6(self): | ||
1247 | 1761 | self.patch(dhcp.settings, "DHCP_CONNECT", True) | ||
1248 | 1762 | with TwistedLoggerFixture() as logger: | ||
1249 | 1763 | yield deferToDatabase( | ||
1250 | 1764 | self.create_rack_controller, missing_ipv6=True) | ||
1251 | 1765 | self.assertDocTestMatches( | ||
1252 | 1766 | "...No IPv6 interface...", logger.output) | ||
1253 | 1767 | |||
1254 | 1768 | @wait_for_reactor | ||
1255 | 1769 | @inlineCallbacks | ||
1256 | 1770 | def test__doesnt_call_configure_for_both_ipv4_and_ipv6(self): | 1717 | def test__doesnt_call_configure_for_both_ipv4_and_ipv6(self): |
1257 | 1771 | # ... when DHCP_CONNECT is False. | 1718 | # ... when DHCP_CONNECT is False. |
1258 | 1772 | rack_controller, config = yield deferToDatabase( | 1719 | rack_controller, config = yield deferToDatabase( |
1259 | 1773 | 1720 | ||
1260 | === modified file 'src/maasserver/tests/test_forms_vlan.py' | |||
1261 | --- src/maasserver/tests/test_forms_vlan.py 2016-04-27 20:38:06 +0000 | |||
1262 | +++ src/maasserver/tests/test_forms_vlan.py 2016-12-06 08:04:41 +0000 | |||
1263 | @@ -8,6 +8,7 @@ | |||
1264 | 8 | import random | 8 | import random |
1265 | 9 | 9 | ||
1266 | 10 | from maasserver.forms_vlan import VLANForm | 10 | from maasserver.forms_vlan import VLANForm |
1267 | 11 | from maasserver.models.fabric import Fabric | ||
1268 | 11 | from maasserver.models.vlan import DEFAULT_MTU | 12 | from maasserver.models.vlan import DEFAULT_MTU |
1269 | 12 | from maasserver.testing.factory import factory | 13 | from maasserver.testing.factory import factory |
1270 | 13 | from maasserver.testing.testcase import MAASServerTestCase | 14 | from maasserver.testing.testcase import MAASServerTestCase |
1271 | @@ -27,6 +28,27 @@ | |||
1272 | 27 | ], | 28 | ], |
1273 | 28 | }, form.errors) | 29 | }, form.errors) |
1274 | 29 | 30 | ||
1275 | 31 | def test__vlans_already_using_relay_vlan_not_shown(self): | ||
1276 | 32 | fabric = Fabric.objects.get_default_fabric() | ||
1277 | 33 | relay_vlan = factory.make_VLAN() | ||
1278 | 34 | factory.make_VLAN(relay_vlan=relay_vlan) | ||
1279 | 35 | form = VLANForm(fabric=fabric, data={}) | ||
1280 | 36 | self.assertItemsEqual( | ||
1281 | 37 | [fabric.get_default_vlan(), relay_vlan], | ||
1282 | 38 | form.fields['relay_vlan'].queryset) | ||
1283 | 39 | |||
1284 | 40 | def test__self_vlan_not_used_in_relay_vlan_field(self): | ||
1285 | 41 | fabric = Fabric.objects.get_default_fabric() | ||
1286 | 42 | relay_vlan = fabric.get_default_vlan() | ||
1287 | 43 | form = VLANForm(instance=relay_vlan, data={}) | ||
1288 | 44 | self.assertItemsEqual([], form.fields['relay_vlan'].queryset) | ||
1289 | 45 | |||
1290 | 46 | def test__no_relay_vlans_allowed_when_dhcp_on(self): | ||
1291 | 47 | vlan = factory.make_VLAN(dhcp_on=True) | ||
1292 | 48 | factory.make_VLAN() | ||
1293 | 49 | form = VLANForm(instance=vlan, data={}) | ||
1294 | 50 | self.assertItemsEqual([], form.fields['relay_vlan'].queryset) | ||
1295 | 51 | |||
1296 | 30 | def test__creates_vlan(self): | 52 | def test__creates_vlan(self): |
1297 | 31 | fabric = factory.make_Fabric() | 53 | fabric = factory.make_Fabric() |
1298 | 32 | vlan_name = factory.make_name("vlan") | 54 | vlan_name = factory.make_name("vlan") |
1299 | @@ -211,6 +233,54 @@ | |||
1300 | 211 | vlan = reload_object(vlan) | 233 | vlan = reload_object(vlan) |
1301 | 212 | self.assertTrue(vlan.dhcp_on) | 234 | self.assertTrue(vlan.dhcp_on) |
1302 | 213 | 235 | ||
1303 | 236 | def test_update_sets_relay_vlan(self): | ||
1304 | 237 | vlan = factory.make_VLAN() | ||
1305 | 238 | relay_vlan = factory.make_VLAN() | ||
1306 | 239 | form = VLANForm(instance=vlan, data={ | ||
1307 | 240 | "relay_vlan": relay_vlan.id, | ||
1308 | 241 | }) | ||
1309 | 242 | self.assertTrue(form.is_valid(), form.errors) | ||
1310 | 243 | form.save() | ||
1311 | 244 | vlan = reload_object(vlan) | ||
1312 | 245 | self.assertEquals(relay_vlan.id, vlan.relay_vlan.id) | ||
1313 | 246 | |||
1314 | 247 | def test_update_clears_relay_vlan_when_None(self): | ||
1315 | 248 | relay_vlan = factory.make_VLAN() | ||
1316 | 249 | vlan = factory.make_VLAN(relay_vlan=relay_vlan) | ||
1317 | 250 | form = VLANForm(instance=vlan, data={ | ||
1318 | 251 | "relay_vlan": None, | ||
1319 | 252 | }) | ||
1320 | 253 | self.assertTrue(form.is_valid(), form.errors) | ||
1321 | 254 | form.save() | ||
1322 | 255 | vlan = reload_object(vlan) | ||
1323 | 256 | self.assertIsNone(vlan.relay_vlan) | ||
1324 | 257 | |||
1325 | 258 | def test_update_clears_relay_vlan_when_empty(self): | ||
1326 | 259 | relay_vlan = factory.make_VLAN() | ||
1327 | 260 | vlan = factory.make_VLAN(relay_vlan=relay_vlan) | ||
1328 | 261 | form = VLANForm(instance=vlan, data={ | ||
1329 | 262 | "relay_vlan": "", | ||
1330 | 263 | }) | ||
1331 | 264 | self.assertTrue(form.is_valid(), form.errors) | ||
1332 | 265 | form.save() | ||
1333 | 266 | vlan = reload_object(vlan) | ||
1334 | 267 | self.assertIsNone(vlan.relay_vlan) | ||
1335 | 268 | |||
1336 | 269 | def test_update_disables_relay_vlan_when_dhcp_turned_on(self): | ||
1337 | 270 | relay_vlan = factory.make_VLAN() | ||
1338 | 271 | vlan = factory.make_VLAN(relay_vlan=relay_vlan) | ||
1339 | 272 | factory.make_ipv4_Subnet_with_IPRanges(vlan=vlan) | ||
1340 | 273 | rack = factory.make_RackController(vlan=vlan) | ||
1341 | 274 | vlan.primary_rack = rack | ||
1342 | 275 | vlan.save() | ||
1343 | 276 | form = VLANForm(instance=reload_object(vlan), data={ | ||
1344 | 277 | "dhcp_on": "true", | ||
1345 | 278 | }) | ||
1346 | 279 | self.assertTrue(form.is_valid(), form.errors) | ||
1347 | 280 | form.save() | ||
1348 | 281 | vlan = reload_object(vlan) | ||
1349 | 282 | self.assertIsNone(vlan.relay_vlan) | ||
1350 | 283 | |||
1351 | 214 | def test_update_validates_primary_rack_with_dhcp_on(self): | 284 | def test_update_validates_primary_rack_with_dhcp_on(self): |
1352 | 215 | vlan = factory.make_VLAN() | 285 | vlan = factory.make_VLAN() |
1353 | 216 | form = VLANForm(instance=vlan, data={ | 286 | form = VLANForm(instance=vlan, data={ |
1354 | 217 | 287 | ||
1355 | === modified file 'src/maasserver/triggers/system.py' | |||
1356 | --- src/maasserver/triggers/system.py 2016-10-12 15:26:17 +0000 | |||
1357 | +++ src/maasserver/triggers/system.py 2016-12-06 08:04:41 +0000 | |||
1358 | @@ -268,6 +268,8 @@ | |||
1359 | 268 | DHCP_VLAN_UPDATE = dedent("""\ | 268 | DHCP_VLAN_UPDATE = dedent("""\ |
1360 | 269 | CREATE OR REPLACE FUNCTION sys_dhcp_vlan_update() | 269 | CREATE OR REPLACE FUNCTION sys_dhcp_vlan_update() |
1361 | 270 | RETURNS trigger as $$ | 270 | RETURNS trigger as $$ |
1362 | 271 | DECLARE | ||
1363 | 272 | relay_vlan maasserver_vlan; | ||
1364 | 271 | BEGIN | 273 | BEGIN |
1365 | 272 | -- DHCP was turned off. | 274 | -- DHCP was turned off. |
1366 | 273 | IF OLD.dhcp_on AND NOT NEW.dhcp_on THEN | 275 | IF OLD.dhcp_on AND NOT NEW.dhcp_on THEN |
1367 | @@ -303,6 +305,60 @@ | |||
1368 | 303 | PERFORM pg_notify(CONCAT('sys_dhcp_', NEW.secondary_rack_id), ''); | 305 | PERFORM pg_notify(CONCAT('sys_dhcp_', NEW.secondary_rack_id), ''); |
1369 | 304 | END IF; | 306 | END IF; |
1370 | 305 | END IF; | 307 | END IF; |
1371 | 308 | |||
1372 | 309 | -- Relay VLAN was set when it was previously unset. | ||
1373 | 310 | IF OLD.relay_vlan_id IS NULL AND NEW.relay_vlan_id IS NOT NULL THEN | ||
1374 | 311 | SELECT maasserver_vlan.* INTO relay_vlan | ||
1375 | 312 | FROM maasserver_vlan | ||
1376 | 313 | WHERE maasserver_vlan.id = NEW.relay_vlan_id; | ||
1377 | 314 | IF relay_vlan.primary_rack_id IS NOT NULL THEN | ||
1378 | 315 | PERFORM pg_notify( | ||
1379 | 316 | CONCAT('sys_dhcp_', relay_vlan.primary_rack_id), ''); | ||
1380 | 317 | IF relay_vlan.secondary_rack_id IS NOT NULL THEN | ||
1381 | 318 | PERFORM pg_notify( | ||
1382 | 319 | CONCAT('sys_dhcp_', relay_vlan.secondary_rack_id), ''); | ||
1383 | 320 | END IF; | ||
1384 | 321 | END IF; | ||
1385 | 322 | -- Relay VLAN was unset when it was previously set. | ||
1386 | 323 | ELSIF OLD.relay_vlan_id IS NOT NULL AND NEW.relay_vlan_id IS NULL THEN | ||
1387 | 324 | SELECT maasserver_vlan.* INTO relay_vlan | ||
1388 | 325 | FROM maasserver_vlan | ||
1389 | 326 | WHERE maasserver_vlan.id = OLD.relay_vlan_id; | ||
1390 | 327 | IF relay_vlan.primary_rack_id IS NOT NULL THEN | ||
1391 | 328 | PERFORM pg_notify( | ||
1392 | 329 | CONCAT('sys_dhcp_', relay_vlan.primary_rack_id), ''); | ||
1393 | 330 | IF relay_vlan.secondary_rack_id IS NOT NULL THEN | ||
1394 | 331 | PERFORM pg_notify( | ||
1395 | 332 | CONCAT('sys_dhcp_', relay_vlan.secondary_rack_id), ''); | ||
1396 | 333 | END IF; | ||
1397 | 334 | END IF; | ||
1398 | 335 | -- Relay VLAN has changed on the VLAN. | ||
1399 | 336 | ELSIF OLD.relay_vlan_id != NEW.relay_vlan_id THEN | ||
1400 | 337 | -- Alert old VLAN if required. | ||
1401 | 338 | SELECT maasserver_vlan.* INTO relay_vlan | ||
1402 | 339 | FROM maasserver_vlan | ||
1403 | 340 | WHERE maasserver_vlan.id = OLD.relay_vlan_id; | ||
1404 | 341 | IF relay_vlan.primary_rack_id IS NOT NULL THEN | ||
1405 | 342 | PERFORM pg_notify( | ||
1406 | 343 | CONCAT('sys_dhcp_', relay_vlan.primary_rack_id), ''); | ||
1407 | 344 | IF relay_vlan.secondary_rack_id IS NOT NULL THEN | ||
1408 | 345 | PERFORM pg_notify( | ||
1409 | 346 | CONCAT('sys_dhcp_', relay_vlan.secondary_rack_id), ''); | ||
1410 | 347 | END IF; | ||
1411 | 348 | END IF; | ||
1412 | 349 | -- Alert new VLAN if required. | ||
1413 | 350 | SELECT maasserver_vlan.* INTO relay_vlan | ||
1414 | 351 | FROM maasserver_vlan | ||
1415 | 352 | WHERE maasserver_vlan.id = NEW.relay_vlan_id; | ||
1416 | 353 | IF relay_vlan.primary_rack_id IS NOT NULL THEN | ||
1417 | 354 | PERFORM pg_notify( | ||
1418 | 355 | CONCAT('sys_dhcp_', relay_vlan.primary_rack_id), ''); | ||
1419 | 356 | IF relay_vlan.secondary_rack_id IS NOT NULL THEN | ||
1420 | 357 | PERFORM pg_notify( | ||
1421 | 358 | CONCAT('sys_dhcp_', relay_vlan.secondary_rack_id), ''); | ||
1422 | 359 | END IF; | ||
1423 | 360 | END IF; | ||
1424 | 361 | END IF; | ||
1425 | 306 | RETURN NEW; | 362 | RETURN NEW; |
1426 | 307 | END; | 363 | END; |
1427 | 308 | $$ LANGUAGE plpgsql; | 364 | $$ LANGUAGE plpgsql; |
1428 | 309 | 365 | ||
1429 | === modified file 'src/maasserver/triggers/tests/test_system_listener.py' | |||
1430 | --- src/maasserver/triggers/tests/test_system_listener.py 2016-10-12 15:26:17 +0000 | |||
1431 | +++ src/maasserver/triggers/tests/test_system_listener.py 2016-12-06 08:04:41 +0000 | |||
1432 | @@ -806,6 +806,120 @@ | |||
1433 | 806 | finally: | 806 | finally: |
1434 | 807 | yield listener.stopService() | 807 | yield listener.stopService() |
1435 | 808 | 808 | ||
1436 | 809 | @wait_for_reactor | ||
1437 | 810 | @inlineCallbacks | ||
1438 | 811 | def test_sends_messages_when_relay_vlan_set(self): | ||
1439 | 812 | yield deferToDatabase(register_system_triggers) | ||
1440 | 813 | primary_rack = yield deferToDatabase(self.create_rack_controller) | ||
1441 | 814 | secondary_rack = yield deferToDatabase(self.create_rack_controller) | ||
1442 | 815 | relay_vlan = yield deferToDatabase(self.create_vlan, params={ | ||
1443 | 816 | "dhcp_on": True, | ||
1444 | 817 | "primary_rack": primary_rack, | ||
1445 | 818 | "secondary_rack": secondary_rack, | ||
1446 | 819 | }) | ||
1447 | 820 | vlan = yield deferToDatabase(self.create_vlan) | ||
1448 | 821 | primary_rack_dv = DeferredValue() | ||
1449 | 822 | secondary_rack_dv = DeferredValue() | ||
1450 | 823 | listener = self.make_listener_without_delay() | ||
1451 | 824 | listener.register( | ||
1452 | 825 | "sys_dhcp_%s" % primary_rack.id, | ||
1453 | 826 | lambda *args: primary_rack_dv.set(args)) | ||
1454 | 827 | listener.register( | ||
1455 | 828 | "sys_dhcp_%s" % secondary_rack.id, | ||
1456 | 829 | lambda *args: secondary_rack_dv.set(args)) | ||
1457 | 830 | yield listener.startService() | ||
1458 | 831 | try: | ||
1459 | 832 | yield deferToDatabase(self.update_vlan, vlan.id, { | ||
1460 | 833 | "relay_vlan": relay_vlan, | ||
1461 | 834 | }) | ||
1462 | 835 | yield primary_rack_dv.get(timeout=2) | ||
1463 | 836 | yield secondary_rack_dv.get(timeout=2) | ||
1464 | 837 | finally: | ||
1465 | 838 | yield listener.stopService() | ||
1466 | 839 | |||
1467 | 840 | @wait_for_reactor | ||
1468 | 841 | @inlineCallbacks | ||
1469 | 842 | def test_sends_messages_when_relay_vlan_unset(self): | ||
1470 | 843 | yield deferToDatabase(register_system_triggers) | ||
1471 | 844 | primary_rack = yield deferToDatabase(self.create_rack_controller) | ||
1472 | 845 | secondary_rack = yield deferToDatabase(self.create_rack_controller) | ||
1473 | 846 | relay_vlan = yield deferToDatabase(self.create_vlan, params={ | ||
1474 | 847 | "dhcp_on": True, | ||
1475 | 848 | "primary_rack": primary_rack, | ||
1476 | 849 | "secondary_rack": secondary_rack, | ||
1477 | 850 | }) | ||
1478 | 851 | vlan = yield deferToDatabase(self.create_vlan, { | ||
1479 | 852 | "relay_vlan": relay_vlan, | ||
1480 | 853 | }) | ||
1481 | 854 | primary_rack_dv = DeferredValue() | ||
1482 | 855 | secondary_rack_dv = DeferredValue() | ||
1483 | 856 | listener = self.make_listener_without_delay() | ||
1484 | 857 | listener.register( | ||
1485 | 858 | "sys_dhcp_%s" % primary_rack.id, | ||
1486 | 859 | lambda *args: primary_rack_dv.set(args)) | ||
1487 | 860 | listener.register( | ||
1488 | 861 | "sys_dhcp_%s" % secondary_rack.id, | ||
1489 | 862 | lambda *args: secondary_rack_dv.set(args)) | ||
1490 | 863 | yield listener.startService() | ||
1491 | 864 | try: | ||
1492 | 865 | yield deferToDatabase(self.update_vlan, vlan.id, { | ||
1493 | 866 | "relay_vlan": None, | ||
1494 | 867 | }) | ||
1495 | 868 | yield primary_rack_dv.get(timeout=2) | ||
1496 | 869 | yield secondary_rack_dv.get(timeout=2) | ||
1497 | 870 | finally: | ||
1498 | 871 | yield listener.stopService() | ||
1499 | 872 | |||
1500 | 873 | @wait_for_reactor | ||
1501 | 874 | @inlineCallbacks | ||
1502 | 875 | def test_sends_messages_when_relay_vlan_changed(self): | ||
1503 | 876 | yield deferToDatabase(register_system_triggers) | ||
1504 | 877 | old_primary_rack = yield deferToDatabase(self.create_rack_controller) | ||
1505 | 878 | old_secondary_rack = yield deferToDatabase(self.create_rack_controller) | ||
1506 | 879 | old_relay_vlan = yield deferToDatabase(self.create_vlan, params={ | ||
1507 | 880 | "dhcp_on": True, | ||
1508 | 881 | "primary_rack": old_primary_rack, | ||
1509 | 882 | "secondary_rack": old_secondary_rack, | ||
1510 | 883 | }) | ||
1511 | 884 | new_primary_rack = yield deferToDatabase(self.create_rack_controller) | ||
1512 | 885 | new_secondary_rack = yield deferToDatabase(self.create_rack_controller) | ||
1513 | 886 | new_relay_vlan = yield deferToDatabase(self.create_vlan, params={ | ||
1514 | 887 | "dhcp_on": True, | ||
1515 | 888 | "primary_rack": new_primary_rack, | ||
1516 | 889 | "secondary_rack": new_secondary_rack, | ||
1517 | 890 | }) | ||
1518 | 891 | vlan = yield deferToDatabase(self.create_vlan, { | ||
1519 | 892 | "relay_vlan": old_relay_vlan, | ||
1520 | 893 | }) | ||
1521 | 894 | old_primary_rack_dv = DeferredValue() | ||
1522 | 895 | old_secondary_rack_dv = DeferredValue() | ||
1523 | 896 | new_primary_rack_dv = DeferredValue() | ||
1524 | 897 | new_secondary_rack_dv = DeferredValue() | ||
1525 | 898 | listener = self.make_listener_without_delay() | ||
1526 | 899 | listener.register( | ||
1527 | 900 | "sys_dhcp_%s" % old_primary_rack.id, | ||
1528 | 901 | lambda *args: old_primary_rack_dv.set(args)) | ||
1529 | 902 | listener.register( | ||
1530 | 903 | "sys_dhcp_%s" % old_secondary_rack.id, | ||
1531 | 904 | lambda *args: old_secondary_rack_dv.set(args)) | ||
1532 | 905 | listener.register( | ||
1533 | 906 | "sys_dhcp_%s" % new_primary_rack.id, | ||
1534 | 907 | lambda *args: new_primary_rack_dv.set(args)) | ||
1535 | 908 | listener.register( | ||
1536 | 909 | "sys_dhcp_%s" % new_secondary_rack.id, | ||
1537 | 910 | lambda *args: new_secondary_rack_dv.set(args)) | ||
1538 | 911 | yield listener.startService() | ||
1539 | 912 | try: | ||
1540 | 913 | yield deferToDatabase(self.update_vlan, vlan.id, { | ||
1541 | 914 | "relay_vlan": new_relay_vlan, | ||
1542 | 915 | }) | ||
1543 | 916 | yield old_primary_rack_dv.get(timeout=2) | ||
1544 | 917 | yield old_secondary_rack_dv.get(timeout=2) | ||
1545 | 918 | yield new_primary_rack_dv.get(timeout=2) | ||
1546 | 919 | yield new_secondary_rack_dv.get(timeout=2) | ||
1547 | 920 | finally: | ||
1548 | 921 | yield listener.stopService() | ||
1549 | 922 | |||
1550 | 809 | 923 | ||
1551 | 810 | class TestDHCPSubnetListener( | 924 | class TestDHCPSubnetListener( |
1552 | 811 | MAASTransactionServerTestCase, TransactionalHelpersMixin): | 925 | MAASTransactionServerTestCase, TransactionalHelpersMixin): |
1553 | 812 | 926 | ||
1554 | === modified file 'src/maasserver/websockets/handlers/tests/test_vlan.py' | |||
1555 | --- src/maasserver/websockets/handlers/tests/test_vlan.py 2016-10-19 18:06:01 +0000 | |||
1556 | +++ src/maasserver/websockets/handlers/tests/test_vlan.py 2016-12-06 08:04:41 +0000 | |||
1557 | @@ -42,6 +42,7 @@ | |||
1558 | 42 | "external_dhcp": vlan.external_dhcp, | 42 | "external_dhcp": vlan.external_dhcp, |
1559 | 43 | "primary_rack": vlan.primary_rack, | 43 | "primary_rack": vlan.primary_rack, |
1560 | 44 | "secondary_rack": vlan.secondary_rack, | 44 | "secondary_rack": vlan.secondary_rack, |
1561 | 45 | "relay_vlan": vlan.relay_vlan_id, | ||
1562 | 45 | } | 46 | } |
1563 | 46 | data['rack_sids'] = sorted(list({ | 47 | data['rack_sids'] = sorted(list({ |
1564 | 47 | interface.node.system_id | 48 | interface.node.system_id |
1565 | @@ -206,6 +207,20 @@ | |||
1566 | 206 | self.assertThat(vlan.primary_rack, Is(None)) | 207 | self.assertThat(vlan.primary_rack, Is(None)) |
1567 | 207 | self.assertThat(vlan.secondary_rack, Is(None)) | 208 | self.assertThat(vlan.secondary_rack, Is(None)) |
1568 | 208 | 209 | ||
1569 | 210 | def test__configure_dhcp_with_relay_vlan(self): | ||
1570 | 211 | user = factory.make_admin() | ||
1571 | 212 | handler = VLANHandler(user, {}) | ||
1572 | 213 | vlan = factory.make_VLAN() | ||
1573 | 214 | relay_vlan = factory.make_VLAN() | ||
1574 | 215 | handler.configure_dhcp({ | ||
1575 | 216 | "id": vlan.id, | ||
1576 | 217 | "controllers": [], | ||
1577 | 218 | "relay_vlan": relay_vlan.id, | ||
1578 | 219 | }) | ||
1579 | 220 | vlan = reload_object(vlan) | ||
1580 | 221 | self.assertThat(vlan.dhcp_on, Equals(False)) | ||
1581 | 222 | self.assertThat(vlan.relay_vlan, Equals(relay_vlan)) | ||
1582 | 223 | |||
1583 | 209 | def test__non_superuser_asserts(self): | 224 | def test__non_superuser_asserts(self): |
1584 | 210 | user = factory.make_User() | 225 | user = factory.make_User() |
1585 | 211 | handler = VLANHandler(user, {}) | 226 | handler = VLANHandler(user, {}) |
1586 | 212 | 227 | ||
1587 | === modified file 'src/maasserver/websockets/handlers/vlan.py' | |||
1588 | --- src/maasserver/websockets/handlers/vlan.py 2016-10-20 19:39:48 +0000 | |||
1589 | +++ src/maasserver/websockets/handlers/vlan.py 2016-12-06 08:04:41 +0000 | |||
1590 | @@ -104,6 +104,10 @@ | |||
1591 | 104 | NODE_PERMISSION.ADMIN, vlan), "Permission denied." | 104 | NODE_PERMISSION.ADMIN, vlan), "Permission denied." |
1592 | 105 | vlan.delete() | 105 | vlan.delete() |
1593 | 106 | 106 | ||
1594 | 107 | def update(self, parameters): | ||
1595 | 108 | """Delete this VLAN.""" | ||
1596 | 109 | return super(VLANHandler, self).update(parameters) | ||
1597 | 110 | |||
1598 | 107 | def _configure_iprange_and_gateway(self, parameters): | 111 | def _configure_iprange_and_gateway(self, parameters): |
1599 | 108 | if 'subnet' in parameters and parameters['subnet'] is not None: | 112 | if 'subnet' in parameters and parameters['subnet'] is not None: |
1600 | 109 | subnet = Subnet.objects.get(id=parameters['subnet']) | 113 | subnet = Subnet.objects.get(id=parameters['subnet']) |
1601 | @@ -171,18 +175,21 @@ | |||
1602 | 171 | # of parameters, to prevent spurious log statements. | 175 | # of parameters, to prevent spurious log statements. |
1603 | 172 | if 'extra' in parameters: | 176 | if 'extra' in parameters: |
1604 | 173 | self._configure_iprange_and_gateway(parameters['extra']) | 177 | self._configure_iprange_and_gateway(parameters['extra']) |
1611 | 174 | iprange_count = IPRange.objects.filter( | 178 | if 'relay_vlan' not in parameters: |
1612 | 175 | type=IPRANGE_TYPE.DYNAMIC, subnet__vlan=vlan).count() | 179 | iprange_count = IPRange.objects.filter( |
1613 | 176 | if iprange_count == 0: | 180 | type=IPRANGE_TYPE.DYNAMIC, subnet__vlan=vlan).count() |
1614 | 177 | raise ValueError( | 181 | if iprange_count == 0: |
1615 | 178 | "Cannot configure DHCP: At least one dynamic range is " | 182 | raise ValueError( |
1616 | 179 | "required.") | 183 | "Cannot configure DHCP: At least one dynamic range is " |
1617 | 184 | "required.") | ||
1618 | 180 | controllers = parameters.get('controllers', []) | 185 | controllers = parameters.get('controllers', []) |
1619 | 181 | data = { | 186 | data = { |
1620 | 182 | "dhcp_on": True if len(controllers) > 0 else False, | 187 | "dhcp_on": True if len(controllers) > 0 else False, |
1621 | 183 | "primary_rack": controllers[0] if len(controllers) > 0 else None, | 188 | "primary_rack": controllers[0] if len(controllers) > 0 else None, |
1622 | 184 | "secondary_rack": controllers[1] if len(controllers) > 1 else None, | 189 | "secondary_rack": controllers[1] if len(controllers) > 1 else None, |
1623 | 185 | } | 190 | } |
1624 | 191 | if 'relay_vlan' in parameters: | ||
1625 | 192 | data['relay_vlan'] = parameters['relay_vlan'] | ||
1626 | 186 | form = VLANForm(instance=vlan, data=data) | 193 | form = VLANForm(instance=vlan, data=data) |
1627 | 187 | if form.is_valid(): | 194 | if form.is_valid(): |
1628 | 188 | form.save() | 195 | form.save() |
I have tested this and the dhcpd.conf is getting written correctly. Still waiting on review from design before landing.