204
194
return receive_offers(transaction_id)
207
def process_request(client_func, *args, **kwargs):
208
"""Run a MAASClient query and check for common errors.
210
:return: None if there is an error, otherwise the decoded response body.
213
response = client_func(*args, **kwargs)
214
except (HTTPError, URLError) as e:
215
maaslog.warning("Failed to contact region controller:\n%s", e)
217
code = response.getcode()
218
if code != http.client.OK:
220
"Failed talking to region controller, it returned:\n%s\n%s",
221
code, response.read())
224
raw_data = response.read()
225
if len(raw_data) > 0:
226
data = json.loads(raw_data)
229
except ValueError as e:
231
"Failed to decode response from region controller:\n%s", e)
236
def determine_cluster_interfaces(knowledge):
237
"""Given server knowledge, determine network interfaces on this cluster.
239
:return: a list of tuples of (interface name, ip) for all interfaces.
241
:note: this uses an API call and not local probing because the
242
region controller has the definitive and final say in what does and
246
'api/2.0/nodegroups/%s/interfaces/' % knowledge['nodegroup_uuid'])
247
oauth = MAASOAuth(*knowledge['api_credentials'])
248
client = MAASClient(oauth, MAASDispatcher(), knowledge['maas_url'])
249
interfaces = process_request(client.get, api_path, 'list')
250
if interfaces is None:
253
interface_names = sorted(
254
(interface['interface'], interface['ip'])
255
for interface in interfaces
256
if interface['interface'] != '')
257
return interface_names
260
def probe_interface(interface, ip):
197
def probe_interface(interface):
261
198
"""Probe the given interface for DHCP servers.
263
:param interface: interface as returned from determine_cluster_interfaces
264
:param ip: ip as returned from determine_cluster_interfaces
200
:param interface: interface name
265
201
:return: A set of IP addresses of detected servers.
267
203
:note: Any servers running on the IP address of the local host are
287
223
"your cluster interfaces configuration.", interface)
290
# Using servers.discard(ip) here breaks Mock in the tests, so
291
# we're creating a copy of the set instead.
292
results = servers.difference([ip])
296
def update_region_controller(knowledge, interface, server):
297
"""Update the region controller with the status of the probe.
299
:param knowledge: dictionary of server info
300
:param interface: name of interface, e.g. eth0
301
:param server: IP address of detected DHCP server, or None
303
api_path = 'api/2.0/nodegroups/%s/interfaces/%s/' % (
304
knowledge['nodegroup_uuid'], interface)
305
oauth = MAASOAuth(*knowledge['api_credentials'])
306
client = MAASClient(oauth, MAASDispatcher(), knowledge['maas_url'])
310
client.post, api_path, 'report_foreign_dhcp', foreign_dhcp_ip=server)
313
def periodic_probe_task(api_knowledge):
314
"""Probe for DHCP servers and set NodeGroupInterface.foriegn_dhcp.
316
This should be run periodically so that the database has an up-to-date
317
view of any rogue DHCP servers on the network.
319
NOTE: This uses blocking I/O with sequential polling of interfaces, and
320
hence doesn't scale well. It's a future improvement to make
321
to throw it in parallel threads or async I/O.
323
:param api_knowledge: A dict of the information needed to be able to
324
make requests to the region's REST API.
326
# Determine all the active interfaces on this cluster (nodegroup).
327
interfaces = determine_cluster_interfaces(api_knowledge)
328
if interfaces is None:
329
maaslog.info("No interfaces on cluster, not probing DHCP.")
332
# Iterate over interfaces and probe each one.
333
for interface, ip in interfaces:
335
servers = probe_interface(interface, ip)
338
"Failed to probe sockets; did you configure authbind as per "
343
# Only send one, if it gets cleared out then the
344
# next detection pass will send a different one, if it
346
update_region_controller(
347
api_knowledge, interface, servers.pop())
349
update_region_controller(api_knowledge, interface, None)