aboutsummaryrefslogtreecommitdiffstats
path: root/src/dashboard
diff options
context:
space:
mode:
Diffstat (limited to 'src/dashboard')
-rw-r--r--src/dashboard/actions.py47
-rw-r--r--src/dashboard/admin_utils.py246
2 files changed, 196 insertions, 97 deletions
diff --git a/src/dashboard/actions.py b/src/dashboard/actions.py
deleted file mode 100644
index 44b1fdd..0000000
--- a/src/dashboard/actions.py
+++ /dev/null
@@ -1,47 +0,0 @@
-##############################################################################
-# Copyright (c) 2019 Parker Berberian, Sawyer Bergeron, and others.
-#
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-from resource_inventory.models import Host, Vlan
-from account.models import Lab
-from booking.models import Booking
-from datetime import timedelta
-from django.utils import timezone
-
-
-def free_leaked_hosts(free_old_bookings=False, old_booking_age=timedelta(days=1)):
- bundles = [booking.resource for booking in Booking.objects.filter(end__gt=timezone.now())]
- active_hosts = set()
- for bundle in bundles:
- active_hosts.update([host for host in bundle.hosts.all()])
-
- marked_hosts = set(Host.objects.filter(booked=True))
-
- for host in (marked_hosts - active_hosts):
- host.booked = False
- host.save()
-
-
-def free_leaked_public_vlans():
- booked_host_interfaces = []
-
- for lab in Lab.objects.all():
-
- for host in Host.objects.filter(booked=True).filter(lab=lab):
- for interface in host.interfaces.all():
- booked_host_interfaces.append(interface)
-
- in_use_vlans = Vlan.objects.filter(public=True).distinct('vlan_id').filter(interface__in=booked_host_interfaces)
-
- manager = lab.vlan_manager
-
- for vlan in Vlan.objects.all():
- if vlan not in in_use_vlans:
- if vlan.public:
- manager.release_public_vlan(vlan.vlan_id)
- manager.release_vlans(vlan)
diff --git a/src/dashboard/admin_utils.py b/src/dashboard/admin_utils.py
index 76db762..186a64f 100644
--- a/src/dashboard/admin_utils.py
+++ b/src/dashboard/admin_utils.py
@@ -1,3 +1,12 @@
+##############################################################################
+# Copyright (c) 2021 Sawyer Bergeron and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
from resource_inventory.models import (
ResourceTemplate,
Image,
@@ -23,7 +32,10 @@ import pydoc
from django.contrib.auth.models import User
-from account.models import Lab
+from account.models import (
+ Lab,
+ PublicNetwork
+)
from resource_inventory.resource_manager import ResourceManager
from resource_inventory.pdf_templater import PDFTemplater
@@ -42,12 +54,37 @@ from api.models import JobStatus
def print_div():
- print("====================================================================")
+ """
+ Utility function for printing dividers, does nothing directly useful as a utility
+ """
+ print("=" * 68)
def book_host(owner_username, host_labid, lab_username, hostname, image_id, template_name, length_days=21, collaborator_usernames=[], purpose="internal", project="LaaS"):
"""
creates a quick booking using the given host
+
+ @owner_username is the simple username for the user who will own the resulting booking.
+ Do not set this to a lab username!
+
+ @image_id is the django id of the image in question, NOT the labid of the image.
+ Query Image objects by their public status and compatible host types
+
+ @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object
+
+ @lab_username for iol is `unh_iol`, other labs will be documented here
+
+ @hostname the hostname that the resulting host should have set
+
+ @template_name the name of the (public, or user accessible) template to use for this booking
+
+ @length_days how long the booking should be, no hard limit currently
+
+ @collaborator_usernames a list of usernames for collaborators to the booking
+
+ @purpose what this booking will be used for
+
+ @project what project/group this booking is on behalf of or the owner represents
"""
lab = Lab.objects.get(lab_user__username=lab_username)
host = Server.objects.filter(lab=lab).get(labid=host_labid)
@@ -116,6 +153,16 @@ def book_host(owner_username, host_labid, lab_username, hostname, image_id, temp
def mark_working(host_labid, lab_username, working=True):
+ """
+ Mark a host working/not working so that it is either bookable or hidden in the dashboard.
+
+ @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object
+
+ @lab_username: param of the form `unh_iol` or similar
+
+ @working: bool, whether by the end of execution the host should be considered working or not working
+ """
+
lab = Lab.objects.get(lab_user__username=lab_username)
server = Server.objects.filter(lab=lab).get(labid=host_labid)
print("changing server working status from ", server.working, "to", working)
@@ -124,6 +171,16 @@ def mark_working(host_labid, lab_username, working=True):
def mark_booked(host_labid, lab_username, booked=True):
+ """
+ Mark a host as booked/unbooked
+
+ @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object
+
+ @lab_username: param of the form `unh_iol` or similar
+
+ @working: bool, whether by the end of execution the host should be considered booked or not booked
+ """
+
lab = Lab.objects.get(lab_user__username=lab_username)
server = Server.objects.filter(lab=lab).get(labid=host_labid)
print("changing server booked status from ", server.booked, "to", booked)
@@ -131,13 +188,26 @@ def mark_booked(host_labid, lab_username, booked=True):
server.save()
-# returns host filtered by lab and then unique id within lab
def get_host(host_labid, lab_username):
+ """
+ Returns host filtered by lab and then unique id within lab
+
+ @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object
+
+ @lab_username: param of the form `unh_iol` or similar
+ """
lab = Lab.objects.get(lab_user__username=lab_username)
return Server.objects.filter(lab=lab).get(labid=host_labid)
def get_info(host_labid, lab_username):
+ """
+ Returns various information on the host queried by the given parameters
+
+ @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object
+
+ @lab_username: param of the form `unh_iol` or similar
+ """
info = {}
host = get_host(host_labid, lab_username)
info['host_labid'] = host_labid
@@ -202,8 +272,16 @@ def detect_leaked_hosts(labid="unh_iol"):
return filtered
-def booking_for_host(host_labid: str, labid="unh_iol"):
- server = Server.objects.get(lab__lab_user__username=labid, labid=host_labid)
+def booking_for_host(host_labid: str, lab_username="unh_iol"):
+ """
+ Returns the booking that this server is a part of, if any.
+ Fails with an exception if no such booking exists
+
+ @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object
+
+ @lab_username: param of the form `unh_iol` or similar
+ """
+ server = Server.objects.get(lab__lab_user__username=lab_username, lab_username=host_labid)
booking = server.bundle.booking_set.first()
print_div()
print(booking)
@@ -214,7 +292,15 @@ def booking_for_host(host_labid: str, labid="unh_iol"):
return booking
-def force_release_booking(booking_id):
+def force_release_booking(booking_id: int):
+ """
+ Takes a booking id and forces the booking to end whether or not the tasks have
+ completed normally.
+
+ Use with caution! Hosts may or may not be released depending on other underlying issues
+
+ @booking_id: the id of the Booking object to be released
+ """
booking = Booking.objects.get(id=booking_id)
job = booking.job
tasks = job.get_tasklist()
@@ -223,7 +309,28 @@ def force_release_booking(booking_id):
task.save()
+def free_leaked_public_vlans(safety_buffer_days=2):
+ for lab in Lab.objects.all():
+ current_booking_set = Booking.objects.filter(end__gte=timezone.now() + timedelta(days=safety_buffer_days))
+
+ marked_nets = set()
+
+ for booking in current_booking_set:
+ for network in get_network_metadata(booking.id):
+ marked_nets.add(network["vlan_id"])
+
+ for net in PublicNetwork.objects.filter(lab=lab).filter(in_use=True):
+ if net.vlan not in marked_nets:
+ lab.vlan_manager.release_public_vlan(net.vlan)
+
+
def get_network_metadata(booking_id: int):
+ """
+ Takes a booking id and prints all (known) networks that are owned by it.
+ Returns an object of the form {<network name>: {"vlan_id": int, "netname": str <network name>, "public": bool <whether network is public/routable}}
+
+ @booking_id: the id of the Booking object to be queried
+ """
booking = Booking.objects.get(id=booking_id)
bundle = booking.resource
pnets = PhysicalNetwork.objects.filter(bundle=bundle).all()
@@ -236,46 +343,50 @@ def get_network_metadata(booking_id: int):
def print_dict_pretty(a_dict):
+ """
+ admin_utils internal function
+ """
+
print(json.dumps(a_dict, sort_keys=True, indent=4))
-"""
-schema:
-{
- "name": str
- "description": str
- "labs": [
- str (lab username)
- ]
- "disks": {
- <diskname> : {
- capacity: int (GiB)
- media_type: str ("SSD" or "HDD")
- interface: str ("sata", "sas", "ssd", "nvme", "scsi", or "iscsi")
+def add_profile(data):
+ """
+ Used for adding a host profile to the dashboard
+
+ schema (of dict passed as "data" param):
+ {
+ "name": str
+ "description": str
+ "labs": [
+ str (lab username)
+ ]
+ "disks": {
+ <diskname> : {
+ capacity: int (GiB)
+ media_type: str ("SSD" or "HDD")
+ interface: str ("sata", "sas", "ssd", "nvme", "scsi", or "iscsi")
+ }
}
- }
- interfaces: {
- <intname>: {
- "speed": int (mbit)
- "nic_type": str ("onboard" or "pcie")
- "order": int (compared to the other interfaces, indicates the "order" that the ports are laid out)
+ interfaces: {
+ <intname>: {
+ "speed": int (mbit)
+ "nic_type": str ("onboard" or "pcie")
+ "order": int (compared to the other interfaces, indicates the "order" that the ports are laid out)
+ }
+ }
+ cpus: {
+ cores: int (hardware threads count)
+ architecture: str (x86_64" or "aarch64")
+ cpus: int (number of sockets)
+ cflags: str
+ }
+ ram: {
+ amount: int (GiB)
+ channels: int
}
}
- cpus: {
- cores: int (hardware threads count)
- architecture: str (x86_64" or "aarch64")
- cpus: int (number of sockets)
- cflags: str
- }
- ram: {
- amount: int (GiB)
- channels: int
- }
-}
-"""
-
-
-def add_profile(data):
+ """
base_profile = ResourceProfile.objects.create(name=data['name'], description=data['description'])
base_profile.save()
@@ -306,6 +417,11 @@ def add_profile(data):
def make_default_template(resource_profile, image_id=None, template_name=None, connected_interface_names=None, interfaces_tagged=False, connected_interface_tagged=False, owner_username="root", lab_username="unh_iol", public=True, temporary=False, description=""):
+ """
+ Do not call this function without reading the related source code, it may have unintended effects.
+
+ Used for creating a default template from some host profile
+ """
if not resource_profile:
raise Exception("No viable continuation from none resource_profile")
@@ -352,16 +468,27 @@ def make_default_template(resource_profile, image_id=None, template_name=None, c
connection.save()
-"""
-Note: interfaces should be dict from interface name (eg ens1f0) to dict of schema:
- {
- mac_address: <mac addr>,
- bus_addr: <bus addr>, //this field is optional, "" is default
- }
-"""
+def add_server(profile, name, interfaces, lab_username="unh_iol", vendor="unknown", model="unknown"):
+ """
+ Used to enroll a new host of some profile
+
+ @profile: the ResourceProfile in question (by reference to a model object)
+ @name: the unique name of the server, currently indistinct from labid
-def add_server(profile, uname, interfaces, lab_username="unh_iol", vendor="unknown", model="unknown"):
+ @interfaces: interfaces should be dict from interface name (eg ens1f0) to dict of schema:
+ {
+ mac_address: <mac addr>,
+ bus_addr: <bus addr>, //this field is optional, "" is default
+ }
+
+ @lab_username: username of the lab to be added to
+
+ @vendor: vendor name of the host, such as "HPE" or "Gigabyte"
+
+ @model: specific model of the host, such as "DL380 Gen 9"
+
+ """
server = Server.objects.create(
bundle=None,
profile=profile,
@@ -369,9 +496,9 @@ def add_server(profile, uname, interfaces, lab_username="unh_iol", vendor="unkno
working=True,
vendor=vendor,
model=model,
- labid=uname,
+ labid=name,
lab=Lab.objects.get(lab_user__username=lab_username),
- name=uname,
+ name=name,
booked=False)
for iface_prof in InterfaceProfile.objects.filter(host=profile).all():
@@ -388,12 +515,25 @@ def add_server(profile, uname, interfaces, lab_username="unh_iol", vendor="unkno
def extend_booking(booking_id, days=0, hours=0, minutes=0, weeks=0):
+ """
+ Extend a booking by n <days, hours, minutes, weeks>
+
+ @booking_id: id of the booking
+
+ @days/@hours/@minutes/@weeks: the cumulative amount of delta to add to the length of the booking
+ """
+
booking = Booking.objects.get(id=booking_id)
booking.end = booking.end + timedelta(days=days, hours=hours, minutes=minutes, weeks=weeks)
booking.save()
def docs(function=None, fulltext=False):
+ """
+ Print documentation for a given function in admin_utils.
+ Call without arguments for more information
+ """
+
fn = None
if isinstance(function, str):
@@ -422,7 +562,13 @@ def docs(function=None, fulltext=False):
def admin_functions():
+ """
+ List functions available to call within admin_utils
+ """
+
return [name for name, func in inspect.getmembers(sys.modules[__name__]) if (inspect.isfunction(func) and func.__module__ == __name__)]
print("Hint: call `docs(<function name>)` or `admin_functions()` for help on using the admin utils")
+print("docs(<function name>) displays documentation on a given function")
+print("admin_functions() lists all functions available to call within this module")