aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api/migrations/0022_add_cifile_generated_field.py15
-rw-r--r--src/api/urls.py2
-rw-r--r--src/api/views.py102
-rw-r--r--src/booking/stats.py1
-rw-r--r--src/booking/views.py5
-rw-r--r--src/dashboard/admin_utils.py218
-rw-r--r--src/templates/base/booking/booking_detail.html4
7 files changed, 341 insertions, 6 deletions
diff --git a/src/api/migrations/0022_add_cifile_generated_field.py b/src/api/migrations/0022_add_cifile_generated_field.py
new file mode 100644
index 0000000..f83a102
--- /dev/null
+++ b/src/api/migrations/0022_add_cifile_generated_field.py
@@ -0,0 +1,15 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('api', '0018_cloudinitfile'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="CloudInitFile",
+ name="generated",
+ field=models.BooleanField(default=False)
+ ),
+ ]
diff --git a/src/api/urls.py b/src/api/urls.py
index acef947..cbb453c 100644
--- a/src/api/urls.py
+++ b/src/api/urls.py
@@ -62,6 +62,7 @@ from api.views import (
single_image,
single_opsys,
create_ci_file,
+ booking_details,
)
urlpatterns = [
@@ -93,6 +94,7 @@ urlpatterns = [
path('booking/<int:booking_id>', specific_booking),
path('booking/<int:booking_id>/extendBooking/<int:days>', extend_booking),
path('booking/makeBooking', make_booking),
+ path('booking/<int:booking_id>/details', booking_details),
path('resource_inventory/availableTemplates', available_templates),
path('resource_inventory/<int:template_id>/images', images_for_template),
diff --git a/src/api/views.py b/src/api/views.py
index ffa9b3f..d5966ed 100644
--- a/src/api/views.py
+++ b/src/api/views.py
@@ -33,7 +33,7 @@ from api.forms import DowntimeForm
from account.models import UserProfile, Lab
from booking.models import Booking
from booking.quick_deployer import create_from_API
-from api.models import LabManagerTracker, get_task, Job, AutomationAPIManager, APILog
+from api.models import LabManagerTracker, get_task, Job, AutomationAPIManager, APILog, GeneratedCloudConfig
from notifier.manager import NotificationHandler
from analytics.models import ActiveVPNUser
from resource_inventory.models import (
@@ -654,3 +654,103 @@ def list_labs(request):
lab_list.append(lab_info)
return JsonResponse(lab_list, safe=False)
+
+
+"""
+Booking Details API Views
+"""
+
+
+def booking_details(request, booking_id=""):
+ token = auth_and_log(request, 'booking/{}/details'.format(booking_id))
+
+ if isinstance(token, HttpResponse):
+ return token
+
+ booking = get_object_or_404(Booking, pk=booking_id, owner=token.user)
+
+ # overview
+ overview = {
+ 'username': GeneratedCloudConfig._normalize_username(None, str(token.user)),
+ 'purpose': booking.purpose,
+ 'project': booking.project,
+ 'start_time': booking.start,
+ 'end_time': booking.end,
+ 'pod_definitions': booking.resource.template,
+ 'lab': booking.lab
+ }
+
+ # deployment progress
+ task_list = []
+ for task in booking.job.get_tasklist():
+ task_info = {
+ 'name': str(task),
+ 'status': 'DONE',
+ 'lab_response': 'No response provided (yet)'
+ }
+ if task.status < 100:
+ task_info['status'] = 'PENDING'
+ elif task.status < 200:
+ task_info['status'] = 'IN PROGRESS'
+
+ if task.message:
+ if task.type_str == "Access Task" and request.user.id != task.config.user.id:
+ task_info['lab_response'] = '--secret--'
+ else:
+ task_info['lab_response'] = str(task.message)
+ task_list.append(task_info)
+
+ # pods
+ pod_list = []
+ for host in booking.resource.get_resources():
+ pod_info = {
+ 'hostname': host.config.name,
+ 'machine': host.name,
+ 'role': '',
+ 'is_headnode': host.config.is_head_node,
+ 'image': host.config.image,
+ 'ram': {'amount': str(host.profile.ramprofile.first().amount) + 'G', 'channels': host.profile.ramprofile.first().channels},
+ 'cpu': {'arch': host.profile.cpuprofile.first().architecture, 'cores': host.profile.cpuprofile.first().cores, 'sockets': host.profile.cpuprofile.first().cpus},
+ 'disk': {'size': str(host.profile.storageprofile.first().size) + 'GiB', 'type': host.profile.storageprofile.first().media_type, 'mount_point': host.profile.storageprofile.first().name},
+ 'interfaces': [],
+ }
+ try:
+ pod_info['role'] = host.template.opnfvRole
+ except Exception:
+ pass
+ for intprof in host.profile.interfaceprofile.all():
+ int_info = {
+ 'name': intprof.name,
+ 'speed': intprof.speed
+ }
+ pod_info['interfaces'].append(int_info)
+ pod_list.append(pod_info)
+
+ # diagnostic info
+ diagnostic_info = {
+ 'job_id': booking.job.id,
+ 'ci_files': '',
+ 'pods': []
+ }
+ for host in booking.resource.get_resources():
+ pod = {
+ 'host': host.name,
+ 'configs': [],
+
+ }
+ for ci_file in host.config.cloud_init_files.all():
+ ci_info = {
+ 'id': ci_file.id,
+ 'text': ci_file.text
+ }
+ pod['configs'].append(ci_info)
+ diagnostic_info['pods'].append(pod)
+
+ details = {
+ 'overview': overview,
+ 'deployment_progress': task_list,
+ 'pods': pod_list,
+ 'diagnostic_info': diagnostic_info,
+ 'pdf': booking.pdf
+ }
+ return JsonResponse(str(details), safe=False)
diff --git a/src/booking/stats.py b/src/booking/stats.py
index 70f91fa..5a59d32 100644
--- a/src/booking/stats.py
+++ b/src/booking/stats.py
@@ -94,6 +94,7 @@ class StatisticsManager(object):
proj_count = sorted(Counter(projects).items(), key=lambda x: x[1])
project_keys = [proj[0] for proj in proj_count[-5:]]
+ project_keys = ['None' if x is None else x for x in project_keys]
project_counts = [proj[1] for proj in proj_count[-5:]]
resources = {key: [x, value] for key, value in profiles.items()}
diff --git a/src/booking/views.py b/src/booking/views.py
index 940428b..367a18d 100644
--- a/src/booking/views.py
+++ b/src/booking/views.py
@@ -24,7 +24,7 @@ from booking.models import Booking
from booking.stats import StatisticsManager
from booking.forms import HostReImageForm
from workflow.forms import FormUtils
-from api.models import JobFactory
+from api.models import JobFactory, GeneratedCloudConfig
from workflow.views import login
from booking.forms import QuickBookingForm
from booking.quick_deployer import create_from_form, drop_filter
@@ -167,7 +167,8 @@ def booking_detail_view(request, booking_id):
'booking': booking,
'pdf': booking.pdf,
'user_id': user.id,
- 'image_mapping': build_image_mapping(booking.lab, user)
+ 'image_mapping': build_image_mapping(booking.lab, user),
+ 'posix_username': GeneratedCloudConfig._normalize_username(None, user.username)
}
return render(
diff --git a/src/dashboard/admin_utils.py b/src/dashboard/admin_utils.py
index 045caeb..75e4f3e 100644
--- a/src/dashboard/admin_utils.py
+++ b/src/dashboard/admin_utils.py
@@ -27,9 +27,11 @@ from resource_inventory.models import (
)
import json
+import yaml
import sys
import inspect
import pydoc
+import csv
from django.contrib.auth.models import User
@@ -43,9 +45,7 @@ from resource_inventory.pdf_templater import PDFTemplater
from booking.quick_deployer import update_template
-from datetime import timedelta
-
-from django.utils import timezone
+from datetime import timedelta, date, datetime, timezone
from booking.models import Booking
from notifier.manager import NotificationHandler
@@ -225,6 +225,99 @@ def get_info(host_labid, lab_username):
return info
+class CumulativeData:
+ use_days = 0
+ count_bookings = 0
+ count_extensions = 0
+
+ def __init__(self, file_writer):
+ self.file_writer = file_writer
+
+ def account(self, booking, usage_days):
+ self.count_bookings += 1
+ self.count_extensions += booking.ext_count
+ self.use_days += usage_days
+
+ def write_cumulative(self):
+ self.file_writer.writerow([])
+ self.file_writer.writerow([])
+ self.file_writer.writerow(['Lab Use Days', 'Count of Bookings', 'Total Extensions Used'])
+ self.file_writer.writerow([self.use_days, self.count_bookings, (self.count_bookings * 2) - self.count_extensions])
+
+
+def get_years_booking_data(start_year=None, end_year=None):
+ """
+ Outputs yearly booking information from the past 'start_year' years (default: current year)
+ until the last day of the end year (default current year) as a csv file.
+ """
+ if start_year is None and end_year is None:
+ start = datetime.combine(date(datetime.now().year, 1, 1), datetime.min.time()).replace(tzinfo=timezone.utc)
+ end = datetime.combine(date(start.year + 1, 1, 1), datetime.min.time()).replace(tzinfo=timezone.utc)
+ elif end_year is None:
+ start = datetime.combine(date(start_year, 1, 1), datetime.min.time()).replace(tzinfo=timezone.utc)
+ end = datetime.combine(date(datetime.now().year, 1, 1), datetime.min.time()).replace(tzinfo=timezone.utc)
+ else:
+ start = datetime.combine(date(start_year, 1, 1), datetime.min.time()).replace(tzinfo=timezone.utc)
+ end = datetime.combine(date(end_year + 1, 1, 1), datetime.min.time()).replace(tzinfo=timezone.utc)
+
+ if (start.year == end.year - 1):
+ file_name = "yearly_booking_data_" + str(start.year) + ".csv"
+ else:
+ file_name = "yearly_booking_data_" + str(start.year) + "-" + str(end.year - 1) + ".csv"
+
+ with open(file_name, "w", newline="") as file:
+ file_writer = csv.writer(file)
+ cumulative_data = CumulativeData(file_writer)
+ file_writer.writerow(
+ [
+ 'ID',
+ 'Project',
+ 'Purpose',
+ 'User',
+ 'Collaborators',
+ 'Extensions Left',
+ 'Usage Days',
+ 'Start',
+ 'End'
+ ]
+ )
+
+ for booking in Booking.objects.filter(start__gte=start, start__lte=end):
+ filtered = False
+ booking_filter = [279]
+ user_filter = ["ParkerBerberian", "ssmith", "ahassick", "sbergeron", "jhodgdon", "rhodgdon", "aburch", "jspewock"]
+ user = booking.owner.username if booking.owner.username is not None else "None"
+
+ for b in booking_filter:
+ if b == booking.id:
+ filtered = True
+
+ for u in user_filter:
+ if u == user:
+ filtered = True
+ # trims time delta to the the specified year(s) if between years
+ usage_days = ((end if booking.end > end else booking.end) - (start if booking.start < start else booking.start)).days
+ collaborators = []
+
+ for c in booking.collaborators.all():
+ collaborators.append(c.username)
+
+ if (not filtered):
+ cumulative_data.account(booking, usage_days)
+ file_writer.writerow([
+ str(booking.id),
+ str(booking.project),
+ str(booking.purpose),
+ str(booking.owner.username),
+ ','.join(collaborators),
+ str(booking.ext_count),
+ str(usage_days),
+ str(booking.start),
+ str(booking.end)
+ ])
+ cumulative_data.write_cumulative()
+
+
def map_cntt_interfaces(labid: str):
"""
Use this during cntt migrations, call it with a host labid and it will change profiles for this host
@@ -351,6 +444,125 @@ def print_dict_pretty(a_dict):
print(json.dumps(a_dict, sort_keys=True, indent=4))
+def import_host(filenames):
+ """
+ Imports host from an array of converted inspection files and if needed creates a new profile for the host.
+ NOTE: CONVERT INSPECTION FILES USING convert_inspect_results(["file", "file"])
+ (original file names not including "-import.yaml" i.e. hpe44) AND FILL IN <NEEDED FIELDS> BEFORE THIS
+ @filenames: array of host import file names to import
+ """
+
+ for filename in filenames:
+
+ # open import file
+ file = open("dashboard/" + filename + "-import.yaml", "r")
+ data = yaml.safe_load(file)
+
+ # if a new profile is needed create one and a matching template
+ if (data["new_profile"]):
+ add_profile(data)
+ print("Profile: " + data["name"] + " created!")
+ make_default_template(
+ ResourceProfile.objects.get(name=data["name"]),
+ Image.objects.get(lab_id=data["image"]).id,
+ None,
+ None,
+ False,
+ False,
+ data["owner"],
+ "unh_iol",
+ True,
+ False,
+ data["temp_desc"]
+ )
+
+ print(" Template: " + data["temp_name"] + " created!")
+
+ # add the server
+ add_server(
+ ResourceProfile.objects.get(name=data["name"]),
+ data["hostname"],
+ data["interfaces"],
+ data["lab"],
+ data["vendor"],
+ data["model"]
+ )
+
+ print(data["hostname"] + " imported!")
+
+
+def convert_inspect_results(files):
+ """
+ Converts an array of inspection result files into templates (filename-import.yaml) to be filled out for importing the servers into the dashboard
+ @files an array of file names (not including the file type. i.e hpe44). Default: []
+ """
+ for filename in files:
+ # open host inspect file
+ file = open("dashboard/" + filename + ".yaml")
+ output = open("dashboard/" + filename + "-import.yaml", "w")
+ data = json.load(file)
+
+ # gather data about disks
+ disk_data = {}
+ for i in data["disk"]:
+
+ # don't include loops in disks
+ if "loop" not in i:
+ disk_data[i["name"]] = {
+ "capacity": i["size"][:-3],
+ "media_type": "<\"SSD\" or \"HDD\">",
+ "interface": "<\"sata\", \"sas\", \"ssd\", \"nvme\", \"scsi\", or \"iscsi\">",
+ }
+
+ # gather interface data
+ interface_data = {}
+ for i in data["interfaces"]:
+ interface_data[data["interfaces"][i]["name"]] = {
+ "speed": data["interfaces"][i]["speed"],
+ "nic_type": "<\"onboard\" or \"pcie\">",
+ "order": "<order in switch>",
+ "mac_address": data["interfaces"][i]["mac"],
+ "bus_addr": data["interfaces"][i]["busaddr"],
+ }
+
+ # gather cpu data
+ cpu_data = {
+ "cores": data["cpu"]["cores"],
+ "architecture": data["cpu"]["arch"],
+ "cpus": data["cpu"]["cpus"],
+ "cflags": "<cflags string>",
+ }
+
+ # gather ram data
+ ram_data = {
+ "amount": data["memory"][:-1],
+ "channels": "<int of ram channels used>",
+ }
+
+ # assemble data for host import file
+ import_data = {
+ "new_profile": "<True or False> (Set to True to create a new profile for the host's type)",
+ "name": "<profile name> (Used to set the profile of a host and for creating a new profile)",
+ "description": "<profile description>",
+ "labs": "<labs using profile>",
+ "temp_name": "<Template name>",
+ "temp_desc": "<template description>",
+ "image": "<image lab_id>",
+ "owner": "<template owner>",
+ "hostname": data["hostname"],
+ "lab": "<lab server is in> (i.e. \"unh_iol\")",
+ "disks": disk_data,
+ "interfaces": interface_data,
+ "cpus": cpu_data,
+ "ram": ram_data,
+ "vendor": "<host vendor>",
+ "model": "<host model>",
+ }
+
+ # export data as yaml
+ yaml.dump(import_data, output)
+
+
def add_profile(data):
"""
Used for adding a host profile to the dashboard
diff --git a/src/templates/base/booking/booking_detail.html b/src/templates/base/booking/booking_detail.html
index 4a8f35a..70958f6 100644
--- a/src/templates/base/booking/booking_detail.html
+++ b/src/templates/base/booking/booking_detail.html
@@ -24,6 +24,10 @@ code {
<div class="collapse show" id="panel_overview">
<table class="table m-0">
<tr>
+ <td>Username</td>
+ <td>{{ posix_username }}</td>
+ </tr>
+ <tr>
<td>Purpose</td>
<td>{{ booking.purpose }}</td>
</tr>