Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions monitoring/publishing/static/stylesheet.css
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,15 @@ li {
background-color: #8D8D8D;
color: white;
}

textarea {
vertical-align: top;
}

button#validate-button {
background-color: #e0e0e0;
border-color: #b0b0b0;
cursor: pointer;
font-weight: bold;
padding: 5px 10px;
}
1 change: 1 addition & 0 deletions monitoring/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
'monitoring.availability',
'monitoring.benchmarks',
'monitoring.iris',
'monitoring.validator',
]

REST_FRAMEWORK = {
Expand Down
7 changes: 7 additions & 0 deletions monitoring/templates/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,12 @@ <h2>Return format</h2>
For example: <a href="{% url 'gridsitesync-detail' 'RAL-LCG2' %}?format=json">{% url 'gridsitesync-detail' 'RAL-LCG2' %}?format=json</a>
</p>

<h2>Record Validator</h2>
<p><ul><li>
<a href="{% url 'validator' %}">{% url 'validator' %}</a> - A validating tool to check APEL records are
correctly formatted. Records can be validated against a specific record type, or the type can be inferred from
a record header.
</li></ul></p>

</body>
</html>
1 change: 1 addition & 0 deletions monitoring/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@
path('benchmarks/', include('monitoring.benchmarks.urls')),
path('iris/', include('monitoring.iris.urls')),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
path('validator/', include ('monitoring.validator.urls')),
]
Empty file.
3 changes: 3 additions & 0 deletions monitoring/validator/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
5 changes: 5 additions & 0 deletions monitoring/validator/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class ValidatorConfig(AppConfig):
name = 'monitoring.validator'
Empty file.
Empty file added monitoring/validator/models.py
Empty file.
60 changes: 60 additions & 0 deletions monitoring/validator/templates/validator/validator_index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{% load static %}

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">

<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Record Validator</title>
<link href="{% static 'stylesheet.css' %}" rel="stylesheet" type="text/css" />
<link href="{% static 'style.css' %}" rel="stylesheet" type="text/css" />
</head>

<body>
<h1>Record Validator</h2>

<form id="validate-form" method="post">
{% csrf_token %}
<label for="record_type">Record Type:</label>
<select name="record_type" id="record_type">
<option value="All">All</option>
<option value="CloudRecord">Cloud Record</option>
<option value="CloudSummaryRecord">Cloud Summary Record</option>
<option value="JobRecord">Job Record</option>
<option value="JobRecord04">Job Record v0.4</option>
<option value="NormalisedSummaryRecord">Normalised Summary Record</option>
<option value="NormalisedSummaryRecord04">Normalised Summary Record v0.4</option>
<option value="SummaryRecord">Summary Record</option>
<option value="SummaryRecord04">Summary Record v0.4</option>
<option value="SyncRecord">Sync Record</option>
</select>
<p>Selecting 'All' means the type is determined by the record header.</p>
<p>If you select a specific record type, the header will need to be removed from the record input.</p>
<p>v0.4 records are the newer record type - they require dictionary fields for benchmark data.</p>
<br>
<label for="input_record">Enter Record:</label>
<textarea
id="input_record"
name="input_record"
rows="15"
cols="50"
wrap="wrap"
>{{ input_record|default:''|escape }}</textarea>
<br><br>
<button type="submit" id="validate-button">Validate!</button>
<br><br>
</form>

<script>
// Setting select option back to what was selected (as it gets reset on form submission)
document.getElementById("record_type").value = "{{ record_type }}";
</script>

<div id="output">
{% if output %}
<h3>Validation Output</h3>
{{ output }}
{% endif %}
</div>
</body>
</html>
3 changes: 3 additions & 0 deletions monitoring/validator/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
8 changes: 8 additions & 0 deletions monitoring/validator/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.urls import path

from monitoring.validator import views

urlpatterns = [
path('', views.index, name="validator"),
]

89 changes: 89 additions & 0 deletions monitoring/validator/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from django.shortcuts import render
from django.views.decorators.http import require_http_methods

# Apel record-checking class imports
from apel.db.loader.record_factory import RecordFactory, RecordFactoryException
from apel.db.records.record import InvalidRecordException

# Apel record-type class imports
from apel.db.records.job import JobRecord, JobRecord04
from apel.db.records.summary import SummaryRecord, SummaryRecord04
from apel.db.records.normalised_summary import NormalisedSummaryRecord, NormalisedSummaryRecord04
from apel.db.records.sync import SyncRecord
from apel.db.records.cloud import CloudRecord
from apel.db.records.cloud_summary import CloudSummaryRecord


@require_http_methods(["GET", "POST"])
def index(request):
"""
Validates inputted records using the Apel record validation methods.
It either validates a record against a specific type or against all types, depending on what record_type
option was chosen on the html template. The default is `All`.
The input record, record type and validation output are then returned to the html template as context on get
request, so that the html page retains its information/context when refreshing the page or submitting the form.
"""
template_name = "validator/validator_index.html"
input_record = ""
record_type = "All"
output = ""

# On form submission, trigger record validation
if request.method == "POST":
input_record = request.POST.get("input_record", "")
record_type = request.POST.get("record_type", "")
output = validate(input_record, record_type)

context = {
"input_record": input_record,
"record_type": record_type,
"output": output,
}

return render(request, template_name, context)


def validate(record: str, record_type: str) -> str:
"""
Validated record(s) and record_type passed in from the html page template.
If record type is all, make use of the create_records apel method (expects a record header).
Else, make use of the _create_record_objects apel method (expects there to be no record header).
If the record is valid, return a "valid record" string.
If the record is invalid, an InvalidRecordException or RecordFactoryException is raised by the Apel methods.
Catch these exceptions and return the exception information.
"""
if not record:
return "Please enter a record to be validated."

record = record.strip()

# Map record_type string to record_type class
# String is always exact as determined through html form selection option
record_map = {
"JobRecord": JobRecord,
"JobRecord04": JobRecord04,
"SummaryRecord": SummaryRecord,
"SummaryRecord04": SummaryRecord04,
"NormalisedSummaryRecord": NormalisedSummaryRecord,
"NormalisedSummaryRecord04": NormalisedSummaryRecord04,
"SyncRecord": SyncRecord,
"CloudRecord": CloudRecord,
"CloudSummaryRecord": CloudSummaryRecord,
}

try:
recordFactory = RecordFactory()

if record_type == "All":
result = recordFactory.create_records(record)
else:
record_class = record_map[record_type]
result = recordFactory._create_record_objects(record, record_class)

if "Record object at" in str(result):
return "Record(s) valid!"

return str(result)

except (InvalidRecordException, RecordFactoryException) as e:
return str(e)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Pin packages to support and work with py3.6.
apel
Django==3.2.25
djangorestframework==3.15.1
pytz==2025.2
Expand Down