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"]
|
||||
path = proxstar/static/css/csh-material-bootstrap
|
||||
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_PASS = environ.get('PROXSTAR_PROXMOX_PASS', '')
|
||||
PROXMOX_ISO_STORAGE = environ.get('PROXSTAR_PROXMOX_ISO_STORAGE', 'nfs-iso')
|
||||
PROXMOX_SSH_KEY = environ.get('PROXSTAR_PROXMOX_SSH_KEY', '')
|
||||
|
||||
# STARRS
|
||||
STARRS_DB_HOST = environ.get('PROXSTAR_STARRS_DB_HOST', '')
|
||||
|
@ -50,3 +51,7 @@ SQLALCHEMY_DATABASE_URI = environ.get('PROXSTAR_SQLALCHEMY_DATABASE_URI', '')
|
|||
# REDIS
|
||||
REDIS_HOST = environ.get('PROXSTAR_REDIS_HOST', 'localhost')
|
||||
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 time
|
||||
import atexit
|
||||
import subprocess
|
||||
from rq import Queue
|
||||
from redis import Redis
|
||||
|
@ -10,6 +11,7 @@ from werkzeug.contrib.cache import SimpleCache
|
|||
from flask_pyoidc.flask_pyoidc import OIDCAuthentication
|
||||
from flask import Flask, render_template, request, redirect, send_from_directory, session
|
||||
from proxstar.db import *
|
||||
from proxstar.vnc import *
|
||||
from proxstar.util import *
|
||||
from proxstar.tasks import *
|
||||
from proxstar.starrs import *
|
||||
|
@ -28,6 +30,13 @@ app.config.from_pyfile(config)
|
|||
app.config["GIT_REVISION"] = subprocess.check_output(
|
||||
['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
|
||||
while retry < 5:
|
||||
try:
|
||||
|
@ -132,6 +141,7 @@ def vm_details(vmid):
|
|||
vm = get_vm(proxmox, vmid)
|
||||
vm['vmid'] = 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['iso'] = get_vm_iso(proxmox, vmid, config=vm['config'])
|
||||
vm['interfaces'] = []
|
||||
|
@ -179,6 +189,23 @@ def vm_power(vmid, action):
|
|||
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'])
|
||||
@auth.oidc_auth
|
||||
def vm_cpu(vmid, cores):
|
||||
|
@ -420,23 +447,23 @@ def template_disk(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")
|
||||
@auth.oidc_logout
|
||||
def logout():
|
||||
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__":
|
||||
app.run()
|
||||
|
|
|
@ -287,12 +287,6 @@ def get_pools(proxmox, db):
|
|||
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):
|
||||
proxmox.pools(pool).delete()
|
||||
users = proxmox.access.users.get()
|
||||
|
|
|
@ -105,13 +105,6 @@ table, th, td {
|
|||
margin: 0px auto;
|
||||
}
|
||||
|
||||
.rrd-graph {
|
||||
width: 800px;
|
||||
height: 200px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.user-img {
|
||||
border-radius: 50%;
|
||||
height: 25px;
|
||||
|
|
|
@ -802,3 +802,23 @@ function hide_for_template(obj) {
|
|||
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 %}
|
||||
{% if vm['qmpstatus'] == 'running' or vm['qmpstatus'] == 'paused' %}
|
||||
{% 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>
|
||||
{% endif %}
|
||||
<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>
|
||||
<dt>Status</dt>
|
||||
<dd>{{ vm['qmpstatus'] }}</dd>
|
||||
<dt>Node</dt>
|
||||
<dd>{{ vm['node'] }}</dd>
|
||||
<dt>Cores</dt>
|
||||
<dd>
|
||||
{{ 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
|
||||
raven
|
||||
paramiko
|
||||
websockify
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue