Shared Pools

This commit is contained in:
Joe Abbate 2022-09-25 00:58:29 -04:00
parent 50855e3712
commit dff517d822
No known key found for this signature in database
GPG key ID: 7F1CC23828058430
12 changed files with 349 additions and 16 deletions

View file

@ -266,6 +266,18 @@ ALTER TABLE ONLY public.vm_expiration
ADD CONSTRAINT vm_expiration_pkey PRIMARY KEY (id);
--
-- Name: allowed_users; Type: TABLE; Schema: public; Owner: proxstar
--
CREATE TABLE public.shared_pools (
name VARCHAR(32) PRIMARY KEY,
members VARCHAR(32)[]
);
ALTER TABLE public.shared_pools OWNER TO proxstar;
--
-- Name: DATABASE proxstar; Type: ACL; Schema: -; Owner: postgres
--

View file

@ -44,6 +44,9 @@ from proxstar.db import (
delete_allowed_user,
get_template_disk,
set_template_info,
add_shared_pool,
get_shared_pool,
get_shared_pools
)
from proxstar.vnc import (
add_vnc_target,
@ -197,7 +200,25 @@ def list_vms(user_view=None):
vms.append(pending_vm)
else:
vms = 'INACTIVE'
return render_template('list_vms.html', user=user, user_view=user_view, vms=vms)
return render_template('list_vms.html', user=user, external_view=user_view, vms=vms)
@app.route('/pool/shared/<string:name>')
@auth.oidc_auth
def list_shared_vms(name=None):
user = User(session['userinfo']['preferred_username'])
pool = get_shared_pool(db, name)
if pool:
if user.name in pool.members or user.rtp:
proxmox = connect_proxmox()
vms = proxmox.pools(pool.name).get()['members']
else:
return 'Not Member of Pool', 403
else:
return 'Pool does not exist', 400
if app.config['FORCE_STANDARD_USER']:
user.rtp = False
return render_template('list_vms.html', user=user, external_view=pool, vms=vms)
@app.route('/pools')
@ -205,11 +226,10 @@ def list_pools():
user = User(session['userinfo']['preferred_username'])
if app.config['FORCE_STANDARD_USER']:
user.rtp = False
if not user.rtp:
abort(403)
connect_proxmox()
vms = get_pool_cache(db)
return render_template('list_pools.html', user=user, vms=vms)
proxmox = connect_proxmox()
user_pools = get_pool_cache(db) if user.rtp else []
shared_pools = map(lambda pool: {"name": pool.name, "members": pool.members, "vms": proxmox.pools(pool.name).get()['members']}, get_shared_pools(db, user.name, user.rtp))
return render_template('list_pools.html', user=user, user_pools=user_pools, shared_pools=shared_pools)
@app.route('/isos')
@ -463,9 +483,11 @@ def create():
if request.method == 'GET':
stored_isos = get_isos(proxmox, app.config['PROXMOX_ISO_STORAGE'])
pools = get_pools(proxmox, db)
for pool in get_shared_pools(db, user.name, True):
pools.append(pool.name)
templates = get_templates(db)
return render_template(
'create.html',
'create_vm.html',
user=user,
usage=user.usage,
limits=user.limits,
@ -585,6 +607,58 @@ def ignored_pools(pool):
else:
return '', 403
@app.route('/pool/shared/create', methods=['GET', 'POST'])
@auth.oidc_auth
def create_shared_pool():
user = User(session['userinfo']['preferred_username'])
if request.method == 'GET':
return render_template('create_pool.html',user=user)
elif request.method == 'POST':
name = request.form['name']
members = request.form['members'].split(';')
description = request.form['description']
if 'rtp' in session['userinfo']['groups']:
try:
proxmox = connect_proxmox()
proxmox.pools.post(poolid=name, comment=description)
except:
return 'Error creating pool', 400
add_shared_pool(db, name, members)
return '', 200
else:
return '', 403
@app.route('/pool/shared/<string:name>/modify', methods=['POST'])
@auth.oidc_auth
def modify_shared_pool(name):
members = request.form['members'].split(',')
if 'rtp' in session['userinfo']['groups']:
pool = get_shared_pool(db, name)
if pool:
pool.members = members
db.commit()
return '', 200
return 'Pool not found', 400
else:
return '', 403
@app.route('/pool/shared/<string:name>/delete', methods=['POST'])
@auth.oidc_auth
def delete_shared_pool(name):
if 'rtp' in session['userinfo']['groups']:
pool = get_shared_pool(db, name)
if pool:
db.delete(pool)
db.commit()
proxmox = connect_proxmox()
proxmox.pools(name).delete()
return '', 200
return 'Pool not found', 400
else:
return '', 403
@app.route('/user/<string:user>/allow', methods=['POST', 'DELETE'])
@auth.oidc_auth

View file

@ -14,6 +14,7 @@ from proxstar.models import (
Template,
Usage_Limit,
VM_Expiration,
Shared_Pools,
)
@ -212,3 +213,23 @@ def set_template_info(db, template_id, name, disk):
template.name = name
template.disk = disk
db.commit()
def add_shared_pool(db, name, members):
if db.query(Shared_Pools).get(name):
return "Name Already in Use"
db.add(Shared_Pools(name=name, members=members))
db.commit()
def get_shared_pool(db, name):
return db.query(Shared_Pools).get(name)
def get_shared_pools(db, user, all_pools):
if all_pools:
return db.query(Shared_Pools).all()
pools = []
for pool in db.query(Shared_Pools).filter(Shared_Pools.members.contains(f"{{{user}}}")).all():
pools.append(pool)
return pools

View file

@ -1,6 +1,7 @@
from sqlalchemy import Column, Date, Integer, String
from sqlalchemy.dialects import postgresql
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.mutable import MutableList
from sqlalchemy.types import JSON, Text
from proxstar.util import default_repr
@ -53,3 +54,9 @@ class Ignored_Pools(Base):
class Allowed_Users(Base):
__tablename__ = 'allowed_users'
id = Column(String(32), primary_key=True)
@default_repr
class Shared_Pools(Base):
__tablename__ = 'shared_pools'
name = Column(String(32), primary_key=True)
members = Column(MutableList.as_mutable(postgresql.ARRAY(String(32))))

View file

@ -2,7 +2,7 @@ from flask import current_app as app
from proxmoxer import ProxmoxAPI
from proxstar import logging
from proxstar.db import get_ignored_pools
from proxstar.db import get_ignored_pools, get_shared_pool
from proxstar.ldapdb import is_user

View file

@ -379,6 +379,54 @@ $("#create-vm").click(function(){
});
});
$("#create-pool").click(function(){
console.log("bingus");
const name = document.getElementById('name').value.toLowerCase();
const description = document.getElementById('description').value;
const members = document.getElementById('members').value;
var info = document.createElement('span');
swal({
title: `Are you sure you want to create ${name}?`,
content: info,
icon: "info",
buttons: {
cancel: true,
confirm: {
text: "Create",
closeModal: false,
}
}
})
.then((willCreate) => {
if (willCreate) {
var data = new FormData();
data.append('name', name);
data.append('description', description);
data.append('members', members);
fetch('/pool/shared/create', {
credentials: 'same-origin',
method: 'POST',
body: data
}).then((response) => {
console.log(response);
var swal_text = `${name} is now being created. Check back soon and it should be good to go.`
return swal(`${swal_text}`, {
icon: "success",
buttons: {
ok: {
text: "OK",
closeModal: true,
className: "",
}
}
});
}).then(() => {
window.location = "/";
});
}
});
});
$("#change-cores").click(function(){
const vmid = $(this).data('vmid');
const usage = $(this).data('usage');
@ -569,6 +617,67 @@ $(".edit-limit").click(function(){
});
});
$(".edit-shared-members").click(function(){
const pool = $(this).data('pool');
const currentMembers = $(this).data('members').slice(1,-1).split(', ');
var currentMembersString = "";
currentMembers.forEach(name => {
currentMembersString += name.slice(1,-1) + ',';
});
var options = document.createElement('div');
var members = document.createElement('input');
members.type = 'text';
members.defaultValue = currentMembersString.slice(0,-1);
options.append(members);
swal({
title: `Enter the new member list for ${pool}:`,
content: options,
buttons: {
cancel: {
text: "Cancel",
visible: true,
closeModal: true,
className: "",
},
confirm: {
text: "Submit",
closeModal: false,
}
},
})
.then((willChange) => {
if (willChange) {
var data = new FormData();
data.append('members', $(members).val());
fetch(`/pool/shared/${pool}/modify`, {
credentials: 'same-origin',
method: 'post',
body: data
}).then((response) => {
return swal(`Now applying new member list to ${pool}!`, {
icon: "success",
buttons: {
ok: {
text: "OK",
closeModal: true,
className: "",
}
}
});
}).then(() => {
window.location = "/";
});
}
}).catch(err => {
if (err) {
swal("Uh oh...", `Unable to change the members of ${pool}. Please try again later.`, "error");
} else {
swal.stopLoading();
swal.close();
}
});
});
$(".delete-user").click(function(){
const user = $(this).data('user');
swal({
@ -607,6 +716,44 @@ $(".delete-user").click(function(){
});
});
$(".delete-pool").click(function(){
const pool = $(this).data('pool');
swal({
title: `Are you sure you want to delete the pool ${pool}?`,
icon: "warning",
buttons: {
cancel: true,
delete: {
text: "delete",
closeModal: false,
className: "swal-button--danger",
}
},
dangerMode: true,
})
.then((willDelete) => {
if (willDelete) {
fetch(`/pool/shared/${ pool }/delete`, {
credentials: 'same-origin',
method: 'post'
}).then((response) => {
return swal(`The pool ${pool} has been deleted!`, {
icon: "success",
});
}).then(() => {
window.location = "/";
}).catch(err => {
if (err) {
swal("Uh oh...", `Unable to delete the pool ${pool}. Please try again later.`, "error");
} else {
swal.stopLoading();
swal.close();
}
});
}
});
});
$(".delete-ignored-pool").click(function(){
const pool = $(this).data('pool');
fetch(`/pool/${pool}/ignore`, {

View file

@ -54,11 +54,17 @@
Create VM
</a>
</li>
{% if user.rtp %}
<li class="nav-item navbar-user dropdown">
<a class="nav-link" href="/pools">
<i class="fas fa-user"></i>
User Pools
Pools
</a>
</li>
{% if user.rtp %}
<li class="nav-item navbar-user dropdown">
<a class="nav-link" href="/pool/shared/create">
<i class="fas fa-plus-circle"></i>
Create Shared Pool
</a>
</li>
<li class="nav-item navbar-user dropdown">

View file

@ -0,0 +1,37 @@
{% extends "base.html" %}
{% block body %}
<div class="container">
<div class="row">
{% if user['rtp'] %}
<div class="col-md-12 col-sm-12">
{% else %}
<div class="col-lg-9 col-md-8 col-sm-12">
{% endif %}
<div class="card bg-light mb-3">
<div class="card-header">
<h3 class="card-title">Create Shared Pool</h3>
</div>
<div class="card-body">
<div class="form-group">
<label for="name" class="pull-left">Pool Name</label>
<input type="text" name="name" id="name" class="form-control">
</div>
<div class="form-group">
<label for="description" class="pull-left">Description</label>
<input type="text" name="description" id="description" class="form-control">
</div>
<div class="form-group">
<label for="members" class="pull-left">Members</label>
<input type="text" name="members" id="members" class="form-control">
</div>
<div class="text-center">
<button class="btn btn-success" id="create-pool" name="create">CREATE</button>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,9 +1,8 @@
{% extends "base.html" %}
{% block body %}
<div class="container">
<div class="row">
{% for pool in vms %}
{% for pool in user_pools %}
<div class="col-lg-3 col-md-4 col-sm-6 col-xs-12">
<div class="card bg-light mb-3">
<div class="card-header text-center">
@ -56,7 +55,30 @@
<button class="btn btn-info proxstar-poolbtn edit-limit" data-user="{{ pool['user'] }}" data-cpu="{{ pool['limits']['cpu'] }}" data-mem="{{ pool['limits']['mem'] }}" data-disk="{{ pool['limits']['disk'] }}">EDIT</button>
{% if not pool['vms'] %}
<button class="btn btn-danger proxstar-poolbtn delete-user" data-user="{{ pool['user'] }}">DELETE</button>
{% endif %}
{% endif %}
</div>
</div>
</div>
</div>
{% endfor %}
{% for pool in shared_pools %}
<div class="col-lg-3 col-md-4 col-sm-6 col-xs-12">
<div class="card bg-light mb-3">
<div class="card-header text-center">
<h5 class="card-title user-title">
<a href="/pool/{{ pool.name }}">
{{ pool.name }}
</a>
</h5>
</div>
<div class="card-body">
<div class="text-center">
<h6>{{ pool.members|length }} members</h5>
<button class="btn btn-info proxstar-poolbtn edit-shared-members" data-pool="{{ pool.name }}" data-members="{{ pool.members }}">EDIT</button>
{% if not pool['vms'] %}
<button class="btn btn-danger proxstar-poolbtn delete-pool" data-pool="{{ pool.name }}">DELETE</button>
{% endif %}
</div>
</div>
</div>

View file

@ -3,11 +3,11 @@
<div class="container">
<div class="row">
{% if user_view %}
{% if external_view %}
<div class="col-md-12 col-sm-12">
<div class="card bg-light mb-3">
<div class="card-header text-center">
<h5 class="card-title">{{ user_view.name }}</h5>
<h5 class="card-title">{{ external_view.name }}</h5>
</div>
</div>
</div>

View file

@ -3,7 +3,7 @@ from rq.registry import StartedJobRegistry
from proxstar.ldapdb import is_active, is_user, is_current_student
from proxstar import db, q, redis_conn
from proxstar.db import get_allowed_users, get_user_usage_limits, is_rtp
from proxstar.db import get_allowed_users, get_user_usage_limits, is_rtp, get_shared_pools
from proxstar.proxmox import connect_proxmox, get_pools
from proxstar.util import lazy_property, default_repr
from proxstar.vm import VM
@ -39,6 +39,7 @@ class User:
if 'name' not in vm:
vms.remove(vm)
vms = sorted(vms, key=lambda k: k['name'])
return vms
@lazy_property
@ -63,6 +64,12 @@ class User:
allowed_vms = []
for vm in self.vms:
allowed_vms.append(vm['vmid'])
shared_pools = get_shared_pools(db, self.name, False)
proxmox = connect_proxmox()
for pool in shared_pools:
vms = proxmox.pools(pool.name).get()['members']
for vm in vms:
allowed_vms.append(vm['vmid'])
return allowed_vms
@lazy_property