Support finger ports in finger URL

We support configuring an alternate port for finger. Make sure it makes
its way into the URL we provide if it's provided.

Change-Id: I5f511e15c031755d5c90627830ed29b80c6285fd
diff --git a/zuul/cmd/executor.py b/zuul/cmd/executor.py
index 57ecfa3..44a7d3f 100755
--- a/zuul/cmd/executor.py
+++ b/zuul/cmd/executor.py
@@ -40,9 +40,6 @@
 # Similar situation with gear and statsd.
 
 
-DEFAULT_FINGER_PORT = 79
-
-
 class Executor(zuul.cmd.ZuulApp):
 
     def parse_arguments(self):
@@ -127,8 +124,10 @@
         self.setup_logging('executor', 'log_config')
         self.log = logging.getLogger("zuul.Executor")
 
-        self.finger_port = int(get_default(self.config, 'executor',
-                                           'finger_port', DEFAULT_FINGER_PORT))
+        self.finger_port = int(
+            get_default(self.config, 'executor', 'finger_port',
+                        zuul.executor.server.DEFAULT_FINGER_PORT)
+        )
 
         self.start_log_streamer()
         self.change_privs()
@@ -136,7 +135,8 @@
         ExecutorServer = zuul.executor.server.ExecutorServer
         self.executor = ExecutorServer(self.config, self.connections,
                                        jobdir_root=self.jobroot_dir,
-                                       keep_jobdir=self.args.keep_jobdir)
+                                       keep_jobdir=self.args.keep_jobdir,
+                                       log_streaming_port=self.finger_port)
         self.executor.start()
 
         signal.signal(signal.SIGUSR2, zuul.cmd.stack_dump_handler)
diff --git a/zuul/executor/server.py b/zuul/executor/server.py
index a4b48db..bc4ebd3 100644
--- a/zuul/executor/server.py
+++ b/zuul/executor/server.py
@@ -36,6 +36,7 @@
 
 COMMANDS = ['stop', 'pause', 'unpause', 'graceful', 'verbose',
             'unverbose', 'keep', 'nokeep']
+DEFAULT_FINGER_PORT = 79
 
 
 class Watchdog(object):
@@ -357,13 +358,14 @@
     log = logging.getLogger("zuul.ExecutorServer")
 
     def __init__(self, config, connections={}, jobdir_root=None,
-                 keep_jobdir=False):
+                 keep_jobdir=False, log_streaming_port=DEFAULT_FINGER_PORT):
         self.config = config
         self.keep_jobdir = keep_jobdir
         self.jobdir_root = jobdir_root
         # TODOv3(mordred): make the executor name more unique --
         # perhaps hostname+pid.
         self.hostname = socket.gethostname()
+        self.log_streaming_port = log_streaming_port
         self.zuul_url = config.get('merger', 'zuul_url')
         self.merger_lock = threading.Lock()
         self.verbose = False
@@ -808,9 +810,6 @@
         self.prepareAnsibleFiles(args)
 
         data = {
-            'url': 'finger://{server}/{unique}'.format(
-                unique=self.job.unique,
-                server=self.executor_server.hostname),
             # TODO(mordred) worker_name is needed as a unique name for the
             # client to use for cancelling jobs on an executor. It's defaulting
             # to the hostname for now, but in the future we should allow
@@ -818,7 +817,17 @@
             # one executor on a host.
             'worker_name': self.executor_server.hostname,
             'worker_hostname': self.executor_server.hostname,
+            'worker_log_port': self.executor_server.log_streaming_port
         }
+        if self.executor_server.log_streaming_port != DEFAULT_FINGER_PORT:
+            data['url'] = "finger://{hostname}:{port}/{uuid}".format(
+                hostname=data['worker_hostname'],
+                port=data['worker_log_port'],
+                uuid=self.job.unique)
+        else:
+            data['url'] = 'finger://{hostname}/{uuid}'.format(
+                hostname=data['worker_hostname'],
+                uuid=self.job.unique)
 
         self.job.sendWorkData(json.dumps(data))
         self.job.sendWorkStatus(0, 100)
diff --git a/zuul/model.py b/zuul/model.py
index a89c6d1..dc04e59 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -1105,11 +1105,13 @@
     def __init__(self):
         self.name = "Unknown"
         self.hostname = None
+        self.log_port = None
 
     def updateFromData(self, data):
         """Update worker information if contained in the WORK_DATA response."""
         self.name = data.get('worker_name', self.name)
         self.hostname = data.get('worker_hostname', self.hostname)
+        self.log_port = data.get('worker_log_port', self.log_port)
 
     def __repr__(self):
         return '<Worker %s>' % self.name