add ability to change boot order, a lot more cleanup from conversion to classes

This commit is contained in:
Jordan Rodgers 2018-03-18 22:18:20 -04:00
parent cefe605bb9
commit 64b8d3983e
7 changed files with 217 additions and 74 deletions

View file

@ -13,7 +13,6 @@ from sqlalchemy.orm import sessionmaker
from flask_pyoidc.flask_pyoidc import OIDCAuthentication
from flask import Flask, render_template, request, redirect, session, abort
from proxstar.db import *
from proxstar.vm import VM
from proxstar.vnc import *
from proxstar.util import gen_password
from proxstar.starrs import *
@ -64,6 +63,7 @@ starrs = psycopg2.connect(
app.config['STARRS_DB_NAME'], app.config['STARRS_DB_USER'],
app.config['STARRS_DB_HOST'], app.config['STARRS_DB_PASS']))
from proxstar.vm import VM
from proxstar.user import User
from proxstar.tasks import generate_pool_cache_task, process_expiring_vms_task, cleanup_vnc_task, delete_vm_task, create_vm_task, setup_template_task
@ -179,25 +179,11 @@ def vm_details(vmid):
proxmox = connect_proxmox()
if user.rtp or int(vmid) in user.allowed_vms:
vm = VM(vmid)
vm_dict = vm.info
vm_dict['vmid'] = vmid
vm_dict['config'] = vm.config
vm_dict['node'] = vm.node
vm_dict['disks'] = vm.get_disks()
vm_dict['iso'] = vm.get_iso()
vm_dict['interfaces'] = []
for interface in vm.get_interfaces():
vm_dict['interfaces'].append(
[interface[0],
get_ip_for_mac(starrs, interface[1])])
vm_dict['expire'] = get_vm_expire(
db, vmid, app.config['VM_EXPIRE_MONTHS']).strftime('%m/%d/%Y')
usage_check = user.check_usage(vm_dict['config']['cores'],
vm_dict['config']['memory'], 0)
usage_check = user.check_usage(vm.cpu, vm.mem, 0)
return render_template(
'vm_details.html',
user=user,
vm=vm_dict,
vm=vm,
usage=user.usage,
limits=user.limits,
usage_check=usage_check)
@ -338,8 +324,9 @@ def vm_renew(vmid):
if user.rtp or int(vmid) in user.allowed_vms:
vm = VM(vmid)
renew_vm_expire(db, vmid, app.config['VM_EXPIRE_MONTHS'])
for interface in vm.get_interfaces():
renew_ip(starrs, get_ip_for_mac(starrs, interface[1]))
for interface in vm.interfaces:
if interface[2] != 'No IP':
renew_ip(starrs, interface[2])
return '', 200
else:
return '', 403
@ -384,6 +371,22 @@ def delete(vmid):
return '', 403
@app.route("/vm/<string:vmid>/boot_order", methods=['POST'])
@auth.oidc_auth
def boot_order(vmid):
user = User(session['userinfo']['preferred_username'])
proxmox = connect_proxmox()
if user.rtp or int(vmid) in user.allowed_vms:
boot_order = []
for key, value in request.form.items():
boot_order.append(value)
vm = VM(vmid)
vm.set_boot_order(boot_order)
return '', 200
else:
return '', 403
@app.route("/vm/create", methods=['GET', 'POST'])
@auth.oidc_auth
def create():
@ -414,7 +417,10 @@ def create():
iso = "{}:iso/{}".format(app.config['PROXMOX_ISO_STORAGE'],
iso)
if not user.rtp:
usage_check = user.check_usage(0, 0, disk)
if template == 'none':
usage_check = user.check_usage(0, 0, disk)
else:
usage_check = user.check_usage(cores, memory, disk)
username = user.name
else:
usage_check = None

View file

@ -392,6 +392,8 @@ $("#create-vm").click(function(){
const template = document.getElementById('template').value;
const iso = document.getElementById('iso').value;
const user = document.getElementById('user');
const max_cpu = $(this).data('max_cpu');
const max_mem = $(this).data('max_mem');
const max_disk = $(this).data('max_disk');
var disk = document.getElementById('disk').value;
fetch(`/template/${template}/disk`, {
@ -407,6 +409,10 @@ $("#create-vm").click(function(){
if (name && disk) {
if (disk > max_disk) {
swal("Uh oh...", `You do not have enough disk resources available! Please lower the VM disk size to ${max_disk}GB or lower.`, "error");
} else if (template != 'none' && cores > max_cpu) {
swal("Uh oh...", `You do not have enough CPU resources available! Please lower the VM cores to ${max_cpu} or lower.`, "error");
} else if (template != 'none' && mem/1024 > max_mem) {
swal("Uh oh...", `You do not have enough memory resources available! Please lower the VM memory to ${max_mem}GB or lower.`, "error");
} else {
fetch(`/hostname/${name}`, {
credentials: 'same-origin',
@ -942,3 +948,84 @@ $(".edit-template").click(function(){
}
});
});
$("#edit-boot-order").click(function(){
const vmid = $(this).data('vmid');
const vmname = $(this).data('vmname');
const boot_order = $(this).data('boot_order');
var options = document.createElement('div');
for (i = 0; i < boot_order.length; i++) {
text = document.createElement('span');
text.innerHTML = `${i + 1}. `;
options.append(text);
var entry = document.createElement('select');
entry.setAttribute("id", `boot-order-${i + 1}`);
for (j = 0; j < boot_order.length; j++) {
entry.appendChild(new Option(boot_order[j], boot_order[j]));
}
entry.selectedIndex = i;
entry.setAttribute('style', 'width: 85px');
options.append(entry);
options.append(document.createElement('br'));
}
swal({
title: `Select the new boot order for ${vmname} (full shutdown required for settings to take effect):`,
content: options,
buttons: {
cancel: {
text: "Cancel",
visible: true,
closeModal: true,
},
confirm: {
text: "Submit",
closeModal: false,
}
},
})
.then((willChange) => {
if (willChange) {
var data = new FormData();
for (k = 0; k < boot_order.length; k++) {
e = document.getElementById(`boot-order-${k + 1}`);
data.append(`${k + 1}`, e.options[e.selectedIndex].value);
}
fetch(`/vm/${vmid}/boot_order`, {
credentials: 'same-origin',
method: 'post',
body: data
}).then((response) => {
return swal(`Now applying the new boot order to ${vmname}!`, {
icon: "success",
buttons: {
ok: {
text: "OK",
closeModal: true,
}
}
});
}).then(() => {
window.location = `/vm/${vmid}`;
});
}
}).catch(err => {
if (err) {
swal("Uh oh...", `Unable to change the boot order for ${vmname}. Please try again later.`, "error");
} else {
swal.stopLoading();
swal.close();
}
});
});
$(document).on('focus click', "[id^=boot-order-]", function() {
previous = $(this).val();
}).on('change', "[id^=boot-order-]", function() {
current = $(this).val();
id = $(this).attr("id");
$("[id^=boot-order-]").each(function() {
if ($(this).attr("id") != id && $(this).val() == current) {
$(this).val(previous);
}
});
});

View file

@ -98,15 +98,13 @@ def process_expiring_vms_task():
expiring_vms = []
vms = user.vms
for vm in vms:
vmid = vm['vmid']
expire = get_vm_expire(db, vmid,
app.config['VM_EXPIRE_MONTHS'])
days = (expire - datetime.date.today()).days
vm = VM(vm['vmid'])
days = (vm.expire - datetime.date.today()).days
if days in [10, 7, 3, 1, 0]:
name = VM(vmid).config['name']
expiring_vms.append([name, days])
name = vm.name
expiring_vms.append([vm.name, days])
if days == 0:
VM(vmid).stop()
vm.stop()
if expiring_vms:
send_vm_expire_email('com6056', expiring_vms)

View file

@ -71,7 +71,7 @@
</select>
</div>
{% endif %}
<button class="btn btn-success" id="create-vm" name="create" data-max_disk="{{ limits['disk'] - usage['disk'] }}">CREATE</button>
<button class="btn btn-success" id="create-vm" name="create" data-max_cpu="{{ limits['cpu'] - usage['cpu'] }}" data-max_mem="{{ limits['mem'] - usage['mem'] }}" data-max_disk="{{ limits['disk'] - usage['disk'] }}">CREATE</button>
{% endif %}
</div>
</div>

View file

@ -9,27 +9,27 @@
<h3 class="panel-title">Actions</h3>
</div>
<div class="panel-body">
{% if vm['qmpstatus'] == 'stopped' %}
{% if vm.qmpstatus == 'stopped' %}
{% if not usage_check %}
<button class="btn btn-success proxstar-actionbtn" id="start-vm" name="start" data-vmid="{{ vm['vmid'] }}" data-vmname="{{ vm['name'] }}">START</button>
<button class="btn btn-success proxstar-actionbtn" id="start-vm" name="start" data-vmid="{{ vm.id }}" data-vmname="{{ vm.name }}">START</button>
{% else %}
Insufficient resources to start VM.
{% endif %}
{% endif %}
{% if vm['qmpstatus'] == 'suspended' or vm['qmpstatus'] == 'paused' %}
<button class="btn btn-success proxstar-actionbtn" id="resume-vm" name="resume" data-vmid="{{ vm['vmid'] }}" data-vmname="{{ vm['name'] }}">RESUME</button>
{% if vm.qmpstatus == 'suspended' or vm.qmpstatus == 'paused' %}
<button class="btn btn-success proxstar-actionbtn" id="resume-vm" name="resume" data-vmid="{{ vm.id }}" data-vmname="{{ vm.name }}">RESUME</button>
{% endif %}
{% if vm['qmpstatus'] == 'running' or vm['qmpstatus'] == 'suspended' or vm['qmpstatus'] == 'paused' %}
{% if vm['qmpstatus'] == 'running' %}
<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>
{% if vm.qmpstatus == 'running' or vm.qmpstatus == 'suspended' or vm.qmpstatus == 'paused' %}
{% if vm.qmpstatus == 'running' %}
<button class="btn btn-success proxstar-actionbtn" id="console-vm" name="console" data-vmid="{{ vm.id }}" data-vmname="{{ vm.name }}">CONSOLE</button>
<button class="btn btn-info proxstar-actionbtn" id="suspend-vm" name="suspend" data-vmid="{{ vm.id }}" 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>
<button class="btn btn-warning proxstar-actionbtn" id="stop-vm" name="stop" data-vmid="{{ vm['vmid'] }}" data-vmname="{{ vm['name'] }}">STOP</button>
<button class="btn btn-warning proxstar-actionbtn" id="reset-vm" name="reset" data-vmid="{{ vm['vmid'] }}" data-vmname="{{ vm['name'] }}">RESET</button>
<button class="btn btn-info proxstar-actionbtn" id="shutdown-vm" name="shutdown" data-vmid="{{ vm.id }}" data-vmname="{{ vm.name }}">SHUTDOWN</button>
<button class="btn btn-warning proxstar-actionbtn" id="stop-vm" name="stop" data-vmid="{{ vm.id }}" data-vmname="{{ vm.name }}">STOP</button>
<button class="btn btn-warning proxstar-actionbtn" id="reset-vm" name="reset" data-vmid="{{ vm.id }}" data-vmname="{{ vm.name }}">RESET</button>
{% endif %}
{% if vm['qmpstatus'] == 'stopped' %}
<button class="btn btn-danger proxstar-actionbtn" id="delete-vm" name="delete" data-vmid="{{ vm['vmid'] }}" data-vmname="{{ vm['name'] }}">DELETE</button>
{% if vm.qmpstatus == 'stopped' %}
<button class="btn btn-danger proxstar-actionbtn" id="delete-vm" name="delete" data-vmid="{{ vm.id }}" data-vmname="{{ vm.name }}">DELETE</button>
{% else %}
To delete VM, power it off.
{% endif %}
@ -43,28 +43,35 @@
</div>
<div class="panel-body">
<ul class="nav nav-list">
<li class="nav-header">Boot Order</li>
<li>
{{ vm.boot_order|join(', ') }}
<button class="btn btn-default proxstar-changebtn" id="edit-boot-order" data-vmid="{{ vm.id }}" data-vmname="{{ vm.name }}" data-boot_order="{{ vm.boot_order_json }}">
<span class="glyphicon glyphicon-cog"></span>
</button>
</li>
<li class="nav-header">Interfaces</li>
{% for interface in vm['interfaces'] %}
<li>{{ interface[0] }}: {{ interface[1] }}</li>
{% for interface in vm.interfaces %}
<li>{{ interface[0] }}: {{ interface[2] }}</li>
{% endfor %}
<li class="nav-header">Disks</li>
{% for disk in vm['disks'] %}
{% for disk in vm.disks %}
<li>
{{ disk[0] }}: {{ disk[1] }}GB
<button class="btn btn-default proxstar-changebtn resize-disk" id="resize-disk" name="resize" data-vmid="{{ vm['vmid'] }}" data-disk="{{ disk[0] }}" data-usage="{{ usage['disk'] }}" data-limit="{{ limits['disk'] }}">
<button class="btn btn-default proxstar-changebtn resize-disk" id="resize-disk" name="resize" data-vmid="{{ vm.id }}" data-disk="{{ disk[0] }}" data-usage="{{ usage['disk'] }}" data-limit="{{ limits['disk'] }}">
<span class="glyphicon glyphicon-cog"></span>
</button>
</li>
{% endfor %}
<li class="nav-header">ISO</li>
<li>
{{ vm['iso'] }}
{% if vm['iso'] != 'None' %}
<button class="btn btn-danger proxstar-ejectbtn" id="eject-iso" name="eject" data-vmid="{{ vm['vmid'] }}" data-iso="{{ vm['iso'] }}">
{{ vm.iso }}
{% if vm.iso != 'None' %}
<button class="btn btn-danger proxstar-ejectbtn" id="eject-iso" name="eject" data-vmid="{{ vm.id }}" data-iso="{{ vm.iso }}">
<span class="glyphicon glyphicon-eject"></span>
</button>
{% endif %}
<button class="btn btn-default proxstar-changebtn" id="change-iso" name="change" data-vmid="{{ vm['vmid'] }}" data-iso="{{ vm['iso'] }}">
<button class="btn btn-default proxstar-changebtn" id="change-iso" name="change" data-vmid="{{ vm.id }}" data-iso="{{ vm.iso }}">
<span class="glyphicon glyphicon-cog"></span>
</button>
</li>
@ -80,49 +87,49 @@
<div class="panel-body">
<dl class="dl-horizontal">
<dt>Name</dt>
<dd>{{ vm['name'] }}</dd>
<dd>{{ vm.name }}</dd>
<dt>DNS Name</dt>
<dd>{{ vm['name'] }}.csh.rit.edu</dd>
<dd>{{ vm.name }}.csh.rit.edu</dd>
<dt>ID</dt>
<dd>{{ vm['vmid'] }}</dd>
<dd>{{ vm.id }}</dd>
<dt>Status</dt>
<dd>{{ vm['qmpstatus'] }}</dd>
<dd>{{ vm.qmpstatus }}</dd>
<dt>Node</dt>
<dd>{{ vm['node'] }}</dd>
<dd>{{ vm.node }}</dd>
<dt>Cores</dt>
<dd>
{{ vm['config']['cores'] * vm['config'].get('sockets', 1) }}
{% if vm['qmpstatus'] == 'running' or vm['qmpstatus'] == 'suspended' or vm['qmpstatus'] == 'paused' %}
<button class="btn btn-default proxstar-changebtn" id="change-cores" data-vmid="{{ vm['vmid'] }}" data-usage="{{ usage['cpu'] - (vm['config']['cores'] * vm['config'].get('sockets', 1)) }}" data-limit="{{ limits['cpu'] }}">
{{ vm.cpu }}
{% if vm.qmpstatus == 'running' or vm.qmpstatus == 'suspended' or vm.qmpstatus == 'paused' %}
<button class="btn btn-default proxstar-changebtn" id="change-cores" data-vmid="{{ vm.id }}" data-usage="{{ usage['cpu'] - vm.cpu }}" data-limit="{{ limits['cpu'] }}">
<span class="glyphicon glyphicon-cog"></span>
</button>
{% else %}
<button class="btn btn-default proxstar-changebtn" id="change-cores" data-vmid="{{ vm['vmid'] }}" data-usage=0 data-limit="{{ limits['cpu'] }}">
<button class="btn btn-default proxstar-changebtn" id="change-cores" data-vmid="{{ vm.id }}" data-usage=0 data-limit="{{ limits['cpu'] }}">
<span class="glyphicon glyphicon-cog"></span>
</button>
{% endif %}
</dd>
<dt>Memory</dt>
<dd>
{% if vm['config']['memory'] < 1024 %}
{{ vm['config']['memory'] }}MB
{% if vm.mem < 1024 %}
{{ vm.mem }}MB
{% else %}
{{ vm['config']['memory'] // 1024 }}GB
{{ vm.mem // 1024 }}GB
{% endif %}
{% if vm['qmpstatus'] == 'running' or vm['qmpstatus'] == 'suspended' or vm['qmpstatus'] == 'paused' %}
<button class="btn btn-default proxstar-changebtn" id="change-mem" data-vmid="{{ vm['vmid'] }}" data-usage="{{ usage['mem'] - vm['config']['memory'] // 1024 }}" data-limit="{{ limits['mem'] }}">
{% if vm.qmpstatus == 'running' or vm.qmpstatus == 'suspended' or vm.qmpstatus == 'paused' %}
<button class="btn btn-default proxstar-changebtn" id="change-mem" data-vmid="{{ vm.id }}" data-usage="{{ usage['mem'] - vm.config['memory'] // 1024 }}" data-limit="{{ limits['mem'] }}">
<span class="glyphicon glyphicon-cog"></span>
</button>
{% else %}
<button class="btn btn-default proxstar-changebtn" id="change-mem" data-vmid="{{ vm['vmid'] }}" data-usage=0 data-limit="{{ limits['mem'] }}">
<button class="btn btn-default proxstar-changebtn" id="change-mem" data-vmid="{{ vm.id }}" data-usage=0 data-limit="{{ limits['mem'] }}">
<span class="glyphicon glyphicon-cog"></span>
</button>
{% endif %}
</dd>
<dt>Expiration</dt>
<dd>
{{ vm['expire'] }}
<button class="btn btn-success proxstar-renewbtn" id="renew-vm" name="renew" data-vmid="{{ vm['vmid'] }}" data-vmname="{{ vm['name'] }}">
{{ vm.expire.strftime('%m/%d/%Y') }}
<button class="btn btn-success proxstar-renewbtn" id="renew-vm" name="renew" data-vmid="{{ vm.id }}" data-vmname="{{ vm.name }}">
<span class="glyphicon glyphicon-plus"></span>
</button>
</dd>

View file

@ -76,7 +76,7 @@ class User(object):
if vm.status == 'running' or vm.status == 'paused':
usage['cpu'] += int(vm.cpu * vm.config.get('sockets', 1))
usage['mem'] += (int(vm.mem) / 1024)
for disk in vm.get_disks():
for disk in vm.disks:
usage['disk'] += int(disk[1])
return usage

View file

@ -1,6 +1,11 @@
import time
from proxstar.util import *
import json
from proxstar import db, starrs
from proxstar.db import get_vm_expire
from proxstar.util import lazy_property
from proxstar.starrs import get_ip_for_mac
from proxstar.proxmox import connect_proxmox, get_node_least_mem, get_free_vmid, get_vm_node
from flask import current_app as app
class VM(object):
@ -13,7 +18,7 @@ class VM(object):
@lazy_property
def cpu(self):
return self.config['cores']
return self.config['cores'] * self.config.get('sockets', 1)
@lazy_property
def mem(self):
@ -40,7 +45,8 @@ class VM(object):
def set_cpu(self, cores):
proxmox = connect_proxmox()
proxmox.nodes(self.node).qemu(self.id).config.put(cores=cores)
proxmox.nodes(self.node).qemu(self.id).config.put(
cores=cores, sockets=1)
def set_mem(self, mem):
proxmox = connect_proxmox()
@ -80,7 +86,39 @@ class VM(object):
proxmox = connect_proxmox()
return proxmox.nodes(self.node).qemu(self.id).config.get()
def get_interfaces(self):
@lazy_property
def boot_order(self):
boot_order_lookup = {
'a': 'Floppy',
'c': 'Hard Disk',
'd': 'CD-ROM',
'n': 'Network'
}
raw_boot_order = self.config.get('boot', 'cdn')
boot_order = []
for i in range(0, len(raw_boot_order)):
boot_order.append(boot_order_lookup[raw_boot_order[i]])
return boot_order
@lazy_property
def boot_order_json(self):
return json.dumps(self.boot_order)
def set_boot_order(self, boot_order):
proxmox = connect_proxmox()
boot_order_lookup = {
'Floppy': 'a',
'Hard Disk': 'c',
'CD-ROM': 'd',
'Network': 'n'
}
raw_boot_order = ''
for i in range(0, len(boot_order)):
raw_boot_order += boot_order_lookup[boot_order[i]]
proxmox.nodes(self.node).qemu(self.id).config.put(boot=raw_boot_order)
@lazy_property
def interfaces(self):
interfaces = []
for key, val in self.config.items():
if 'net' in key:
@ -90,7 +128,8 @@ class VM(object):
mac = mac[0].split('=')[1]
else:
mac = mac[1].split('=')[1]
interfaces.append([key, mac])
ip = get_ip_for_mac(starrs, mac)
interfaces.append([key, mac, ip])
interfaces = sorted(interfaces, key=lambda x: x[0])
return interfaces
@ -109,7 +148,8 @@ class VM(object):
disk_size = split.split('=')[1].rstrip('G')
return disk_size
def get_disks(self):
@lazy_property
def disks(self):
disks = []
for key, val in self.config.items():
valid_disk_types = ['virtio', 'ide', 'sata', 'scsi']
@ -123,7 +163,8 @@ class VM(object):
disks = sorted(disks, key=lambda x: x[0])
return disks
def get_iso(self):
@lazy_property
def iso(self):
if self.config.get('ide2'):
if self.config['ide2'].split(',')[0] == 'none':
iso = 'None'
@ -154,6 +195,10 @@ class VM(object):
proxmox.nodes(self.node).qemu(self.id).resize.put(
disk=disk, size="+{}G".format(size))
@lazy_property
def expire(self):
return get_vm_expire(db, self.id, app.config['VM_EXPIRE_MONTHS'])
def create_vm(proxmox, user, name, cores, memory, disk, iso):
node = proxmox.nodes(get_node_least_mem(proxmox))