diff options
-rw-r--r-- | src/booking/forms.py | 8 | ||||
-rw-r--r-- | src/booking/quick_deployer.py | 3 | ||||
-rw-r--r-- | src/dashboard/admin_utils.py | 226 | ||||
-rw-r--r-- | src/static/img/lfedge-logo.png | bin | 0 -> 6633 bytes | |||
-rw-r--r-- | src/static/js/dashboard.js | 9 | ||||
-rw-r--r-- | src/templates/akraino/base.html | 24 | ||||
-rw-r--r-- | src/templates/akraino/dashboard/landing.html | 23 | ||||
-rw-r--r-- | src/templates/base/base.html | 2 | ||||
-rw-r--r-- | src/templates/base/booking/quick_deploy.html | 33 | ||||
-rw-r--r-- | src/templates/lfedge/base.html | 45 | ||||
-rw-r--r-- | src/templates/lfedge/booking/booking_table.html (renamed from src/templates/akraino/booking/booking_table.html) | 0 | ||||
-rw-r--r-- | src/templates/lfedge/booking/quick_deploy.html (renamed from src/templates/akraino/booking/quick_deploy.html) | 2 | ||||
-rw-r--r-- | src/templates/lfedge/dashboard/landing.html | 23 | ||||
-rw-r--r-- | src/templates/lfedge/layout.html (renamed from src/templates/akraino/layout.html) | 2 |
14 files changed, 275 insertions, 125 deletions
diff --git a/src/booking/forms.py b/src/booking/forms.py index 2a8784f..19c0c85 100644 --- a/src/booking/forms.py +++ b/src/booking/forms.py @@ -59,6 +59,14 @@ class QuickBookingForm(forms.Form): self.fields['filter_field'] = MultipleSelectFilterField(widget=MultipleSelectFilterWidget(**lab_data)) + help_text = 'Hostname can be set only for single-node bookings. For multi-node bookings set hostname through Design a POD.' + self.fields['hostname'].widget.attrs.update({ + 'class': 'has-popover', + 'data-content': help_text, + 'data-placement': 'top', + 'data-container': 'body' + }) + def build_user_list(self): """ Build list of UserProfiles. diff --git a/src/booking/quick_deployer.py b/src/booking/quick_deployer.py index 8b3af6c..7865ee4 100644 --- a/src/booking/quick_deployer.py +++ b/src/booking/quick_deployer.py @@ -111,7 +111,8 @@ def update_template(old_template, image, hostname, user): profile=old_config.profile, image=image_to_set, template=template, - is_head_node=old_config.is_head_node + is_head_node=old_config.is_head_node, + name=hostname if len(old_template.getConfigs()) == 1 else old_config.name ) for old_iface_config in old_config.interface_configs.all(): diff --git a/src/dashboard/admin_utils.py b/src/dashboard/admin_utils.py index 76db762..ad276d9 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, @@ -42,12 +51,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 +150,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 +168,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 +185,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 +269,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 +289,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() @@ -224,6 +307,12 @@ def force_release_booking(booking_id): 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 +325,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 +399,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 +450,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 + + @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 + } -def add_server(profile, uname, interfaces, lab_username="unh_iol", vendor="unknown", model="unknown"): + @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 +478,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 +497,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 +544,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") diff --git a/src/static/img/lfedge-logo.png b/src/static/img/lfedge-logo.png Binary files differnew file mode 100644 index 0000000..689f09a --- /dev/null +++ b/src/static/img/lfedge-logo.png diff --git a/src/static/js/dashboard.js b/src/static/js/dashboard.js index efc0542..12d8ee6 100644 --- a/src/static/js/dashboard.js +++ b/src/static/js/dashboard.js @@ -388,21 +388,30 @@ class MultipleSelectFilterWidget { reserveResource(node){ const required_resources = JSON.parse(node['required_resources']); + let hostname = document.getElementById('id_hostname'); + let cnt = 0 + for(let resource in required_resources){ this.available_resources[resource] -= required_resources[resource]; + cnt += required_resources[resource]; } + if (cnt > 1) + hostname.readOnly = true; + this.updateAvailibility(); } releaseResource(node){ const required_resources = JSON.parse(node['required_resources']); + let hostname = document.getElementById('id_hostname'); for(let resource in required_resources){ this.available_resources[resource] += required_resources[resource]; } + hostname.readOnly = false; this.updateAvailibility(); } diff --git a/src/templates/akraino/base.html b/src/templates/akraino/base.html deleted file mode 100644 index 1368476..0000000 --- a/src/templates/akraino/base.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends "base/base.html" %} -{% load staticfiles %} -{% block bgColor %} -<style> -.bgAkr { - background: #d9c2f2; -} -</style> -<nav class="navbar navbar-light bgAkr navbar-fixed-top border-bottom py-0 mb-0" role="navigation"> -{% endblock bgColor %} - -{% block logo %} -<div class="col-12 col-sm order-1 order-sm-2 text-center text-lg-left"> - <a href="https://www.lfedge.org/projects/akraino/" class="navbar-brand"> - <img src="{% static "img/akraino_logo.logo" %}"> - </a> - - <a class="navbar-brand d-none d-lg-inline" href={% url 'dashboard:index' %}> - Akraino Dashboard - </a> -</div> -{% endblock logo %} -{% block dropDown %} -{% endblock dropDown %} diff --git a/src/templates/akraino/dashboard/landing.html b/src/templates/akraino/dashboard/landing.html deleted file mode 100644 index 5533469..0000000 --- a/src/templates/akraino/dashboard/landing.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "base/dashboard/landing.html" %} -{% block about_us %} - <p>The Shared Community Lab at the IOL aims to help development and testing of LFN projects by hosting hardware and providing access to the community.</p> - <p>To get started, you can request access to a server at the right.</p> -{% endblock about_us %} - -{% block btnGrp %} -<style> -.btnAkr { - color: #fff; - background-color: #39c0c0; -} -.btnAkr:hover{ - color: #fff; - background-color: #259a9a; -} -</style> -<p>To get started, book a pod below:</p> -<a class="btn btnAkr btn-lg d-flex flex-column justify-content-center align-content-center border text-white p-4" href="/booking/quick/">Book a Pod</a> -{% endblock btnGrp %} - -{% block returningUsers %} -{% endblock returningUsers %} diff --git a/src/templates/base/base.html b/src/templates/base/base.html index 3ecad1a..394ddec 100644 --- a/src/templates/base/base.html +++ b/src/templates/base/base.html @@ -36,6 +36,7 @@ <div class="col-6 col-sm-2 order-3 d-flex"> <ul class="nav mx-auto mr-sm-0"> <li class="dropdown ml-auto"> + {% block userDropDownText %} <a class="nav-link p-0 text-dark p-2" data-toggle="dropdown" href="#"> {% if request.user.username %} {{request.user.username}} @@ -44,6 +45,7 @@ {% endif %} <i class="fas fa-caret-down rotate"></i> </a> + {% endblock userDropDownText %} <div class="dropdown-menu dropdown-menu-right"> {% if LFID %} {% if user.is_authenticated %} diff --git a/src/templates/base/booking/quick_deploy.html b/src/templates/base/booking/quick_deploy.html index c954073..8c8b1df 100644 --- a/src/templates/base/booking/quick_deploy.html +++ b/src/templates/base/booking/quick_deploy.html @@ -18,7 +18,7 @@ </div> </div> <div class="row justify-content-center"> - <div class="col-12 col-lg-3 my-2"> + <div class="col-12 col-lg-4 my-2"> <div class="col border rounded py-2 h-100"> {% bootstrap_field form.purpose %} {% bootstrap_field form.project %} @@ -31,28 +31,19 @@ </div> </div> {% block collab %} - <div class="col-12 col-lg-3 my-2"> + <div class="col-12 col-lg-4 my-2"> <div class="col border rounded py-2 h-100"> <label>Collaborators</label> {{ form.users }} </div> </div> {% endblock collab %} - <div class="col-12 col-lg-3 my-2"> + <div class="col-12 col-lg-4 my-2"> <div class="col border rounded py-2 h-100"> {% bootstrap_field form.hostname %} {% bootstrap_field form.image %} </div> </div> - {% block opnfv %} - <div class="col-12 col-lg-3 my-2"> - <div class="col border rounded py-2 h-100"> - <strong>OPNFV: (Optional)</strong> - {% bootstrap_field form.installer %} - {% bootstrap_field form.scenario %} - </div> - </div> - {% endblock opnfv %} <div class="col-12 d-flex mt-2 justify-content-end"> <button id="quick_booking_confirm" onclick="submit_form();" type="button" class="btn btn-success">Confirm</button> </div> @@ -85,6 +76,10 @@ } } + $(document).ready(function() { + $('.has-popover').popover({'trigger':'hover'}); + }); + var sup_image_dict = {{image_filter | safe}}; var sup_installer_dict = {{installer_filter | safe}}; var sup_scenario_dict = {{scenario_filter | safe}}; @@ -108,25 +103,11 @@ } imageFilter(); - $('#id_installer').children().hide(); - $('#id_scenario').children().hide(); - Array.from(document.getElementsByClassName("grid-item-select-btn")).forEach(function (element) { element.addEventListener('click', imageFilter); }); - function installerHider() { - dropFilter("id_installer", sup_installer_dict, "id_image"); - scenarioHider(); - } - document.getElementById('id_image').addEventListener('change', installerHider); - - function scenarioHider() { - dropFilter("id_scenario", sup_scenario_dict, "id_installer"); - } - document.getElementById('id_installer').addEventListener('change', scenarioHider); - function dropFilter(target, target_filter, master) { var dropdown = document.getElementById(target); diff --git a/src/templates/lfedge/base.html b/src/templates/lfedge/base.html new file mode 100644 index 0000000..64c05a4 --- /dev/null +++ b/src/templates/lfedge/base.html @@ -0,0 +1,45 @@ +{% extends "base/base.html" %} +{% load staticfiles %} +{% block bgColor %} +<style> +.LFEdge { + background: #0049b0; + margin-left: -25px; +} + +.wtext { + font-size: 18px; + color: #FFFFFF; +} + +.wtext:hover { + color: #FFFFFF; + text-decoration: none; +} +</style> +<nav class="navbar navbar-light LFEdge navbar-fixed-top border-bottom py-0 mb-0" role="navigation"> +{% endblock bgColor %} + +{% block logo %} +<div class="barClamp col-12 col-sm order-1 order-sm-2 text-center text-lg-left"> + <a href="https://www.lfedge.org/" class="navbar-brand"> + <img src="{% static "img/lfedge-logo.png" %}"> + </a> + + <a class="wtext d-none d-lg-inline" href={% url 'dashboard:index' %}> + Dashboard + </a> +</div> +{% endblock logo %} +{% block dropDown %} +{% endblock dropDown %} +{% block userDropDownText %} +<a class="nav-link p-0 wtext p-2" data-toggle="dropdown" href="#"> + {% if request.user.username %} + {{request.user.username}} + {% else %} + <i class="fas fa-user"></i> + {% endif %} + <i class="fas fa-caret-down rotate"></i> +</a> +{% endblock userDropDownText %} diff --git a/src/templates/akraino/booking/booking_table.html b/src/templates/lfedge/booking/booking_table.html index 4afb4d2..4afb4d2 100644 --- a/src/templates/akraino/booking/booking_table.html +++ b/src/templates/lfedge/booking/booking_table.html diff --git a/src/templates/akraino/booking/quick_deploy.html b/src/templates/lfedge/booking/quick_deploy.html index c3e519f..dac3815 100644 --- a/src/templates/akraino/booking/quick_deploy.html +++ b/src/templates/lfedge/booking/quick_deploy.html @@ -6,7 +6,7 @@ Please select a host type you wish to book. Only available types are shown. More information can be found here: - <a href="https://wiki.akraino.org/display/AK/Shared+Community+Lab">Akraino Wiki</a>. + <a href="https://wiki.lfedge.org/display/LE/Shared+Community+Lab">LF Edge Wiki</a>. If something isn't working right, let us know <a href="mailto:{{contact_email}}"> here! </a> </p> {% endblock form-text %} diff --git a/src/templates/lfedge/dashboard/landing.html b/src/templates/lfedge/dashboard/landing.html new file mode 100644 index 0000000..9a776dc --- /dev/null +++ b/src/templates/lfedge/dashboard/landing.html @@ -0,0 +1,23 @@ +{% extends "base/dashboard/landing.html" %} +{% block about_us %} + <p>The Shared Community Lab at the IOL aims to help development and testing of LF Edge projects by hosting hardware and providing access to the community.</p> + <p>To get started, you can request access to a pod at the right.</p> +{% endblock about_us %} + +{% block btnGrp %} +<style> +.btnLFEdge { + color: #fff; + background-color: #0049b0; +} +.btnLFEdge:hover{ + color: #fff; + background-color: #001776; +} +</style> +<p>To get started, book a pod below:</p> +<a class="btn btnLFEdge btn-lg d-flex flex-column justify-content-center align-content-center border text-white p-4" href="/booking/quick/">Book a Pod</a> +{% endblock btnGrp %} + +{% block returningUsers %} +{% endblock returningUsers %} diff --git a/src/templates/akraino/layout.html b/src/templates/lfedge/layout.html index d30ddb6..217060c 100644 --- a/src/templates/akraino/layout.html +++ b/src/templates/lfedge/layout.html @@ -1,5 +1,5 @@ {% extends "base/layout.html" %} {% block head-title %} -<title>Akraino Dashboard</title> +<title>LF Edge Dashboard</title> {% endblock head-title %} |