├── .gitignore ├── LICENSE ├── README.md └── ipynbhpc /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Andrea Zonca 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ipynbhpc: IPython notebook launcher for HPC systems 2 | =================================================== 3 | 4 | Launch an interactive job via PBS (or equivalent) from a login node, 5 | start IPython notebook on a computing node, and create an SSH tunnel 6 | from the computing node to the login node. 7 | Then that port can be SSH tunneled again to a Linux/MAC/Windows machine 8 | and the IPython notebook opened locally in the browser. 9 | 10 | Therefore the IPython notebook frontend runs in the browser on your local machine, 11 | while the IPython kernel runs on the computing node of the cluster, 12 | results, as well as plots are tunneled via SSH from the computing node 13 | to the local machine, where they are displayed and can be saved as `.png` just 14 | right-clicking on them. 15 | 16 | ## Local machine setup 17 | 18 | * Choose a port number [>1024] 19 | * Create a bash alias (or a Putty profile) to connect to the HPC cluster tunneling that port 20 | 21 | alias sshcluster="ssh yourclusterhostname -L PORT:localhost:PORT" 22 | 23 | ## Cluster setup 24 | 25 | You need to be able to SSH passwordless locally: 26 | 27 | ssh localhost 28 | 29 | should work without the need to type the password. 30 | 31 | If you already have a private key in `.ssh/id_rsa` you can use it, otherwise create a new 32 | key with: 33 | 34 | ssh-keygen 35 | 36 | then you can just add the related 37 | public key `.ssh/id_rsa.pub` to `.ssh/authorized_keys` (the same works with a DSA key). 38 | 39 | ## Script setup (on the cluster) 40 | 41 | Clone the repository and symlink the script to a folder in your `PATH`, i.e. `~/bin/`. 42 | 43 | Customize the script options with environmental variables, add the `export` commands to 44 | your `.bashrc` or `.profile` so they are set automatically at each login. 45 | 46 | Set the port defined in Local machine setup 47 | 48 | export IPYNB_PORT=XXXX 49 | 50 | Set the PBS job duration in minutes: 51 | 52 | export IPYNB_MINUTES=30 53 | 54 | Set the template for requesting an interactive job, it should accept the number of minutes as `%d`, 55 | this example is the default and works on SDSC Gordon: 56 | 57 | export IPYNB_QSUB_TEMPLATE="qsub -I -V -l nodes=1:ppn=16:native:flash,walltime=%d:00 -q normal" 58 | 59 | For example on NERSC Carver it would be: 60 | 61 | export IPYNB_QSUB_TEMPLATE="qsub -I -V -l nodes=1:ppn=1,pvmem=20gb -l walltime=%d:00" 62 | 63 | Set the template for launching the IPython notebook, generally there should be no need to set this 64 | variable, the default should be enough: 65 | 66 | export IPYNB_NOTEBOOK_TEMPLATE="ipython notebook --pylab=inline --port=%s --no-browser" 67 | 68 | ## Use the script 69 | 70 | * SSH in the cluster login node using the bash alias 71 | * Open `screen` to prevent a disconnection to kill your job (if available, on Gordon it is not) 72 | * Launch the script with: 73 | 74 | ipynbhpc 75 | 76 | * Wait for the terminal to return: 77 | 78 | Succesfully opened notebook! 79 | 80 | * Connect with the browser on your local machine to `localhost:PORT`, where `PORT` is the port you chose. 81 | * To kill the process and release the node before completion of the job, just use `CTRL+C` on the terminal 82 | 83 | ## Feedback 84 | 85 | This has been tested only on SDSC Gordon and NERSC Carver, if you find any problem or have a feature request, please open an issue on Github: 86 | 87 | https://github.com/pyHPC/ipynbhpc/issues 88 | 89 | ## Authors 90 | 91 | * [Andrea Zonca](http://github.com/zonca), San Diego Supercomputer Center 92 | * [Marius Millea](http://github.com/marius311), UC Davis 93 | -------------------------------------------------------------------------------- /ipynbhpc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from subprocess import Popen, PIPE, call 3 | import sys 4 | import time 5 | import os 6 | import exceptions 7 | 8 | # Current default QSUB template is suitable for SDSC Gordon 9 | QSUB_TEMPLATE = os.environ.get("IPYNB_QSUB_TEMPLATE", 'qsub -I -V -l nodes=1:ppn=16:native:flash,walltime=%d:00 -q normal') 10 | 11 | NOTEBOOK_TEMPLATE = os.environ.get("IPYNB_NOTEBOOK_TEMPLATE", 'ipython notebook --pylab=inline --port=%d --no-browser') 12 | 13 | try: 14 | PORT = int(os.environ["IPYNB_PORT"]) 15 | except exceptions.KeyError: 16 | print("Need to define IPYNB_PORT environmental variable [>1024], i.e. export IPYNB_PORT=XXXX") 17 | sys.exit(1) 18 | 19 | try: 20 | MINUTES = int(os.environ["IPYNB_MINUTES"]) 21 | except exceptions.KeyError: 22 | MINUTES = 30 23 | print("You can define IPYNB_MINUTES environmental variable to set the duration of the job, i.e. export IPYNB_MINUTES=XX, using default %d minutes" % MINUTES) 24 | def readwhile(stream,func): 25 | while True: 26 | line = stream.readline() 27 | if line!='': 28 | print line[:-1] 29 | if func(line): break 30 | else: 31 | raise exceptions.Exception("Disconnected unexpectedly.") 32 | 33 | print("Requesting an interactive node") 34 | pqsub=Popen(['ssh','-t','-t','-4','localhost'],stdin=PIPE,stdout=PIPE,stderr=PIPE) 35 | pqsub.stdin.write((QSUB_TEMPLATE % MINUTES)+"\n") 36 | pqsub.stdin.write('echo HOSTNAME=`hostname`\n') 37 | 38 | def gethostname(line): 39 | global hostname 40 | if line.startswith('HOSTNAME'): 41 | hostname = line.split('=')[1].strip() 42 | return True 43 | 44 | print("Waiting for the job to start...") 45 | readwhile(pqsub.stdout, gethostname) 46 | 47 | pqsub.stdin.write('cd $PBS_O_WORKDIR\n') 48 | pqsub.stdin.write('echo CD\n') 49 | readwhile(pqsub.stdout, lambda line: line.startswith('CD')) 50 | 51 | print("Launching the notebook") 52 | pqsub.stdin.write((NOTEBOOK_TEMPLATE % PORT)+"\n") 53 | readwhile(pqsub.stdout, lambda line: line.find('NotebookApp')>0) 54 | 55 | print("Setting up SSH tunnel from computing to login node") 56 | tunnel = ['ssh', '-t', '-Y', hostname, '-L', '%s:localhost:%s'% (PORT,PORT)] 57 | ptunnel = Popen(tunnel,stdout=PIPE,stdin=PIPE) 58 | ptunnel.stdin.write('echo TUNNEL\n') 59 | readwhile(ptunnel.stdout,lambda line: line.startswith('TUNNEL')) 60 | 61 | print("Succesfully opened notebook!") 62 | print("Kill this process to end your notebook connection.") 63 | time.sleep(MINUTES*60) 64 | 65 | pqsub.kill() 66 | ptunnel.kill() 67 | 68 | print("Succesfully cleaned up connections.") 69 | --------------------------------------------------------------------------------