aboutsummaryrefslogtreecommitdiff
path: root/contrib/python/podman/libs/tunnel.py
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/python/podman/libs/tunnel.py')
-rw-r--r--contrib/python/podman/libs/tunnel.py131
1 files changed, 131 insertions, 0 deletions
diff --git a/contrib/python/podman/libs/tunnel.py b/contrib/python/podman/libs/tunnel.py
new file mode 100644
index 000000000..2cb178644
--- /dev/null
+++ b/contrib/python/podman/libs/tunnel.py
@@ -0,0 +1,131 @@
+"""Cache for SSH tunnels."""
+import collections
+import os
+import subprocess
+import threading
+import time
+import weakref
+
+Context = collections.namedtuple('Context', (
+ 'uri',
+ 'interface',
+ 'local_socket',
+ 'remote_socket',
+ 'username',
+ 'hostname',
+ 'identity_file',
+))
+
+
+class Portal(collections.MutableMapping):
+ """Expiring container for tunnels."""
+
+ def __init__(self, sweap=25):
+ """Construct portal, reap tunnels every sweap seconds."""
+ self.data = collections.OrderedDict()
+ self.sweap = sweap
+ self.ttl = sweap * 2
+ self.lock = threading.RLock()
+
+ def __getitem__(self, key):
+ """Given uri return tunnel and update TTL."""
+ with self.lock:
+ value, _ = self.data[key]
+ self.data[key] = (value, time.time() + self.ttl)
+ self.data.move_to_end(key)
+ return value
+
+ def __setitem__(self, key, value):
+ """Store given tunnel keyed with uri."""
+ if not isinstance(value, Tunnel):
+ raise ValueError('Portals only support Tunnels.')
+
+ with self.lock:
+ self.data[key] = (value, time.time() + self.ttl)
+ self.data.move_to_end(key)
+
+ def __delitem__(self, key):
+ """Remove and close tunnel from portal."""
+ with self.lock:
+ value, _ = self.data[key]
+ del self.data[key]
+ value.close(key)
+ del value
+
+ def __iter__(self):
+ """Iterate tunnels."""
+ with self.lock:
+ values = self.data.values()
+
+ for tunnel, _ in values:
+ yield tunnel
+
+ def __len__(self):
+ """Return number of tunnels in portal."""
+ with self.lock:
+ return len(self.data)
+
+ def _schedule_reaper(self):
+ timer = threading.Timer(interval=self.sweap, function=self.reap)
+ timer.setName('PortalReaper')
+ timer.setDaemon(True)
+ timer.start()
+
+ def reap(self):
+ """Remove tunnels who's TTL has expired."""
+ with self.lock:
+ now = time.time()
+ for entry, timeout in self.data:
+ if timeout < now:
+ self.__delitem__(entry)
+ else:
+ # StopIteration as soon as possible
+ break
+ self._schedule_reaper()
+
+
+class Tunnel(object):
+ """SSH tunnel."""
+
+ def __init__(self, context):
+ """Construct Tunnel."""
+ self.context = context
+ self._tunnel = None
+
+ def bore(self, id):
+ """Create SSH tunnel from given context."""
+ cmd = [
+ 'ssh',
+ '-nNT',
+ '-L',
+ '{}:{}'.format(self.context.local_socket,
+ self.context.remote_socket),
+ '-i',
+ self.context.identity_file,
+ 'ssh://{}@{}'.format(self.context.username, self.context.hostname),
+ ]
+
+ if os.environ.get('PODMAN_DEBUG'):
+ cmd.append('-vvv')
+
+ self._tunnel = subprocess.Popen(cmd, close_fds=True)
+ for i in range(5):
+ if os.path.exists(self.context.local_socket):
+ break
+ time.sleep(1)
+ else:
+ raise TimeoutError('Failed to create tunnel using: {}'.format(
+ ' '.join(cmd)))
+ weakref.finalize(self, self.close, id)
+ return self
+
+ def close(self, id):
+ """Close SSH tunnel."""
+ print('Tunnel collapsed!')
+ if self._tunnel is None:
+ return
+
+ self._tunnel.kill()
+ self._tunnel.wait(300)
+ os.remove(self.context.local_socket)
+ self._tunnel = None