Skip to content

Commit 2c17d69

Browse files
authored
Update VNC functionality for Proxmox 7 (#148)
Chown `targets`, Add run and kill scripts Lol Joe figured it out * Dude it works holy shit We need to fix some logistical bugs, probably, and also like remove dead code lol * Open VNC session on the node that the VM belongs Figured out why I couldn't open a session on anything but 01. It was because I was making the API call on proxmox01-nrh. So that's where the session opened. I hope that by doing this, it will balance the load (what little there is) from VNC sessions. * Update websockify-related tasks * Remove SSH key from build * Add option to specify VNC port. Should be 443 for OKD, probably 8081 for development. This hosts a smattering of fixes, acutally uses gunicorn properly(?), launches websockify correctly, and introduces MORE DEAD CODE! TODO: Fix the scheduling system * Make things not crash as much :) * Remove obviously dead code There's still some code in here that may require more careful extraction, testing, and review, so I'm saving that for another PR. * Fix Joe's complaints * Replace hardcoded URL
1 parent 3bad0f0 commit 2c17d69

File tree

15 files changed

+213
-144
lines changed

15 files changed

+213
-144
lines changed

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ COPY start_worker.sh start_scheduler.sh .
77
COPY .git ./.git
88
COPY *.py .
99
COPY proxstar ./proxstar
10-
RUN touch proxmox_ssh_key && chmod a+w proxmox_ssh_key # This is some OKD shit.
11-
ENTRYPOINT ddtrace-run python3 wsgi.py
10+
RUN touch proxmox_ssh_key targets && chmod a+w proxmox_ssh_key targets # This is some OKD shit.
11+
ENTRYPOINT ddtrace-run gunicorn proxstar:app --bind=0.0.0.0:8080

HACKING/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
volume/
22
volume/*
3-
.env
3+
.env
4+
ssh_key

HACKING/README.md

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,43 @@
1+
# Contributing
2+
1. [Fork](https://help.github.com/en/articles/fork-a-repo) this repository
3+
- Optionally create a new [git branch](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell) if your change is more than a small tweak (`git checkout -b BRANCH-NAME-HERE`)
4+
5+
2. Follow the _Podman Environment Instructions_ to set up a Podman dev environment. If you'd like to run Proxstar entirely on your own hardware, check out _Setting up a full dev environment_
6+
7+
3. Create a Virtualenv to do your linting in
8+
```
9+
mkdir venv
10+
python3.8 -m venv venv
11+
source venv/bin/activate
12+
```
13+
14+
4. Make your changes locally, commit, and push to your fork
15+
- If you want to test locally, you should copy `HACKING/.env.sample` to `HACKING/.env`, and talk to an RTP about filling in secrets.
16+
- Lint and format your local changes with `pylint proxstar` and `black proxstar`
17+
- You'll need dependencies installed locally to do this. You should do that in a [venv](https://packaging.python.org/tutorials/installing-packages/#creating-virtual-environments) of some sort to keep your system clean. All the dependencies are listed in [requirements.txt](./requirements.txt), so you can install everything with `pip install -r requirements.txt`. You'll need python 3.6 at minimum, though things should work up to python 3.8.
18+
19+
5. Create a [Pull Request](https://help.github.com/en/articles/about-pull-requests) on this repo for our Webmasters to review
20+
21+
### Podman Environment Instructions
22+
23+
1. Build your containers. The `proxstar` container serves as proxstar, rq, rq-scheduler, and VNC. The `proxstar-postgres` container sets up the database schema.
24+
25+
`mkdir HACKING/proxstar-postgres/volume`
26+
27+
`podman build . --tag=proxstar`
28+
29+
`podman build HACKING/proxstar-postgres --tag=proxstar-postgres`
30+
31+
2. Configure your environment variables. I'd recommend setting up a .env file and passing that into your container. Check `.env.template` for more info.
32+
33+
3. Run it. This sets up redis, postgres, rq, and proxstar.
34+
35+
`./HACKING/launch_env.sh`
36+
37+
4. To stop all containers, use the provided script
38+
39+
`./HACKING/stop_env.sh`
40+
141
## Setting up a full dev environment
242

343
If you want to work on Proxstar using a 1:1 development setup, there are a couple things you're going to need
@@ -6,9 +46,11 @@ If you want to work on Proxstar using a 1:1 development setup, there are a coupl
646
- SSH into
747
- With portforwarding (see `man ssh` for info on the `-L` option)
848
- and run
49+
- Podman
950
- Flask
10-
- Redis
11-
- Docker
51+
- Redis
52+
- Postgres
53+
- RQ
1254
- At least one (1) Proxmox host running Proxmox >6.3
1355
- A CSH account
1456
- An RTP (to tell you secrets)
@@ -25,24 +67,4 @@ If you're trying to run this all on a VM without a graphical web browser, you ca
2567
```
2668
ssh [email protected] -L 8000:localhost:8000
2769
```
28-
# New Deployment Instructions
29-
30-
1. Build your containers. The `proxstar` container serves as proxstar, rq, rq-scheduler, and VNC. The `proxstar-postgres` container sets up the database schema.
31-
32-
`mkdir HACKING/proxstar-postgres/volume`
33-
34-
`podman build . --tag=proxstar`
35-
36-
`podman build HACKING/proxstar-postgres --tag=proxstar-postgres`
37-
38-
2. Configure your environment variables. I'd recommend setting up a .env file and passing that into your container. Check `.env.template` for more info.
3970

40-
3. Run it. This sets up redis, postgres, rq, and proxstar.
41-
42-
```
43-
podman run --rm -d --network=proxstar --name=proxstar-redis redis:alpine
44-
podman run --rm -d --network=proxstar --name=proxstar-postgres -e POSTGRES_PASSWORD=changeme -v ./HACKING/proxstar-postgres/volume:/var/lib/postgresql/data:Z proxstar-postgres
45-
podman run --rm -d --network=proxstar --name=proxstar-rq-scheduler --env-file=HACKING/.env --entrypoint ./start_scheduler.sh proxstar
46-
podman run --rm -d --network=proxstar --name=proxstar-rq --env-file=HACKING/.env --entrypoint ./start_worker.sh proxstar
47-
podman run --rm -d --network=proxstar --name=proxstar -p 8000:8000 --env-file=HACKING/.env proxstar
48-
```

HACKING/launch_env.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/bash
2+
podman run --rm -d --network=proxstar --name=proxstar-redis redis:alpine
3+
podman run --rm -d --network=proxstar --name=proxstar-postgres -e POSTGRES_PASSWORD=changeme -v ./HACKING/proxstar-postgres/volume:/var/lib/postgresql/data:Z proxstar-postgres
4+
podman run --rm -d --network=proxstar --name=proxstar-rq-scheduler --env-file=HACKING/.env --entrypoint ./start_scheduler.sh proxstar
5+
podman run --rm -d --network=proxstar --name=proxstar-rq --env-file=HACKING/.env --entrypoint ./start_worker.sh proxstar
6+
podman run --rm -it --network=proxstar --name=proxstar -p 8000:8000 -p 8081:8081 --env-file=HACKING/.env --entrypoint='["gunicorn", "proxstar:app", "--bind=0.0.0.0:8000"]' proxstar

HACKING/stop_env.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/bash
2+
podman kill proxstar
3+
podman kill proxstar-rq
4+
podman kill proxstar-rq-scheduler
5+
podman stop proxstar-redis
6+
podman stop proxstar-postgres

README.md

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,7 @@ It is available to house members at [proxstar.csh.rit.edu](https://proxstar.csh.
1717

1818
## Contributing
1919

20-
1. [Fork](https://help.github.com/en/articles/fork-a-repo) this repository
21-
- Optionally create a new [git branch](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell) if your change is more than a small tweak (`git checkout -b BRANCH-NAME-HERE`)
22-
3. Make your changes locally, commit, and push to your fork
23-
- If you want to test locally, you should copy `config.py` to `config_local.py`, and talk to an RTP about filling in secrets.
24-
- Lint and format your local changes with `pylint proxstar` and `black proxstar`
25-
- You'll need dependencies installed locally to do this. You should do that in a [venv](https://packaging.python.org/tutorials/installing-packages/#creating-virtual-environments) of some sort to keep your system clean. All the dependencies are listed in [requirements.txt](./requirements.txt), so you can install everything with `pip install -r requirements.txt`. You'll need python 3.6 at minimum, though things should work up to python 3.8.
26-
4. Create a [Pull Request](https://help.github.com/en/articles/about-pull-requests) on this repo for our Webmasters to review
20+
Check out `HACKING/` for more info.
2721

2822
## Questions/Concerns
2923

wsgi.py renamed to app.py

File renamed without changes.

config.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,10 @@
6262
REDIS_PORT = int(environ.get('PROXSTAR_REDIS_PORT', '6379'))
6363

6464
# VNC
65-
WEBSOCKIFY_PATH = environ.get('PROXSTAR_WEBSOCKIFY_PATH', '/opt/app-root/bin/websockify')
66-
WEBSOCKIFY_TARGET_FILE = environ.get('PROXSTAR_WEBSOCKIFY_TARGET_FILE', '/opt/app-root/src/targets')
65+
WEBSOCKIFY_PATH = environ.get('PROXSTAR_WEBSOCKIFY_PATH', '/usr/local/bin/websockify')
66+
WEBSOCKIFY_TARGET_FILE = environ.get('PROXSTAR_WEBSOCKIFY_TARGET_FILE', '/opt/proxstar/targets')
67+
VNC_HOST = environ.get('PROXSTAR_VNC_HOST', 'proxstar-vnc.csh.rit.edu')
68+
VNC_PORT = environ.get('PROXSTAR_VNC_PORT', '443')
6769

6870
# SENTRY
6971
# If you set the sentry dsn locally, make sure you use the local-dev or some

gunicorn_conf.py renamed to gunicorn.conf.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import subprocess
33

44
from flask import Flask
5-
65
app = Flask(__name__)
76
if os.path.exists(os.path.join(app.config.get('ROOT_DIR', os.getcwd()), "config_local.py")):
87
config = os.path.join(app.config.get('ROOT_DIR', os.getcwd()), "config_local.py")
@@ -16,6 +15,7 @@
1615
def start_websockify(websockify_path, target_file):
1716
result = subprocess.run(['pgrep', 'websockify'], stdout=subprocess.PIPE)
1817
if not result.stdout:
18+
print("Websockify is stopped. Starting websockify.")
1919
subprocess.call(
2020
[
2121
websockify_path,
@@ -28,7 +28,10 @@ def start_websockify(websockify_path, target_file):
2828
],
2929
stdout=subprocess.PIPE,
3030
)
31+
else:
32+
print("Websockify started.")
3133

3234

3335
def on_starting(server):
36+
print("Booting Websockify server in daemon mode...")
3437
start_websockify(app.config['WEBSOCKIFY_PATH'], app.config['WEBSOCKIFY_TARGET_FILE'])

proxstar/__init__.py

Lines changed: 53 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,25 @@
66
import subprocess
77
import psutil
88
import psycopg2
9+
10+
# from gunicorn_conf import start_websockify
911
import rq_dashboard
1012
from rq import Queue
1113
from redis import Redis
1214
from rq_scheduler import Scheduler
1315
from sqlalchemy import create_engine
1416
from sqlalchemy.orm import sessionmaker
15-
from flask import Flask, render_template, request, redirect, session, abort, url_for, jsonify
17+
from flask import (
18+
Flask,
19+
render_template,
20+
request,
21+
redirect,
22+
session,
23+
abort,
24+
url_for,
25+
jsonify,
26+
Response,
27+
)
1628
import sentry_sdk
1729
from sentry_sdk.integrations.flask import FlaskIntegration
1830
from sentry_sdk.integrations.rq import RqIntegration
@@ -34,13 +46,11 @@
3446
set_template_info,
3547
)
3648
from proxstar.vnc import (
37-
send_stop_ssh_tunnel,
38-
stop_ssh_tunnel,
3949
add_vnc_target,
40-
start_ssh_tunnel,
4150
get_vnc_targets,
4251
delete_vnc_target,
4352
stop_websockify,
53+
open_vnc_session,
4454
)
4555
from proxstar.auth import get_auth
4656
from proxstar.util import gen_password
@@ -67,8 +77,9 @@
6777
environment=app.config['SENTRY_ENV'],
6878
)
6979

70-
with open('proxmox_ssh_key', 'w') as ssh_key_file:
71-
ssh_key_file.write(app.config['PROXMOX_SSH_KEY'])
80+
if not os.path.exists('proxmox_ssh_key'):
81+
with open('proxmox_ssh_key', 'w') as ssh_key_file:
82+
ssh_key_file.write(app.config['PROXMOX_SSH_KEY'])
7283

7384
ssh_tunnels = []
7485

@@ -142,14 +153,22 @@ def rq_dashboard_auth(*args, **kwargs): # pylint: disable=unused-argument,unuse
142153

143154
@app.errorhandler(404)
144155
def not_found(e):
145-
user = User(session['userinfo']['preferred_username'])
146-
return render_template('404.html', user=user, e=e), 404
156+
try:
157+
user = User(session['userinfo']['preferred_username'])
158+
return render_template('404.html', user=user, e=e), 404
159+
except KeyError as exception:
160+
print(exception)
161+
return render_template('404.html', user='chom', e=e), 404
147162

148163

149164
@app.errorhandler(403)
150165
def forbidden(e):
151-
user = User(session['userinfo']['preferred_username'])
152-
return render_template('403.html', user=user, e=e), 403
166+
try:
167+
user = User(session['userinfo']['preferred_username'])
168+
return render_template('403.html', user=user, e=e), 403
169+
except KeyError as exception:
170+
print(exception)
171+
return render_template('403.html', user='chom', e=e), 403
153172

154173

155174
@app.route('/')
@@ -247,47 +266,43 @@ def vm_power(vmid, action):
247266
vm.start()
248267
elif action == 'stop':
249268
vm.stop()
250-
send_stop_ssh_tunnel(vmid)
269+
# TODO (willnilges): Replace with remove target function or something
270+
# send_stop_ssh_tunnel(vmid)
251271
elif action == 'shutdown':
252272
vm.shutdown()
253-
send_stop_ssh_tunnel(vmid)
273+
# send_stop_ssh_tunnel(vmid)
254274
elif action == 'reset':
255275
vm.reset()
256276
elif action == 'suspend':
257277
vm.suspend()
258-
send_stop_ssh_tunnel(vmid)
278+
# send_stop_ssh_tunnel(vmid)
259279
elif action == 'resume':
260280
vm.resume()
261281
return '', 200
262282
else:
263283
return '', 403
264284

265285

266-
@app.route('/console/vm/<string:vmid>/stop', methods=['POST'])
267-
def vm_console_stop(vmid):
268-
if request.form['token'] == app.config['VNC_CLEANUP_TOKEN']:
269-
stop_ssh_tunnel(vmid, ssh_tunnels)
270-
return '', 200
271-
else:
272-
return '', 403
273-
274-
275286
@app.route('/console/vm/<string:vmid>', methods=['POST'])
276287
@auth.oidc_auth
277288
def vm_console(vmid):
278289
user = User(session['userinfo']['preferred_username'])
279290
connect_proxmox()
280291
if user.rtp or int(vmid) in user.allowed_vms:
292+
# import pdb; pdb.set_trace()
281293
vm = VM(vmid)
282-
stop_ssh_tunnel(vm.id, ssh_tunnels)
283-
port = str(5900 + int(vmid))
284-
token = add_vnc_target(port)
285-
node = '{}.csh.rit.edu'.format(vm.node)
286-
logging.info('creating SSH tunnel to %s for VM %s', node, vm.id)
287-
tunnel = start_ssh_tunnel(node, port)
288-
ssh_tunnels.append(tunnel)
289-
vm.start_vnc(port)
290-
return token, 200
294+
vnc_ticket, vnc_port = open_vnc_session(
295+
vmid, vm.node, app.config['PROXMOX_USER'], app.config['PROXMOX_PASS']
296+
)
297+
node = f'{vm.node}.csh.rit.edu'
298+
token = add_vnc_target(node, vnc_port)
299+
return {
300+
'host': app.config['VNC_HOST'],
301+
'port': app.config['VNC_PORT'],
302+
'token': token,
303+
'password': vnc_ticket,
304+
}, 200
305+
291306
else:
292307
return '', 403
293308

@@ -399,7 +414,7 @@ def delete(vmid):
399414
user = User(session['userinfo']['preferred_username'])
400415
connect_proxmox()
401416
if user.rtp or int(vmid) in user.allowed_vms:
402-
send_stop_ssh_tunnel(vmid)
417+
# send_stop_ssh_tunnel(vmid)
403418
# Submit the delete VM task to RQ
404419
q.enqueue(delete_vm_task, vmid)
405420
return '', 200
@@ -571,29 +586,12 @@ def allowed_users(user):
571586
@app.route('/console/cleanup', methods=['POST'])
572587
def cleanup_vnc():
573588
if request.form['token'] == app.config['VNC_CLEANUP_TOKEN']:
574-
for target in get_vnc_targets():
575-
tunnel = next(
576-
(tunnel for tunnel in ssh_tunnels if tunnel.local_bind_port == int(target['port'])),
577-
None,
578-
)
579-
if tunnel:
580-
if not next(
581-
(
582-
conn
583-
for conn in psutil.net_connections()
584-
if conn.laddr[1] == int(target['port']) and conn.status == 'ESTABLISHED'
585-
),
586-
None,
587-
):
588-
try:
589-
tunnel.stop()
590-
except:
591-
pass
592-
ssh_tunnels.remove(tunnel)
593-
delete_vnc_target(target['port'])
594-
return '', 200
595-
else:
596-
return '', 403
589+
print('Cleaning up targets file...')
590+
with open(app.config['WEBSOCKIFY_TARGET_FILE'], 'w') as targets:
591+
targets.truncate()
592+
return '', 200
593+
print('Got bad cleanup request')
594+
return '', 403
597595

598596

599597
@app.route('/template/<string:template_id>/disk')

proxstar/static/js/script.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -650,9 +650,11 @@ $("#console-vm").click(function(){
650650
credentials: 'same-origin',
651651
method: 'post'
652652
}).then((response) => {
653-
return response.text()
654-
}).then((token) => {
655-
window.open(`/static/noVNC/vnc.html?autoconnect=true&encrypt=true&host=proxstar-vnc.csh.rit.edu&port=443&path=path?token=${token}`, '_blank');
653+
return response.json()
654+
}).then((vnc_params) => {
655+
// TODO (willnilges): encrypt=true
656+
// TODO (willnilges): set host and port to an env variable
657+
window.open(`/static/noVNC/vnc.html?autoconnect=true&password=${vnc_params.password}&host=${vnc_params.host}&port=${vnc_params.port}&path=path?token=${vnc_params.token}`, '_blank');
656658
}).catch(err => {
657659
if (err) {
658660
swal("Uh oh...", `Unable to start console for ${vmname}. Please try again later.`, "error");

0 commit comments

Comments
 (0)