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