diff --git a/designate/agent/__init__.py b/designate/agent/__init__.py index 041fa196..11fa753e 100644 --- a/designate/agent/__init__.py +++ b/designate/agent/__init__.py @@ -24,10 +24,17 @@ OPTS = [ help='Number of agent worker processes to spawn'), cfg.IntOpt('threads', default=1000, help='Number of agent greenthreads to spawn'), - cfg.IPOpt('host', default='0.0.0.0', - help='The host for the Agent to bind to'), - cfg.PortOpt('port', default=5358, - help='The port for the Agent to bind to'), + cfg.IPOpt('host', + deprecated_for_removal=True, + deprecated_reason="Replaced by 'listen' option", + help='Agent Bind Host'), + cfg.PortOpt('port', + deprecated_for_removal=True, + deprecated_reason="Replaced by 'listen' option", + help='Agent Port Number'), + cfg.ListOpt('listen', + default=['0.0.0.0:5358'], + help='Agent host:port pairs to listen on'), cfg.IntOpt('tcp-backlog', default=100, help='The Agent TCP Backlog'), cfg.FloatOpt('tcp-recv-timeout', default=0.5, diff --git a/designate/agent/service.py b/designate/agent/service.py index 5346a80b..503d40cc 100644 --- a/designate/agent/service.py +++ b/designate/agent/service.py @@ -37,6 +37,7 @@ CONF = cfg.CONF class Service(service.DNSService, service.Service): + _dns_default_port = 5358 def __init__(self, threads=None): super(Service, self).__init__(threads=threads) diff --git a/designate/api/__init__.py b/designate/api/__init__.py index 2a4e8127..011a5557 100644 --- a/designate/api/__init__.py +++ b/designate/api/__init__.py @@ -28,10 +28,17 @@ cfg.CONF.register_opts([ cfg.BoolOpt('enable-host-header', default=False, help='Enable host request headers'), cfg.StrOpt('api-base-uri', default='http://127.0.0.1:9001/'), - cfg.IPOpt('api_host', default='0.0.0.0', - help='API Host'), - cfg.PortOpt('api_port', default=9001, + cfg.IPOpt('api_host', + deprecated_for_removal=True, + deprecated_reason="Replaced by 'listen' option", + help='API Bind Host'), + cfg.PortOpt('api_port', + deprecated_for_removal=True, + deprecated_reason="Replaced by 'listen' option", help='API Port Number'), + cfg.ListOpt('listen', + default=['0.0.0.0:9001'], + help='API host:port pairs to listen on'), cfg.StrOpt('api_paste_config', default='api-paste.ini', help='File name for the paste.deploy config for designate-api'), cfg.StrOpt('auth_strategy', default='keystone', diff --git a/designate/mdns/__init__.py b/designate/mdns/__init__.py index 76f40f3d..775e1197 100644 --- a/designate/mdns/__init__.py +++ b/designate/mdns/__init__.py @@ -27,10 +27,17 @@ OPTS = [ help='Number of mdns worker processes to spawn'), cfg.IntOpt('threads', default=1000, help='Number of mdns greenthreads to spawn'), - cfg.IPOpt('host', default='0.0.0.0', + cfg.IPOpt('host', + deprecated_for_removal=True, + deprecated_reason="Replaced by 'listen' option", help='mDNS Bind Host'), - cfg.PortOpt('port', default=5354, + cfg.PortOpt('port', + deprecated_for_removal=True, + deprecated_reason="Replaced by 'listen' option", help='mDNS Port Number'), + cfg.ListOpt('listen', + default=['0.0.0.0:5354'], + help='mDNS host:port pairs to listen on'), cfg.IntOpt('tcp-backlog', default=100, help='mDNS TCP Backlog'), cfg.FloatOpt('tcp-recv-timeout', default=0.5, diff --git a/designate/mdns/service.py b/designate/mdns/service.py index 0449a837..08f3a026 100644 --- a/designate/mdns/service.py +++ b/designate/mdns/service.py @@ -29,6 +29,7 @@ CONF = cfg.CONF class Service(service.DNSService, service.RPCService, service.Service): + _dns_default_port = 5354 @property def storage(self): diff --git a/designate/service.py b/designate/service.py index de4f70a0..8bca146d 100644 --- a/designate/service.py +++ b/designate/service.py @@ -97,6 +97,43 @@ class Service(service.Service): super(Service, self).stop() + def _get_listen_on_addresses(self, default_port): + """ + Helper Method to handle migration from singular host/port to + multiple binds + """ + try: + # The API service uses "api_host", and "api_port", others use + # just host and port. + host = self._service_config.api_host + port = self._service_config.api_port + + except cfg.NoSuchOptError: + host = self._service_config.host + port = self._service_config.port + + if host or port: + LOG.warning(_LW("host and port config options used, the 'listen' " + "option has been ignored")) + + host = host or "0.0.0.0" + port = port or default_port + + return [(host, port)] + + else: + def _split_host_port(l): + try: + host, port = l.split(':', 1) + return host, int(port) + except ValueError: + LOG.exception(_LE('Invalid ip:port pair: %s'), l) + raise + + # Convert listen pair list to a set, to remove accidental + # duplicates. + return map(_split_host_port, set(self._service_config.listen)) + class RPCService(object): """ @@ -180,6 +217,8 @@ class WSGIService(object): def __init__(self, *args, **kwargs): super(WSGIService, self).__init__(*args, **kwargs) + self._wsgi_socks = [] + @abc.abstractproperty def _wsgi_application(self): pass @@ -187,23 +226,28 @@ class WSGIService(object): def start(self): super(WSGIService, self).start() - self._wsgi_sock = utils.bind_tcp( - self._service_config.api_host, - self._service_config.api_port, - CONF.backlog, - CONF.tcp_keepidle) + addresses = self._get_listen_on_addresses(9001) + + for address in addresses: + self._start(address[0], address[1]) + + def _start(self, host, port): + wsgi_sock = utils.bind_tcp( + host, port, CONF.backlog, CONF.tcp_keepidle) if sslutils.is_enabled(CONF): - self._wsgi_sock = sslutils.wrap(CONF, self._wsgi_sock) + wsgi_sock = sslutils.wrap(CONF, wsgi_sock) - self.tg.add_thread(self._wsgi_handle) + self._wsgi_socks.append(wsgi_sock) - def _wsgi_handle(self): + self.tg.add_thread(self._wsgi_handle, wsgi_sock) + + def _wsgi_handle(self, wsgi_sock): logger = logging.getLogger('eventlet.wsgi') # Adjust wsgi MAX_HEADER_LINE to accept large tokens. eventlet.wsgi.MAX_HEADER_LINE = self._service_config.max_header_line - eventlet.wsgi.server(self._wsgi_sock, + eventlet.wsgi.server(wsgi_sock, self._wsgi_application, custom_pool=self.tg.pool, log=logger) @@ -221,6 +265,9 @@ class DNSService(object): # reading/writing to the UDP socket at once. Disable this warning. eventlet.debug.hub_prevent_multiple_readers(False) + self._dns_socks_tcp = [] + self._dns_socks_udp = [] + @abc.abstractproperty def _dns_application(self): pass @@ -228,17 +275,23 @@ class DNSService(object): def start(self): super(DNSService, self).start() - self._dns_sock_tcp = utils.bind_tcp( - self._service_config.host, - self._service_config.port, - self._service_config.tcp_backlog) + addresses = self._get_listen_on_addresses(self._dns_default_port) - self._dns_sock_udp = utils.bind_udp( - self._service_config.host, - self._service_config.port) + for address in addresses: + self._start(address[0], address[1]) - self.tg.add_thread(self._dns_handle_tcp) - self.tg.add_thread(self._dns_handle_udp) + def _start(self, host, port): + sock_tcp = utils.bind_tcp( + host, port, self._service_config.tcp_backlog) + + sock_udp = utils.bind_udp( + host, port) + + self._dns_socks_tcp.append(sock_tcp) + self._dns_socks_udp.append(sock_udp) + + self.tg.add_thread(self._dns_handle_tcp, sock_tcp) + self.tg.add_thread(self._dns_handle_udp, sock_udp) def wait(self): super(DNSService, self).wait() @@ -248,18 +301,18 @@ class DNSService(object): # _handle_udp are stopped too. super(DNSService, self).stop() - if hasattr(self, '_dns_sock_tcp'): - self._dns_sock_tcp.close() + for sock_tcp in self._dns_socks_tcp: + sock_tcp.close() - if hasattr(self, '_dns_sock_udp'): - self._dns_sock_udp.close() + for sock_udp in self._dns_socks_udp: + sock_udp.close() - def _dns_handle_tcp(self): + def _dns_handle_tcp(self, sock_tcp): LOG.info(_LI("_handle_tcp thread started")) while True: try: - client, addr = self._dns_sock_tcp.accept() + client, addr = sock_tcp.accept() if self._service_config.tcp_recv_timeout: client.settimeout(self._service_config.tcp_recv_timeout) @@ -312,20 +365,21 @@ class DNSService(object): self.tg.add_thread(self._dns_handle, addr, payload, client=client) - def _dns_handle_udp(self): + def _dns_handle_udp(self, sock_udp): LOG.info(_LI("_handle_udp thread started")) while True: try: # TODO(kiall): Determine the appropriate default value for # UDP recvfrom. - payload, addr = self._dns_sock_udp.recvfrom(8192) + payload, addr = sock_udp.recvfrom(8192) LOG.debug("Handling UDP Request from: %(host)s:%(port)d" % {'host': addr[0], 'port': addr[1]}) # Dispatch a thread to handle the query - self.tg.add_thread(self._dns_handle, addr, payload) + self.tg.add_thread(self._dns_handle, addr, payload, + sock_udp=sock_udp) except socket.error as e: errname = errno.errorcode[e.args[0]] @@ -338,7 +392,7 @@ class DNSService(object): "from: %(host)s:%(port)d") % {'host': addr[0], 'port': addr[1]}) - def _dns_handle(self, addr, payload, client=None): + def _dns_handle(self, addr, payload, client=None, sock_udp=None): """ Handle a DNS Query @@ -360,7 +414,7 @@ class DNSService(object): client.sendall(tcp_response) else: # Handle UDP Responses - self._dns_sock_udp.sendto(response, addr) + sock_udp.sendto(response, addr) except Exception: LOG.exception(_LE("Unhandled exception while processing request " diff --git a/designate/tests/test_mdns/test_service.py b/designate/tests/test_mdns/test_service.py index 5ece2686..b4af3555 100644 --- a/designate/tests/test_mdns/test_service.py +++ b/designate/tests/test_mdns/test_service.py @@ -60,6 +60,8 @@ class MdnsServiceTest(MdnsTestCase): expected_response = (b"271289050001000000000000076578616d706c6503636f6" b"d0000010001") - self.service._dns_handle(self.addr, binascii.a2b_hex(payload)) + sock_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.service._dns_handle(self.addr, binascii.a2b_hex(payload), + sock_udp=sock_udp) sendto_mock.assert_called_once_with( binascii.a2b_hex(expected_response), self.addr) diff --git a/devstack/plugin.sh b/devstack/plugin.sh index a6c5a416..49b1379a 100755 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -82,15 +82,13 @@ function configure_designate { iniset $DESIGNATE_CONF service:api enabled_extensions_v1 $DESIGNATE_ENABLED_EXTENSIONS_V1 iniset $DESIGNATE_CONF service:api enabled_extensions_v2 $DESIGNATE_ENABLED_EXTENSIONS_V2 iniset $DESIGNATE_CONF service:api enabled_extensions_admin $DESIGNATE_ENABLED_EXTENSIONS_ADMIN - iniset $DESIGNATE_CONF service:api api_host $DESIGNATE_SERVICE_HOST iniset $DESIGNATE_CONF service:api api_base_uri $DESIGNATE_SERVICE_PROTOCOL://$DESIGNATE_SERVICE_HOST:$DESIGNATE_SERVICE_PORT/ iniset $DESIGNATE_CONF service:api enable_api_v1 $DESIGNATE_ENABLE_API_V1 iniset $DESIGNATE_CONF service:api enable_api_v2 $DESIGNATE_ENABLE_API_V2 iniset $DESIGNATE_CONF service:api enable_api_admin $DESIGNATE_ENABLE_API_ADMIN # mDNS Configuration - iniset $DESIGNATE_CONF service:mdns host $DESIGNATE_SERVICE_HOST - iniset $DESIGNATE_CONF service:mdns port $DESIGNATE_SERVICE_PORT_MDNS + iniset $DESIGNATE_CONF service:mdns listen ${DESIGNATE_SERVICE_HOST}:${DESIGNATE_SERVICE_PORT_MDNS} # Set up Notifications/Ceilometer Integration iniset $DESIGNATE_CONF DEFAULT notification_driver "$DESIGNATE_NOTIFICATION_DRIVER" @@ -114,9 +112,9 @@ function configure_designate { # TLS Proxy Configuration if is_service_enabled tls-proxy; then # Set the service port for a proxy to take the original - iniset $DESIGNATE_CONF service:api api_port $DESIGNATE_SERVICE_PORT_INT + iniset $DESIGNATE_CONF service:api listen ${DESIGNATE_SERVICE_HOST}:${DESIGNATE_SERVICE_PORT_INT} else - iniset $DESIGNATE_CONF service:api api_port $DESIGNATE_SERVICE_PORT + iniset $DESIGNATE_CONF service:api listen ${DESIGNATE_SERVICE_HOST}:${DESIGNATE_SERVICE_PORT} fi # Setup the Keystone Integration diff --git a/etc/designate/designate.conf.sample b/etc/designate/designate.conf.sample index 3f0828d1..245c73e9 100644 --- a/etc/designate/designate.conf.sample +++ b/etc/designate/designate.conf.sample @@ -100,11 +100,8 @@ debug = False # The base uri used in responses #api_base_uri = 'http://127.0.0.1:9001/' -# Address to bind the API server -#api_host = 0.0.0.0 - -# Port the bind the API server to -#api_port = 9001 +# API bind host+port pairs, comma separated +#listen = 0.0.0.0:9001 # Maximum line size of message headers to be accepted. max_header_line may # need to be increased when using large tokens (typically those generated by @@ -238,11 +235,8 @@ debug = False # Number of mdns greenthreads to spawn #threads = 1000 -# mDNS Bind Host -#host = 0.0.0.0 - -# mDNS Port Number -#port = 5354 +# mDNS bind host+port pairs, comma separated +#listen = 0.0.0.0:5354 # mDNS TCP Backlog #tcp_backlog = 100 @@ -264,8 +258,7 @@ debug = False #----------------------- [service:agent] #workers = None -#host = 0.0.0.0 -#port = 5358 +#listen = 0.0.0.0:5358 #tcp_backlog = 100 #allow_notify = 127.0.0.1 #masters = 127.0.0.1:5354 diff --git a/releasenotes/notes/api-mdns-multiple-bind-c78853de46ee587d.yaml b/releasenotes/notes/api-mdns-multiple-bind-c78853de46ee587d.yaml new file mode 100644 index 00000000..65fbab6f --- /dev/null +++ b/releasenotes/notes/api-mdns-multiple-bind-c78853de46ee587d.yaml @@ -0,0 +1,15 @@ +--- +features: + - designate-mdns, designate-agent and designate-api can now bind to multiple + host:port pairs via the new "listen" configuration arguments for eacg + service. +deprecations: + - designate-api's api_host and api_port configuration options have been + deprecated, please use the new combined "listen" argument in place of + these. + - designate-mdns's host and port configuration options have been + deprecated, please use the new combined "listen" argument in place of + these. + - designate-agents's host and port configuration options have been + deprecated, please use the new combined "listen" argument in place of + these.