diff --git a/pscheduler-server/pscheduler-server/api-server/pschedulerapiserver/admin.py b/pscheduler-server/pscheduler-server/api-server/pschedulerapiserver/admin.py index e03d5db60..f4b487d57 100644 --- a/pscheduler-server/pscheduler-server/api-server/pschedulerapiserver/admin.py +++ b/pscheduler-server/pscheduler-server/api-server/pschedulerapiserver/admin.py @@ -22,8 +22,8 @@ @application.route("/", methods=['GET']) def root(): - return ok_json("This is the pScheduler API server on %s (%s)." - % (server_hostname(), pscheduler.api_local_host_name()), + return ok_json('This is the pScheduler API server' + f' on {server_hostname()} ({socket.gethostname()})', sanitize=False) diff --git a/python-pscheduler/pscheduler/pscheduler/api.py b/python-pscheduler/pscheduler/pscheduler/api.py index 3abcde3c1..cf830872e 100644 --- a/python-pscheduler/pscheduler/pscheduler/api.py +++ b/python-pscheduler/pscheduler/pscheduler/api.py @@ -4,7 +4,10 @@ import multiprocessing import multiprocessing.dummy +import netaddr +import netifaces import os +import re import socket import threading import urllib @@ -23,9 +26,53 @@ def api_local_host(): "Return a hostname and, optionally, a port that should point to the server on this host." return os.environ.get("PSCHEDULER_LOCALHOST", api_local_host_name()) + def api_local_host_name(): - "Return the local system's hostname" - return socket.gethostname() + """ + Return a host name or IP suitable for reaching the local system + """ + + # Try the system's hostname. This shoud cover most cases. + + hostname = socket.gethostname() + try: + socket.gethostbyname(hostname) + # Local host name resolves. Use that. + return hostname + except socket.gaierror: + # Local host name didn't resolve. Do what's next. + pass + + # Enumerate the interface addresses on the system and pick the + # first one, preferring loopbacks and IPv6. This is based loosely + # on the code in pScheduler's LocalIPList class and is duplicated + # here so it can be used elsewhere without the rest of the + # pScehduler library. + + if_regex = r'%.*$' + + addresses = [] + + # Netifaces returns a very deep structure. + for ifhash in [ netifaces.ifaddresses(iface) + for iface in netifaces.interfaces() ]: + for afamily in ifhash: + for iface in ifhash[afamily]: + address = re.sub(if_regex, '', iface["addr"]) + try: + addresses.append(netaddr.IPAddress(address)) + except netaddr.core.AddrFormatError: + # Don't care about things that don't look like IPS. + pass + + addresses = sorted(addresses, + key=lambda v: [v.is_loopback(), v.version, v.sort_key()], + reverse=True) + try: + return str(addresses[0]) + except IndexError: + raise RuntimeError('No interfaces found on this system') + def __host_per_rfc_2732(host): "Format a host name or IP for a URL according to RFC 2732" diff --git a/python-pscheduler/pscheduler/tests/api_test.py b/python-pscheduler/pscheduler/tests/api_test.py index d2a3014a0..05aa63904 100644 --- a/python-pscheduler/pscheduler/tests/api_test.py +++ b/python-pscheduler/pscheduler/tests/api_test.py @@ -3,6 +3,7 @@ test for the api module. """ +import socket import unittest from pscheduler.api import * @@ -15,6 +16,15 @@ class TestApi(PschedTestBase): Api tests. """ + + def test_local_host(self): + self.assertTrue(api_local_host_name() in [ + socket.gethostname(), + '::1', + '127.0.0.1' + ]) + + def test_api(self): """taken from api.__main__"""