mirror of
https://github.com/ComputerScienceHouse/proxstar.git
synced 2025-03-09 15:40:09 +00:00
beginning of vnc work, hopefully some of this works
This commit is contained in:
parent
e0c564269f
commit
576c6c1338
10 changed files with 143 additions and 26 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -1,3 +1,6 @@
|
||||||
[submodule "proxstar/static/css/csh-material-bootstrap"]
|
[submodule "proxstar/static/css/csh-material-bootstrap"]
|
||||||
path = proxstar/static/css/csh-material-bootstrap
|
path = proxstar/static/css/csh-material-bootstrap
|
||||||
url = https://github.com/ComputerScienceHouse/csh-material-bootstrap.git
|
url = https://github.com/ComputerScienceHouse/csh-material-bootstrap.git
|
||||||
|
[submodule "proxstar/static/noVNC"]
|
||||||
|
path = proxstar/static/noVNC
|
||||||
|
url = https://github.com/novnc/noVNC.git
|
||||||
|
|
|
@ -31,6 +31,7 @@ PROXMOX_HOSTS = [
|
||||||
PROXMOX_USER = environ.get('PROXSTAR_PROXMOX_USER', '')
|
PROXMOX_USER = environ.get('PROXSTAR_PROXMOX_USER', '')
|
||||||
PROXMOX_PASS = environ.get('PROXSTAR_PROXMOX_PASS', '')
|
PROXMOX_PASS = environ.get('PROXSTAR_PROXMOX_PASS', '')
|
||||||
PROXMOX_ISO_STORAGE = environ.get('PROXSTAR_PROXMOX_ISO_STORAGE', 'nfs-iso')
|
PROXMOX_ISO_STORAGE = environ.get('PROXSTAR_PROXMOX_ISO_STORAGE', 'nfs-iso')
|
||||||
|
PROXMOX_SSH_KEY = environ.get('PROXSTAR_PROXMOX_SSH_KEY', '')
|
||||||
|
|
||||||
# STARRS
|
# STARRS
|
||||||
STARRS_DB_HOST = environ.get('PROXSTAR_STARRS_DB_HOST', '')
|
STARRS_DB_HOST = environ.get('PROXSTAR_STARRS_DB_HOST', '')
|
||||||
|
@ -50,3 +51,7 @@ SQLALCHEMY_DATABASE_URI = environ.get('PROXSTAR_SQLALCHEMY_DATABASE_URI', '')
|
||||||
# REDIS
|
# REDIS
|
||||||
REDIS_HOST = environ.get('PROXSTAR_REDIS_HOST', 'localhost')
|
REDIS_HOST = environ.get('PROXSTAR_REDIS_HOST', 'localhost')
|
||||||
REDIS_PORT = int(environ.get('PROXSTAR_REDIS_PORT', '6379'))
|
REDIS_PORT = int(environ.get('PROXSTAR_REDIS_PORT', '6379'))
|
||||||
|
|
||||||
|
# VNC
|
||||||
|
WEBSOCKIFY_PATH = environ.get('PROXSTAR_WEBSOCKIFY_PATH', '/opt/app-root/bin/websockify')
|
||||||
|
WEBSOCKIFY_TARGET_FILE = environ.get('PROXSTAR_WEBSOCKIFY_TARGET_FILE', '/opt/app-root/src/targets')
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
import atexit
|
||||||
import subprocess
|
import subprocess
|
||||||
from rq import Queue
|
from rq import Queue
|
||||||
from redis import Redis
|
from redis import Redis
|
||||||
|
@ -10,6 +11,7 @@ from werkzeug.contrib.cache import SimpleCache
|
||||||
from flask_pyoidc.flask_pyoidc import OIDCAuthentication
|
from flask_pyoidc.flask_pyoidc import OIDCAuthentication
|
||||||
from flask import Flask, render_template, request, redirect, send_from_directory, session
|
from flask import Flask, render_template, request, redirect, send_from_directory, session
|
||||||
from proxstar.db import *
|
from proxstar.db import *
|
||||||
|
from proxstar.vnc import *
|
||||||
from proxstar.util import *
|
from proxstar.util import *
|
||||||
from proxstar.tasks import *
|
from proxstar.tasks import *
|
||||||
from proxstar.starrs import *
|
from proxstar.starrs import *
|
||||||
|
@ -28,6 +30,13 @@ app.config.from_pyfile(config)
|
||||||
app.config["GIT_REVISION"] = subprocess.check_output(
|
app.config["GIT_REVISION"] = subprocess.check_output(
|
||||||
['git', 'rev-parse', '--short', 'HEAD']).decode('utf-8').rstrip()
|
['git', 'rev-parse', '--short', 'HEAD']).decode('utf-8').rstrip()
|
||||||
|
|
||||||
|
with open('proxmox_ssh_key', 'w') as key:
|
||||||
|
key.write(app.config['PROXMOX_SSH_KEY'])
|
||||||
|
|
||||||
|
start_websockify(app.config['WEBSOCKIFY_PATH'], app.config['WEBSOCKIFY_TARGET_FILE'])
|
||||||
|
|
||||||
|
ssh_tunnels = []
|
||||||
|
|
||||||
retry = 0
|
retry = 0
|
||||||
while retry < 5:
|
while retry < 5:
|
||||||
try:
|
try:
|
||||||
|
@ -132,6 +141,7 @@ def vm_details(vmid):
|
||||||
vm = get_vm(proxmox, vmid)
|
vm = get_vm(proxmox, vmid)
|
||||||
vm['vmid'] = vmid
|
vm['vmid'] = vmid
|
||||||
vm['config'] = get_vm_config(proxmox, vmid)
|
vm['config'] = get_vm_config(proxmox, vmid)
|
||||||
|
vm['node'] = get_vm_node(proxmox, vmid)
|
||||||
vm['disks'] = get_vm_disks(proxmox, vmid, config=vm['config'])
|
vm['disks'] = get_vm_disks(proxmox, vmid, config=vm['config'])
|
||||||
vm['iso'] = get_vm_iso(proxmox, vmid, config=vm['config'])
|
vm['iso'] = get_vm_iso(proxmox, vmid, config=vm['config'])
|
||||||
vm['interfaces'] = []
|
vm['interfaces'] = []
|
||||||
|
@ -179,6 +189,23 @@ def vm_power(vmid, action):
|
||||||
return '', 403
|
return '', 403
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/vm/<string:vmid>/console", methods=['POST'])
|
||||||
|
@auth.oidc_auth
|
||||||
|
def vm_console(vmid):
|
||||||
|
user = session['userinfo']['preferred_username']
|
||||||
|
rtp = 'rtp' in session['userinfo']['groups']
|
||||||
|
proxmox = connect_proxmox()
|
||||||
|
if rtp or int(vmid) in get_user_allowed_vms(proxmox, db, user):
|
||||||
|
port = str(5900 + int(vmid))
|
||||||
|
token = add_vnc_target(port)
|
||||||
|
node = "{}.csh.rit.edu".format(get_vm_node(proxmox, vmid))
|
||||||
|
tunnel = start_ssh_tunnel(node, port)
|
||||||
|
ssh_tunnels.append(tunnel)
|
||||||
|
return token, 200
|
||||||
|
else:
|
||||||
|
return '', 403
|
||||||
|
|
||||||
|
|
||||||
@app.route("/vm/<string:vmid>/cpu/<int:cores>", methods=['POST'])
|
@app.route("/vm/<string:vmid>/cpu/<int:cores>", methods=['POST'])
|
||||||
@auth.oidc_auth
|
@auth.oidc_auth
|
||||||
def vm_cpu(vmid, cores):
|
def vm_cpu(vmid, cores):
|
||||||
|
@ -420,23 +447,23 @@ def template_disk(template_id):
|
||||||
return get_template_disk(db, template_id)
|
return get_template_disk(db, template_id)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/vm/<string:vmid>/rrd/<path:path>')
|
|
||||||
@auth.oidc_auth
|
|
||||||
def send_rrd(vmid, path):
|
|
||||||
return send_from_directory("rrd/{}".format(vmid), path)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/novnc/<path:path>')
|
|
||||||
@auth.oidc_auth
|
|
||||||
def send_novnc(path):
|
|
||||||
return send_from_directory('static/novnc-pve/novnc', path)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/logout")
|
@app.route("/logout")
|
||||||
@auth.oidc_logout
|
@auth.oidc_logout
|
||||||
def logout():
|
def logout():
|
||||||
return redirect(url_for('list_vms'), 302)
|
return redirect(url_for('list_vms'), 302)
|
||||||
|
|
||||||
|
|
||||||
|
def exit_handler():
|
||||||
|
stop_websockify()
|
||||||
|
for tunnel in ssh_tunnels:
|
||||||
|
try:
|
||||||
|
tunnel.stop()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
atexit.register(exit_handler)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run()
|
app.run()
|
||||||
|
|
|
@ -287,12 +287,6 @@ def get_pools(proxmox, db):
|
||||||
return pools
|
return pools
|
||||||
|
|
||||||
|
|
||||||
def get_rrd_for_vm(proxmox, vmid, source, time):
|
|
||||||
node = proxmox.nodes(get_vm_node(proxmox, vmid))
|
|
||||||
image = node.qemu(vmid).rrd.get(ds=source, timeframe=time)['image']
|
|
||||||
return image
|
|
||||||
|
|
||||||
|
|
||||||
def delete_user_pool(proxmox, pool):
|
def delete_user_pool(proxmox, pool):
|
||||||
proxmox.pools(pool).delete()
|
proxmox.pools(pool).delete()
|
||||||
users = proxmox.access.users.get()
|
users = proxmox.access.users.get()
|
||||||
|
|
|
@ -105,13 +105,6 @@ table, th, td {
|
||||||
margin: 0px auto;
|
margin: 0px auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rrd-graph {
|
|
||||||
width: 800px;
|
|
||||||
height: 200px;
|
|
||||||
padding-top: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-img {
|
.user-img {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
|
|
|
@ -802,3 +802,23 @@ function hide_for_template(obj) {
|
||||||
hide_area.style.display = 'none';
|
hide_area.style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$("#console-vm").click(function(){
|
||||||
|
const vmname = $(this).data('vmname');
|
||||||
|
const vmid = $(this).data('vmid');
|
||||||
|
fetch(`/vm/${vmid}/console`, {
|
||||||
|
credentials: 'same-origin',
|
||||||
|
method: 'post'
|
||||||
|
}).then((response) => {
|
||||||
|
return response.text()
|
||||||
|
}).then((token) => {
|
||||||
|
window.location = `/static/noVNC/vnc.html?autoconnect=true&?encrypt=true&?host=proxstar.csh.rit.edu&?port=8081&?path=path?token=${token}`;
|
||||||
|
}).catch(err => {
|
||||||
|
if (err) {
|
||||||
|
swal("Uh oh...", `Unable to start console for ${vmname}. Please try again later.`, "error");
|
||||||
|
} else {
|
||||||
|
swal.stopLoading();
|
||||||
|
swal.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
1
proxstar/static/noVNC
Submodule
1
proxstar/static/noVNC
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 37b4d13db81e0e80e117c07b86ff98714c7b6b1a
|
|
@ -21,7 +21,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if vm['qmpstatus'] == 'running' or vm['qmpstatus'] == 'paused' %}
|
{% if vm['qmpstatus'] == 'running' or vm['qmpstatus'] == 'paused' %}
|
||||||
{% if vm['qmpstatus'] == 'running' %}
|
{% if vm['qmpstatus'] == 'running' %}
|
||||||
<a href="https://proxmox01.csh.rit.edu/#v1:0:=qemu%2F{{ vm['vmid'] }}:4::::8::" target="_blank">Open VM Console</a>
|
<button class="btn btn-success proxstar-actionbtn" id="console-vm" name="console" data-vmid="{{ vm['vmid'] }}" data-vmname="{{ vm['name'] }}">CONSOLE</button>
|
||||||
<button class="btn btn-info proxstar-actionbtn" id="suspend-vm" name="suspend" data-vmid="{{ vm['vmid'] }}" data-vmname="{{ vm['name'] }}">SUSPEND</button>
|
<button class="btn btn-info proxstar-actionbtn" id="suspend-vm" name="suspend" data-vmid="{{ vm['vmid'] }}" data-vmname="{{ vm['name'] }}">SUSPEND</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button class="btn btn-info proxstar-actionbtn" id="shutdown-vm" name="shutdown" data-vmid="{{ vm['vmid'] }}" data-vmname="{{ vm['name'] }}">SHUTDOWN</button>
|
<button class="btn btn-info proxstar-actionbtn" id="shutdown-vm" name="shutdown" data-vmid="{{ vm['vmid'] }}" data-vmname="{{ vm['name'] }}">SHUTDOWN</button>
|
||||||
|
@ -82,6 +82,8 @@
|
||||||
<dd>{{ vm['vmid'] }}</dd>
|
<dd>{{ vm['vmid'] }}</dd>
|
||||||
<dt>Status</dt>
|
<dt>Status</dt>
|
||||||
<dd>{{ vm['qmpstatus'] }}</dd>
|
<dd>{{ vm['qmpstatus'] }}</dd>
|
||||||
|
<dt>Node</dt>
|
||||||
|
<dd>{{ vm['node'] }}</dd>
|
||||||
<dt>Cores</dt>
|
<dt>Cores</dt>
|
||||||
<dd>
|
<dd>
|
||||||
{{ vm['config']['cores'] * vm['config'].get('sockets', 1) }}
|
{{ vm['config']['cores'] * vm['config'].get('sockets', 1) }}
|
||||||
|
|
71
proxstar/vnc.py
Normal file
71
proxstar/vnc.py
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
from sshtunnel import SSHTunnelForwarder
|
||||||
|
from proxstar.util import *
|
||||||
|
from flask import current_app as app
|
||||||
|
|
||||||
|
|
||||||
|
def start_websockify(websockify_path, target_file):
|
||||||
|
result = subprocess.run(['pgrep', 'websockify'], stdout=subprocess.PIPE)
|
||||||
|
if not result.stdout:
|
||||||
|
subprocess.call(
|
||||||
|
[
|
||||||
|
websockify_path, '8081', '--token-plugin',
|
||||||
|
'TokenFile', '--token-source', target_file,
|
||||||
|
'-D'
|
||||||
|
],
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
|
|
||||||
|
|
||||||
|
def stop_websockify():
|
||||||
|
result = subprocess.run(['pgrep', 'websockify'], stdout=subprocess.PIPE)
|
||||||
|
if result.stdout:
|
||||||
|
pid = result.stdout.strip()
|
||||||
|
subprocess.run(['kill', pid], stdout=subprocess.PIPE)
|
||||||
|
time.sleep(3)
|
||||||
|
if subprocess.run(
|
||||||
|
['pgrep', 'websockify'], stdout=subprocess.PIPE).stdout:
|
||||||
|
time.sleep(10)
|
||||||
|
if subprocess.run(
|
||||||
|
['pgrep', 'websockify'], stdout=subprocess.PIPE).stdout:
|
||||||
|
print('Websockify didn\'t stop, killing forcefully.')
|
||||||
|
subprocess.run(['kill', '-9', pid], stdout=subprocess.PIPE)
|
||||||
|
|
||||||
|
|
||||||
|
def get_vnc_targets():
|
||||||
|
target_file = open(app.config['WEBSOCKIFY_TARGET_FILE'])
|
||||||
|
targets = []
|
||||||
|
for line in target_file:
|
||||||
|
target_dict = dict()
|
||||||
|
values = line.strip().split(':')
|
||||||
|
target_dict['token'] = values[0]
|
||||||
|
target_dict['port'] = values[2]
|
||||||
|
targets.append(target_dict)
|
||||||
|
target_file.close()
|
||||||
|
return (targets)
|
||||||
|
|
||||||
|
|
||||||
|
def add_vnc_target(port):
|
||||||
|
targets = get_vnc_targets()
|
||||||
|
target = next((target for target in targets if target['port'] == port),
|
||||||
|
None)
|
||||||
|
if target:
|
||||||
|
return target['token']
|
||||||
|
else:
|
||||||
|
target_file = open(app.config['WEBSOCKIFY_TARGET_FILE'], 'a')
|
||||||
|
token = gen_password(32, 'abcdefghijklmnopqrstuvwxyz0123456789')
|
||||||
|
target_file.write("{}: 127.0.0.1:{}\n".format(token, str(port)))
|
||||||
|
target_file.close()
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
def start_ssh_tunnel(node, port):
|
||||||
|
port = int(port)
|
||||||
|
server = SSHTunnelForwarder(
|
||||||
|
node,
|
||||||
|
ssh_username='root',
|
||||||
|
ssh_pkey='proxmox_ssh_key',
|
||||||
|
remote_bind_address=('127.0.0.1', port),
|
||||||
|
local_bind_address=('127.0.0.1', port))
|
||||||
|
server.start()
|
||||||
|
return server
|
|
@ -10,3 +10,4 @@ rq-scheduler==0.7.0
|
||||||
gunicorn
|
gunicorn
|
||||||
raven
|
raven
|
||||||
paramiko
|
paramiko
|
||||||
|
websockify
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue