From e6acbd7c7f625d675f75d9be8f75b4cb6d8991bc Mon Sep 17 00:00:00 2001 From: Wim Pomp Date: Tue, 30 Aug 2022 13:04:14 +0200 Subject: [PATCH] First commit --- .gitignore | 3 + .idea/.gitignore | 3 + .idea/inspectionProfiles/Project_Default.xml | 27 ++++++ .../inspectionProfiles/profiles_settings.xml | 6 ++ .idea/matlabhub.iml | 11 +++ .idea/misc.xml | 7 ++ .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 ++ README.md | 35 +++++++ matlab | 23 +++++ matlab.service | 17 ++++ matlabhub/app.py | 96 +++++++++++++++++++ matlabhub/templates/index.html | 37 +++++++ matlabhub/wsgi.py | 3 + setup.py | 26 +++++ 15 files changed, 308 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/matlabhub.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 README.md create mode 100644 matlab create mode 100644 matlab.service create mode 100755 matlabhub/app.py create mode 100644 matlabhub/templates/index.html create mode 100644 matlabhub/wsgi.py create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..21dab19 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/matlabhub/config.yml +/build/ +/matlabhub.egg-info/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..18487aa --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,27 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/matlabhub.iml b/.idea/matlabhub.iml new file mode 100644 index 0000000..1a32326 --- /dev/null +++ b/.idea/matlabhub.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..b7ea289 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..0889ee5 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c884f9b --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# Matlab Hub + +Start proxied matlab instances from a webpage using Nginx. + +## Preequisites + +Linux with Matlab and Python3 + +## Installation + +make matlabhub/config.py: + + license_file: 'path_to_license.lic' + port_range: [10000, 65536] + +install: + + sudo apt install nginx screen + sudo pip install matlabhub + sudo cp matlab /etc/nginx/sites-available/ + sudo ln -s /etc/nginx/sites-available/matlab /etc/nginx/sites-enabled/matlab + sudo cp matlab.service /lib/systemd/system/ + sudo systemctl daemon-reload + sudo systemctl enable nginx + sudo systemctl enable matlab + sudo service nginx start + sudo service matlab start + +## Configuration + +Change the user, path to the socket file and the port (default: 8585) in matlab and matlab.service. + +## Usage + +Browse to http://127.0.0.1:8585 diff --git a/matlab b/matlab new file mode 100644 index 0000000..b1b4dd2 --- /dev/null +++ b/matlab @@ -0,0 +1,23 @@ +# nginx configuration + +server { + listen 8585 default_server; + listen [::]:8585 default_server; + + root /var/www/html; + + index index.html; + + server_name _; + + location ~ ^/([0-9]+) { + proxy_set_header Host '127.0.0.1'; + proxy_pass http://127.0.0.1:$1; + } + + location / { + include proxy_params; + proxy_pass http://unix:/tmp/matlabhub.sock; + } +} + diff --git a/matlab.service b/matlab.service new file mode 100644 index 0000000..fc02af1 --- /dev/null +++ b/matlab.service @@ -0,0 +1,17 @@ +[Unit] +Description=Matlab web server +After=network-online.target firewalld.service +Wants=network-online.target + +[Service] +User=matlab-user +Type=simple +ExecStart=/usr/local/bin/gunicorn --bind unix:/tmp/matlabhub.sock matlabhub.wsgi:application +ExecStop=/bin/kill -s STOP $MAINPID +ExecReload=/bin/kill -s HUP $MAINPID +TimeoutSec=0 +RestartSec=2 +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/matlabhub/app.py b/matlabhub/app.py new file mode 100755 index 0000000..25bbe8b --- /dev/null +++ b/matlabhub/app.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3.10 + +import os +import sys +import re +import socket +import subprocess +import random +import yaml +from flask import Flask, redirect, render_template, request +from time import sleep + +with open(os.path.join(os.path.dirname(__file__), 'config.yml')) as conf_file: + config = yaml.safe_load(conf_file) + + +def is_port_in_use(port): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + return s.connect_ex(('127.0.0.1', port)) == 0 + + +class Matlab: + def __init__(self, port, group=None): + self.port = int(port) + self.group = group + self.screen_name = f'matlab_{port}' if group is None else f'matlab_{group}_{port}' + + def start(self): + command = f'screen -S {self.screen_name} -d -m env MWI_BASE_URL="/{self.port}" ' \ + f'MWI_APP_PORT={self.port} MLM_LICENSE_FILE={config["license_file"]} matlab-proxy-app' + subprocess.Popen(command, shell=True, start_new_session=True) + + def stop(self): + subprocess.run(f"screen -S {self.screen_name} -X stuff '^C'", shell=True) + + +class Hub(dict): + def __init__(self): + super().__init__() + self.update({matlab.port: matlab for matlab in self.find_running()}) + + + def find_running(self): + p = subprocess.run('screen -list', shell=True, capture_output=True) + return [Matlab(port, group) for group, port in re.findall(r'\.matlab_([^_]+)_(\d+)', str(p.stdout))] + + def new(self, group=None): + group = re.escape(re.sub(r'[^\w]|_', '', group)) + ports = list(range(*config['port_range'])) + random.shuffle(ports) + for port in ports: + if port not in self and not is_port_in_use(port): + self[port] = Matlab(port, group) + self[port].start() + sleep(5) + return redirect(f'/{port}') + + def stop(self, port): + self.pop(port).stop() + + def get_groups(self): + return list(set(matlab.group for matlab in self.values())) + + def get_ports(self, group=None): + return [matlab.port for matlab in self.values() if matlab.group == group] + + +sys.argv = sys.argv[:1] +hub = Hub() +app = Flask(__name__) + + +@app.route('/') +def index(): + return redirect('/default') + + +@app.route('/') +def group(group): + return render_template('index.html', ports=hub.get_ports(group), groups=hub.get_groups(), group=group) + + +@app.route('//start/new') +def group_start_new(group): + return hub.new(group) + + +@app.route('/', methods=['POST']) +def start_new_post(group): + return hub.new(request.form['group']) + + +@app.route('//stop/') +def stop(group, port): + hub.stop(int(port)) + return redirect(f'/{group}') diff --git a/matlabhub/templates/index.html b/matlabhub/templates/index.html new file mode 100644 index 0000000..92e2529 --- /dev/null +++ b/matlabhub/templates/index.html @@ -0,0 +1,37 @@ + + + + + Matlab Hub + + +
+

Matlab Hub

+
+

Start new matlab in group:

+
+ + +
+
+

Groups:

+ {% for group in groups %} + {{group}}
+ {% endfor %} +

Matlabs in group {{group}}:

+ {% for port in ports %} + {{port}} stop
+ {% endfor %} +
+ Matlab Hub by Wim Pomp +
+ + \ No newline at end of file diff --git a/matlabhub/wsgi.py b/matlabhub/wsgi.py new file mode 100644 index 0000000..323b90a --- /dev/null +++ b/matlabhub/wsgi.py @@ -0,0 +1,3 @@ +import os +import sys +from matlabhub.app import app as application diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..1852f2d --- /dev/null +++ b/setup.py @@ -0,0 +1,26 @@ +import setuptools + +with open('README.md', 'r') as fh: + long_description = fh.read() + +setuptools.setup( + name='matlabhub', + version='2022.7.0', + author='Wim Pomp @ Lenstra lab NKI', + author_email='w.pomp@nki.nl', + description='matlabhub', + long_description=long_description, + long_description_content_type='text/markdown', + url='https://github.com/wimpomp/matlabhub', + packages=['matlabhub'], + classifiers=[ + 'Programming Language :: Python :: 3', + 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', + 'Operating System :: OS Independent', + ], + python_requires='>=3.8', + install_requires=['matlab-proxy', 'flask'], + entry_points={'console_scripts': ['matlabhub=matlabhub:main'], }, + package_data={'': ['templates/*', 'config.yml']}, + include_package_data=True, +)