Repository: emissary-ingress/emissary

Issue:
Users want to not need to manually trigger a reconfiguration.
A Better Way is to automagically reconfigure on `POST`.


Discussion:

Resolving patch:
diff --git a/ambassador/ambassador.py b/ambassador/ambassador.py
--- a/ambassador/ambassador.py
+++ b/ambassador/ambassador.py
@@ -16,7 +16,7 @@
 import VERSION
 
 from envoy import EnvoyStats, EnvoyConfig
-from utils import RichStatus, SystemInfo
+from utils import RichStatus, SystemInfo, DelayTrigger
 
 __version__ = VERSION.Version
 
@@ -146,6 +146,8 @@ def handle_service_del(req, name):
         cursor.execute("DELETE FROM services WHERE name = :name", locals())
         conn.commit()
 
+        app.reconfigurator.trigger()
+
         return RichStatus.OK(name=name)
     except pg8000.Error as e:
         return RichStatus.fromError("%s: could not delete service: %s" % (name, e))
@@ -169,6 +171,8 @@ def handle_service_post(req, name):
         cursor.execute('INSERT INTO services VALUES(:name, :prefix, 0)', locals())
         conn.commit()
 
+        app.reconfigurator.trigger()
+
         return RichStatus.OK(name=name)
     except pg8000.Error as e:
         return RichStatus.fromError("%s: could not save info: %s" % (name, e))
@@ -192,7 +196,16 @@ def ambassador_stats():
 
     return jsonify(app.stats.stats)
 
-def new_config(envoy_base_config, envoy_config_path, envoy_restarter_pid):
+def new_config(envoy_base_config=None, envoy_config_path=None, envoy_restarter_pid=None):
+    if not envoy_base_config:
+        envoy_base_config = app.envoy_base_config
+
+    if not envoy_config_path:
+        envoy_config_path = app.envoy_config_path
+
+    if not envoy_restarter_pid:
+        envoy_restarter_pid = app.envoy_restarter_pid
+
     config = EnvoyConfig(envoy_base_config)
 
     rc = fetch_all_services()
@@ -221,11 +234,8 @@ def root():
 
         if rc:
             if request.method == 'PUT':
-                rc = new_config(
-                    app.envoy_base_config,      # base config we read earlier
-                    app.envoy_config_path,      # where to write full config
-                    app.envoy_restarter_pid     # PID to signal for reload
-                )
+                app.reconfigurator.trigger()
+                rc = RichStatus.OK(message="reconfigure requested")
             else:
                 rc = handle_service_list(request)
     except Exception as e:
@@ -284,17 +294,16 @@ def main():
 
     logging.info("ambassador found restarter PID %d" % app.envoy_restarter_pid)
 
-    new_config(
-        app.envoy_base_config,      # base config we read earlier
-        app.envoy_config_path,      # where to write full config
-        -1                          # don't signal automagically here
-    )
+    new_config(envoy_restarter_pid = -1)    # Don't automagically signal here.
 
     time.sleep(2)
 
     logging.info("ambassador asking restarter for initial reread")
     os.kill(app.envoy_restarter_pid, signal.SIGHUP)    
 
+    # Set up the trigger for future restarts.
+    app.reconfigurator = DelayTrigger(new_config)
+
     app.run(host='127.0.0.1', port=5000, debug=True)
 
 if __name__ == '__main__':
diff --git a/ambassador/utils.py b/ambassador/utils.py
--- a/ambassador/utils.py
+++ b/ambassador/utils.py
@@ -1,6 +1,9 @@
 #!/usr/bin/env python
 
+import sys
+
 import socket
+import threading
 
 import VERSION
 
@@ -49,3 +52,35 @@ def fromError(self, error, **kwargs):
     @classmethod
     def OK(self, **kwargs):
         return RichStatus(True, **kwargs)
+
+class DelayTrigger (threading.Thread):
+    def __init__(self, onfired, timeout=5, name=None):
+        super().__init__()
+
+        if name:
+            self.name = name
+
+        self.trigger_source, self.trigger_dest = socket.socketpair()
+
+        self.onfired = onfired
+        self.timeout = timeout
+
+        self.setDaemon(True)
+        self.start()
+
+    def trigger(self):
+        self.trigger_source.sendall(b'X')
+
+    def run(self):
+        while True:
+            self.trigger_dest.settimeout(None)
+            x = self.trigger_dest.recv(128)
+
+            self.trigger_dest.settimeout(self.timeout)
+
+            while True:
+                try:
+                    x = self.trigger_dest.recv(128)
+                except socket.timeout:
+                    self.onfired()
+                    break


Test patch: