Merge ~mpontillo/maas:alternate-ntp-servers-in-dhcp--bug-1753496 into maas:master

Proposed by Mike Pontillo
Status: Merged
Approved by: Mike Pontillo
Approved revision: f6eb1e941449439156745339969bb0a144983b50
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~mpontillo/maas:alternate-ntp-servers-in-dhcp--bug-1753496
Merge into: maas:master
Diff against target: 183 lines (+80/-28)
2 files modified
src/maasserver/dhcp.py (+49/-26)
src/maasserver/tests/test_dhcp.py (+31/-2)
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Approve
MAAS Lander Needs Fixing
Review via email: mp+343152@code.launchpad.net

Commit message

LP: #1753496 - Add NTP server for peer rack, if appropriate.

To post a comment you must log in.
Revision history for this message
Paul Gear (paulgear) wrote :

I'm not familiar with how racks, etc. are represented within the MAAS code, but will this produce a list of *all* NTP servers available in the region, or just one additional from a secondary rack?

NTP best practice is to have at least 4 servers, if possible: https://tools.ietf.org/html/draft-ietf-ntp-bcp-06#section-4.1, so if MAAS knows about more NTP servers, I think it should add them all.

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

Machines deployed by MAAS only have access to the rack controllers for DHCP (unless they are region/racks). In HA configurations we only have 2 rack controllers, and such we would have 2 NTP servers for NTP.

That said, these changes here are only apply to DHCP. MAAS only relies on DHCP for commissioning or ephemeral environments. For deployments the machines get statically configured from a different code path that gathers all NTP servers in the specific VLAN.

Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b alternate-ntp-servers-in-dhcp--bug-1753496 lp:~mpontillo/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/2454/console
COMMIT: b49877a5bf10fbbeb913f8bc7b258a6a5340b56a

review: Needs Fixing
f6eb1e9... by Mike Pontillo

Fix lint.

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

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/dhcp.py b/src/maasserver/dhcp.py
2index 1d07cc0..d98134c 100644
3--- a/src/maasserver/dhcp.py
4+++ b/src/maasserver/dhcp.py
5@@ -398,7 +398,7 @@ def make_pools_for_subnet(subnet, failover_peer=None):
6 def make_subnet_config(
7 rack_controller, subnet, default_dns_servers: Optional[list],
8 ntp_servers: Union[list, dict], default_domain, search_list=None,
9- failover_peer=None, subnets_dhcp_snippets: list=None):
10+ failover_peer=None, subnets_dhcp_snippets: list=None, peer_rack=None):
11 """Return DHCP subnet configuration dict for a rack interface.
12
13 :param ntp_servers: Either a list of NTP server addresses or hostnames to
14@@ -416,15 +416,6 @@ def make_subnet_config(
15 if subnets_dhcp_snippets is None:
16 subnets_dhcp_snippets = []
17
18- if isinstance(ntp_servers, dict):
19- try:
20- ntp_server = ntp_servers[
21- subnet.vlan.space_id, subnet.get_ip_version()]
22- except KeyError:
23- ntp_servers = []
24- else:
25- ntp_servers = [ntp_server]
26-
27 subnet_config = {
28 'subnet': str(ip_network.network),
29 'subnet_mask': str(ip_network.netmask),
30@@ -434,7 +425,7 @@ def make_subnet_config(
31 '' if not subnet.gateway_ip
32 else str(subnet.gateway_ip)),
33 'dns_servers': dns_servers,
34- 'ntp_servers': ntp_servers,
35+ 'ntp_servers': get_ntp_servers(ntp_servers, subnet, peer_rack),
36 'domain_name': default_domain.name,
37 'pools': make_pools_for_subnet(subnet, failover_peer),
38 'dhcp_snippets': [
39@@ -454,18 +445,47 @@ def make_failover_peer_config(vlan, rack_controller):
40 interface_ip_address = get_ip_address_for_rack_controller(
41 rack_controller, vlan)
42 if is_primary:
43- peer_address = get_ip_address_for_rack_controller(
44- vlan.secondary_rack, vlan)
45+ peer_rack = vlan.secondary_rack
46 else:
47- peer_address = get_ip_address_for_rack_controller(
48- vlan.primary_rack, vlan)
49+ peer_rack = vlan.primary_rack
50+ peer_address = get_ip_address_for_rack_controller(peer_rack, vlan)
51 name = "failover-vlan-%d" % vlan.id
52- return name, {
53- "name": name,
54- "mode": "primary" if is_primary else "secondary",
55- "address": str(interface_ip_address.ip),
56- "peer_address": str(peer_address.ip),
57- }
58+ return (
59+ name,
60+ {
61+ "name": name,
62+ "mode": "primary" if is_primary else "secondary",
63+ "address": str(interface_ip_address.ip),
64+ "peer_address": str(peer_address.ip),
65+ },
66+ peer_rack
67+ )
68+
69+
70+def get_ntp_servers(ntp_servers, subnet, peer_rack):
71+ """Return the list of NTP servers, based on the initial input list of
72+ servers or dictionary, the subnet the servers will be advertised on,
73+ and the peer rack controller (if present).
74+ """
75+ if isinstance(ntp_servers, dict):
76+ # If ntp_servers is a dict, that means it maps each
77+ # (space_id, address_family) to the best NTP server for that space.
78+ # If it's already a list, that means we're just using the external
79+ # NTP server(s).
80+ space_address_family = (subnet.vlan.space_id, subnet.get_ip_version())
81+ ntp_server = ntp_servers.get(space_address_family)
82+ if ntp_server is None:
83+ return []
84+ else:
85+ if peer_rack is not None:
86+ alternates = get_ntp_server_addresses_for_rack(peer_rack)
87+ alternate_ntp_server = alternates.get(space_address_family)
88+ if alternate_ntp_server is not None:
89+ return [ntp_server, alternate_ntp_server]
90+ return [ntp_server]
91+ else:
92+ # Return the original input; it was already a list.
93+ return ntp_servers
94
95
96 @typed
97@@ -487,12 +507,14 @@ def get_dhcp_configure_for(
98 rack_controller, vlan, ip_version)
99 interface = get_best_interface(interfaces)
100
101- # Generate the failover peer for this VLAN.
102- if vlan.secondary_rack_id is not None:
103- peer_name, peer_config = make_failover_peer_config(
104+ has_secondary = vlan.secondary_rack_id is not None
105+
106+ if has_secondary:
107+ # Generate the failover peer for this VLAN.
108+ peer_name, peer_config, peer_rack = make_failover_peer_config(
109 vlan, rack_controller)
110 else:
111- peer_name, peer_config = None, None
112+ peer_name, peer_config, peer_rack = None, None, None
113
114 if dhcp_snippets is None:
115 dhcp_snippets = []
116@@ -510,7 +532,8 @@ def get_dhcp_configure_for(
117 subnet_configs.append(
118 make_subnet_config(
119 rack_controller, subnet, maas_dns_servers, ntp_servers,
120- domain, search_list, peer_name, subnets_dhcp_snippets))
121+ domain, search_list, peer_name, subnets_dhcp_snippets,
122+ peer_rack))
123
124 # Generate the hosts for all subnets.
125 hosts = make_hosts_for_subnets(subnets, nodes_dhcp_snippets)
126diff --git a/src/maasserver/tests/test_dhcp.py b/src/maasserver/tests/test_dhcp.py
127index 5680ae7..ea601bd 100644
128--- a/src/maasserver/tests/test_dhcp.py
129+++ b/src/maasserver/tests/test_dhcp.py
130@@ -897,6 +897,35 @@ class TestMakeSubnetConfig(MAASServerTestCase):
131 rack_controller, subnet, [""], ntp_servers, default_domain)
132 self.expectThat(config['ntp_servers'], Equals([address.ip]))
133
134+ def test__sets_ntp_from_dict_argument_with_alternates(self):
135+ r1 = factory.make_RackController(interface=False)
136+ r2 = factory.make_RackController(interface=False)
137+ vlan = factory.make_VLAN(primary_rack=r1, secondary_rack=r2)
138+ subnet = factory.make_Subnet(vlan=vlan, dns_servers=[], space=None)
139+ r1_eth0 = factory.make_Interface(
140+ INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=r1)
141+ r2_eth0 = factory.make_Interface(
142+ INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=r2)
143+ a1 = factory.make_StaticIPAddress(
144+ interface=r1_eth0, subnet=subnet,
145+ alloc_type=IPADDRESS_TYPE.STICKY)
146+ a2 = factory.make_StaticIPAddress(
147+ interface=r2_eth0, subnet=subnet,
148+ alloc_type=IPADDRESS_TYPE.STICKY)
149+ r1_ntp_servers = {
150+ (vlan.space_id, subnet.get_ipnetwork().version): a1.ip,
151+ }
152+ r2_ntp_servers = {
153+ (vlan.space_id, subnet.get_ipnetwork().version): a2.ip,
154+ }
155+ default_domain = Domain.objects.get_default_domain()
156+ config = dhcp.make_subnet_config(
157+ r1, subnet, [""], r1_ntp_servers, default_domain, peer_rack=r2)
158+ self.expectThat(config['ntp_servers'], Equals([a1.ip, a2.ip]))
159+ config = dhcp.make_subnet_config(
160+ r2, subnet, [""], r2_ntp_servers, default_domain, peer_rack=r1)
161+ self.expectThat(config['ntp_servers'], Equals([a2.ip, a1.ip]))
162+
163 def test__overrides_ipv4_dns_from_subnet(self):
164 rack_controller = factory.make_RackController(interface=False)
165 vlan = factory.make_VLAN()
166@@ -1351,7 +1380,7 @@ class TestMakeFailoverPeerConfig(MAASServerTestCase):
167 "mode": "primary",
168 "address": str(primary_ip.ip),
169 "peer_address": str(secondary_ip.ip),
170- }), dhcp.make_failover_peer_config(vlan, primary_rack))
171+ }, secondary_rack), dhcp.make_failover_peer_config(vlan, primary_rack))
172
173 def test__renders_config_for_secondary(self):
174 primary_rack = factory.make_RackController()
175@@ -1376,7 +1405,7 @@ class TestMakeFailoverPeerConfig(MAASServerTestCase):
176 "mode": "secondary",
177 "address": str(secondary_ip.ip),
178 "peer_address": str(primary_ip.ip),
179- }), dhcp.make_failover_peer_config(vlan, secondary_rack))
180+ }, primary_rack), dhcp.make_failover_peer_config(vlan, secondary_rack))
181
182
183 class TestGetDHCPConfigureFor(MAASServerTestCase):

Subscribers

People subscribed via source and target branches