mirror of
https://github.com/vale981/emacs-jupyter
synced 2025-03-04 15:41:37 -05:00
Fix jupyter-tunnel-connection
* .travis.yml: Add setup for testing SSH components. * jupyter-base.el (jupyter-tunnel-connection): Document behavior more completely. Refactor for readability. Ensure that the :ip field of the returned property list is set to 127.0.0.1 since it represents the connection info of the remote system but with local ports and should not have a remote IP address. * test/jupyter-test.el (jupyter-tunnel-connection): New test. * test/test-helper.el (jupyter-test-with-kernel): New macro.
This commit is contained in:
parent
6e598da5e9
commit
d14aa94c1f
4 changed files with 112 additions and 40 deletions
|
@ -2,6 +2,10 @@
|
|||
sudo: required
|
||||
dist: trusty
|
||||
language: nix
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- openssh-server
|
||||
matrix:
|
||||
# Report build failure/success before allowed failures complete
|
||||
fast_finish: true
|
||||
|
@ -37,6 +41,10 @@ before_script:
|
|||
script:
|
||||
- export PATH=$HOME/.cask/bin:$PATH
|
||||
- cd $TRAVIS_BUILD_DIR
|
||||
# Ensure ssh does not prompt for anything during tests
|
||||
- ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa
|
||||
- cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
|
||||
- ssh -o StrictHostKeyChecking=no localhost exit
|
||||
- make dev
|
||||
- make compile
|
||||
- make test
|
||||
|
|
|
@ -491,48 +491,63 @@ following fields:
|
|||
;; seconds
|
||||
"sleep 60"))
|
||||
|
||||
(defun jupyter-tunnel-connection (conn-file &optional server)
|
||||
"Forward local ports to the remote ports in CONN-FILE.
|
||||
CONN-FILE is the path to a Jupyter connection file, SERVER is the
|
||||
host that the kernel connection in CONN-FILE is located. Return a
|
||||
copy of the connection plist in CONN-FILE, but with the ports
|
||||
replaced by the local ports used for the forwarding.
|
||||
(defun jupyter-tunnel-connection (conn-file &optional remote-host)
|
||||
"Forward local ports to the remote ports specified in CONN-FILE.
|
||||
CONN-FILE is the path to a Jupyter connection file, the traffic
|
||||
on the local ports will be directed to the remote ports listening
|
||||
at the ip specified in CONN-FILE on the remote network accessed
|
||||
via REMOTE-HOST, an SSH host.
|
||||
|
||||
If CONN-FILE is a `tramp' file name, the SERVER argument will be
|
||||
ignored and the host will be extracted from the information
|
||||
contained in the file name.
|
||||
Return a copy of the connection info. in CONN-FILE as a property
|
||||
list with the port fields (:iopub_port, :shell_port, ...)
|
||||
replaced by the local ports used for the tunneling and the :ip
|
||||
field equal to \"127.0.0.1\".
|
||||
|
||||
Note only SSH tunnels are currently supported."
|
||||
(catch 'no-tunnels
|
||||
(let ((conn-info (jupyter-read-plist conn-file)))
|
||||
(when (and (file-remote-p conn-file)
|
||||
(functionp 'tramp-dissect-file-name))
|
||||
(pcase-let (((cl-struct tramp-file-name method user host)
|
||||
(tramp-dissect-file-name conn-file)))
|
||||
(pcase method
|
||||
;; TODO: Document this in the README along with the fact that
|
||||
;; connection files can use /ssh: TRAMP files.
|
||||
("docker"
|
||||
;; Assume docker is using the -p argument to publish its exposed
|
||||
;; ports to the localhost. The ports used in the container should
|
||||
;; be the same ports accessible on the local host. For example, if
|
||||
;; the shell port is on 1234 in the container, the published port
|
||||
;; flag should be "-p 1234:1234".
|
||||
(throw 'no-tunnels conn-info))
|
||||
(_
|
||||
(setq server (if user (concat user "@" host)
|
||||
host))))))
|
||||
(let* ((keys '(:hb_port :shell_port :control_port
|
||||
:stdin_port :iopub_port))
|
||||
(lports (jupyter-available-local-ports (length keys))))
|
||||
(cl-loop
|
||||
with remoteip = (plist-get conn-info :ip)
|
||||
for (key maybe-rport) on conn-info by #'cddr
|
||||
collect key and if (memq key keys)
|
||||
collect (let ((lport (pop lports)))
|
||||
(prog1 lport
|
||||
(jupyter-make-ssh-tunnel lport maybe-rport server remoteip)))
|
||||
else collect maybe-rport)))))
|
||||
If CONN-FILE is a remote file, the REMOTE-HOST argument is
|
||||
ignored. Instead, the remote host information is obtained from
|
||||
information contained in the remote file name.
|
||||
|
||||
If CONN-FILE is a remote file in a Docker container, i.e. it has
|
||||
a file name prefixed with /docker:, do not tunnel any ports.
|
||||
Assume the ports in CONN-FILE can also be used on localhost for
|
||||
connecting to those same ports in the container. That is, assume
|
||||
the container was launched using a --publish argument like
|
||||
\"--publish 1234:1234\". In this case, only the :ip field is
|
||||
replaced and set to \"127.0.0.1\"."
|
||||
(catch 'no-tunnels-when-docker-conn-file
|
||||
(when (and (file-remote-p conn-file)
|
||||
(functionp 'tramp-dissect-file-name))
|
||||
(pcase-let (((cl-struct tramp-file-name method user host)
|
||||
(tramp-dissect-file-name conn-file)))
|
||||
(pcase method
|
||||
;; TODO: Document this in the README along with the fact that
|
||||
;; connection files can use /ssh: TRAMP files.
|
||||
("docker"
|
||||
(throw 'no-tunnels-when-docker-conn-file
|
||||
(let ((conn-info (jupyter-read-plist conn-file)))
|
||||
(plist-put conn-info :ip "127.0.0.1")
|
||||
conn-info)))
|
||||
(_
|
||||
(setq remote-host (if user (concat user "@" host)
|
||||
host))))))
|
||||
(unless (executable-find "ssh")
|
||||
(error "SSH not found on `exec-path'"))
|
||||
(let* ((port-keys (list :hb_port :shell_port :control_port
|
||||
:stdin_port :iopub_port))
|
||||
(local-ports (jupyter-available-local-ports (length port-keys)))
|
||||
(conn-info (jupyter-read-plist conn-file)))
|
||||
(cl-loop
|
||||
with remote-ip = (plist-get conn-info :ip)
|
||||
for (key value) on conn-info by #'cddr
|
||||
collect key
|
||||
if (eq key :ip) collect "127.0.0.1"
|
||||
else if (memq key port-keys)
|
||||
collect (let ((remote-port value)
|
||||
(local-port (pop local-ports)))
|
||||
(prog1 local-port
|
||||
(jupyter-make-ssh-tunnel
|
||||
local-port remote-port remote-host remote-ip)))
|
||||
else collect value))))
|
||||
|
||||
;;; Helper functions
|
||||
|
||||
|
|
|
@ -772,6 +772,44 @@
|
|||
(delete-file file)))
|
||||
(should-not (memq fun kill-emacs-hook))))
|
||||
|
||||
;; TODO: Docker test
|
||||
(ert-deftest jupyter-tunnel-connection ()
|
||||
:tags '(client ssh)
|
||||
(let ((kernel (jupyter-command-kernel
|
||||
:spec (jupyter-guess-kernelspec "python"))))
|
||||
(jupyter-start-kernel kernel "--ip=0.0.0.0")
|
||||
(unwind-protect
|
||||
(with-current-buffer (process-buffer (oref kernel process))
|
||||
(goto-char (point-min))
|
||||
(re-search-forward "Connection file: \\(.+\\)\n")
|
||||
;; Ensure this is required since TRAMP will fail without it
|
||||
;; on Travis.
|
||||
(require 'files-x)
|
||||
(let* ((method (cdr (assoc "ssh" tramp-methods)))
|
||||
(conn-file (match-string 1))
|
||||
(conn-info (jupyter-read-plist conn-file))
|
||||
(ssh-conn-file (concat "/ssh:localhost:" conn-file))
|
||||
(ssh-conn-info (jupyter-tunnel-connection ssh-conn-file)))
|
||||
(should (equal (plist-get conn-info :ip) "0.0.0.0"))
|
||||
(should (equal (plist-get ssh-conn-info :ip) "127.0.0.1"))
|
||||
(cl-loop for port in '(:hb_port
|
||||
:control_port
|
||||
:shell_port
|
||||
:iopub_port
|
||||
:stdin_port)
|
||||
do (should-not
|
||||
(equal (plist-get conn-info port)
|
||||
(plist-get ssh-conn-info port))))
|
||||
;; Can we talk to the kernel
|
||||
(let* ((manager (jupyter-kernel-process-manager
|
||||
:kernel kernel))
|
||||
(jupyter-current-client
|
||||
(jupyter-make-client manager 'jupyter-kernel-client)))
|
||||
(jupyter-start-channels jupyter-current-client)
|
||||
(should (equal "2" (jupyter-eval "1 + 1")))
|
||||
(jupyter-stop-channels jupyter-current-client))))
|
||||
(jupyter-kill-kernel kernel))))
|
||||
|
||||
(ert-deftest jupyter-client-channels ()
|
||||
:tags '(client channels)
|
||||
(ert-info ("Starting/stopping channels")
|
||||
|
|
|
@ -234,6 +234,17 @@ If the `current-buffer' is not a REPL, this is identical to
|
|||
(accept-process-output nil 1)
|
||||
,@body))))
|
||||
|
||||
(defmacro jupyter-test-with-kernel (kernel-name kernel &rest body)
|
||||
"Start a new kernel with name KERNEL-NAME, bind it to KERNEL, evaluate BODY.
|
||||
KERNEL will be a `jupyter-command-kernel'."
|
||||
(declare (indent 2))
|
||||
`(let ((,kernel (jupyter-command-kernel
|
||||
:spec (jupyter-guess-kernelspec ,kernel-name))))
|
||||
(jupyter-start-kernel ,kernel)
|
||||
(unwind-protect
|
||||
(progn ,@body)
|
||||
(jupyter-kill-kernel ,kernel))))
|
||||
|
||||
(defmacro jupyter-test-with-kernel-client (kernel client &rest body)
|
||||
"Start a new KERNEL client, bind it to CLIENT, evaluate BODY.
|
||||
This only starts a single global client unless the variable
|
||||
|
|
Loading…
Add table
Reference in a new issue