Skip to content

Commit

Permalink
perf: Using flask+async on quads-web
Browse files Browse the repository at this point in the history
closes: #558
closes: #559
Change-Id: Ic6a222d297191449ad4f385ce8c8de8dc35ca917
  • Loading branch information
grafuls committed Dec 17, 2024
1 parent c22d89c commit 2f15b8f
Show file tree
Hide file tree
Showing 11 changed files with 79 additions and 53 deletions.
7 changes: 4 additions & 3 deletions rpm/quads.spec
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ BuildRequires: python3-setuptools
Requires: postfix >= 3.0
Requires: python3 >= 3.11
Requires: python3-jinja2 >= 3.0.3
Requires: python3-werkzeug >= 2.2.2
Requires: python3-werkzeug >= 3.0.6
Requires: nginx >= 1.0
Requires: python3-gunicorn >= 15.0
Requires: python3-passlib >= 1.7
Expand All @@ -54,14 +54,15 @@ Requires: python3-requests >= 2.28.1
Requires: python3-jsonpath-ng >= 1.5
Requires: haveged >= 1.8
Requires: python3-GitPython >= 3.1.40
Requires: python3-flask >= 2.2.2
Requires: python3-flask >= 3.0.3
Requires: python3-flask+async >= 3.0.3
Requires: python3-flask-wtf >= 1.0.1
Requires: python3-flask-sqlalchemy >= 2.5.1
Requires: python3-flask-principal >= 0.4.0
Requires: python3-flask-login >= 0.6.2
Requires: python3-flask-security-too >= 4.1.5
Requires: python3-flask-migrate >= 3.1.0
Requires: python3-flask-httpauth >= 3.2.3
Requires: python3-flask-httpauth >= 4.8.0
Requires: python3-flask-cors >= 3.0.10
Requires: python3-jwt >= 1.6.4
Requires: python3-dotenv >= 0.19.2
Expand Down
4 changes: 2 additions & 2 deletions src/quads/web/blueprints/dynamic_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@


@dynamic_content_bp.route("/<page>")
def dynamic_content(page):
async def dynamic_content(page):
file_paths = get_file_paths()
for file in file_paths:
if page in file:
Expand All @@ -24,7 +24,7 @@ def dynamic_content(page):


@dynamic_content_bp.route("/<directory>/<page>")
def dynamic_content_sub(directory, page):
async def dynamic_content_sub(directory, page):
file_paths = get_file_paths()
for file in file_paths:
if page in file:
Expand Down
6 changes: 3 additions & 3 deletions src/quads/web/blueprints/instack.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import os

from flask import Blueprint, abort, make_response, render_template, send_from_directory
from flask import Blueprint, abort, make_response

from quads.web.blueprints.common import WEB_CONTENT_PATH, get_file_paths
from quads.web.blueprints.common import WEB_CONTENT_PATH

TEMPLATE_DIR = os.path.join(WEB_CONTENT_PATH, "instack")
instack_bp = Blueprint(
Expand All @@ -13,7 +13,7 @@


@instack_bp.route("/<file>")
def instack(file):
async def instack(file):
path = os.path.join(WEB_CONTENT_PATH, "instack")
file_path = os.path.join(path, file)
if not os.path.exists(file_path):
Expand Down
4 changes: 2 additions & 2 deletions src/quads/web/blueprints/visual.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@


@visual_bp.route("/")
def index():
async def index():
with open(os.path.join(VISUAL_DIR, "index.html"), "r") as f:
html_content = f.read()
return render_template("wiki/visuals.html", html_content=html_content)


@visual_bp.route("/<when>")
def visuals(when):
async def visuals(when):
file_paths = get_file_paths(VISUAL_DIR)
for file in file_paths:
if when in file:
Expand Down
44 changes: 22 additions & 22 deletions src/quads/web/blueprints/wiki.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@


@wiki_bp.route("/", methods=["GET", "POST"])
def index():
async def index():
return redirect(url_for("wiki.assignments"))


@wiki_bp.route("/assignments", methods=["GET", "POST"])
def assignments():
async def assignments():
headers = ["NAME", "SUMMARY", "OWNER", "REQUEST", "STATUS", "OSPENV", "OCPINV"]
host_headers = [
"ServerHostnamePublic",
Expand All @@ -56,57 +56,57 @@ def assignments():


@wiki_bp.route("/summary")
def summary():
async def summary():
cloud_operation = CloudOperations(quads_api=quads, foreman=foreman, loop=loop)
clouds_summary = cloud_operation.get_cloud_summary_report()
clouds_summary = await cloud_operation.get_cloud_summary_report()
return jsonify(clouds_summary)


@wiki_bp.route("/utilization")
def utilization():
async def utilization():
cloud_operation = CloudOperations(quads_api=quads, foreman=foreman, loop=loop)
daily_utilization = cloud_operation.get_daily_utilization()
daily_utilization = await cloud_operation.get_daily_utilization()
return jsonify(daily_utilization)


@wiki_bp.route("/managed/<cloud>")
def managed(cloud):
async def managed(cloud):
cloud_operation = CloudOperations(quads_api=quads, foreman=foreman, loop=loop)
managed_nodes = cloud_operation.get_managed_nodes(cloud)
managed_nodes = await cloud_operation.get_managed_nodes(cloud)
return jsonify(managed_nodes)


@wiki_bp.route("/unmanaged")
def unmanaged():
async def unmanaged():
cloud_operation = CloudOperations(quads_api=quads, foreman=foreman, loop=loop)
unmanaged_hosts = cloud_operation.get_unmanaged_hosts(exclude_hosts=Config["exclude_hosts"])
unmanaged_hosts = await cloud_operation.get_unmanaged_hosts(exclude_hosts=Config["exclude_hosts"])
return jsonify(unmanaged_hosts)


@wiki_bp.route("/broken")
def broken():
async def broken():
cloud_operation = CloudOperations(quads_api=quads, foreman=foreman, loop=loop)
domain_broken_hosts = cloud_operation.get_domain_broken_hosts(domain=Config["domain"])
domain_broken_hosts = await cloud_operation.get_domain_broken_hosts(domain=Config["domain"])
return jsonify(domain_broken_hosts)


@wiki_bp.route("/available", methods=["GET", "POST"])
def available():
async def available():
search = ModelSearchForm(request.form)
if request.method == "POST":
return search_results(search)
return await search_results(search)

return render_template("wiki/available.html", form=search, available_hosts=[])


@wiki_bp.route("/results")
def search_results(search):
available_hosts_list = available_hosts(search)
async def search_results(search):
available_hosts_list = await available_hosts(search)
return render_template("wiki/available.html", form=search, available_hosts=available_hosts_list)


@wiki_bp.route("/available_hosts")
def available_hosts(search):
async def available_hosts(search):
models = search.data["model"]
try:
start, end = [datetime.strptime(date, "%Y-%m-%d").date() for date in search.data["date_range"].split(" - ")]
Expand Down Expand Up @@ -147,7 +147,7 @@ def available_hosts(search):


@wiki_bp.route("/dashboard")
def create_inventory():
async def create_inventory():
headers = [
"U",
"ServerHostnamePublic",
Expand All @@ -164,8 +164,8 @@ def create_inventory():


@wiki_bp.route("/rack/<rack>")
def rack(rack):
rack_hosts = loop.run_until_complete(foreman.get_hosts_by_rack(rack))
async def rack(rack):
rack_hosts = await foreman.get_hosts_by_rack(rack)
blacklist = re.compile("|".join([re.escape(word) for word in Config["exclude_hosts"].split("|")]))
host_details = []
assignments_cache = {}
Expand Down Expand Up @@ -200,7 +200,7 @@ def rack(rack):


@wiki_bp.route("/vlans")
def create_vlans():
async def create_vlans():
cloud_operation = CloudOperations(quads_api=quads, foreman=foreman, loop=loop)
vlans = cloud_operation.get_vlans_list()
vlans = await cloud_operation.get_vlans_list()
return render_template("wiki/vlans.html", vlans=vlans)
32 changes: 18 additions & 14 deletions src/quads/web/controller/CloudOperations.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def __init__(self, quads_api, foreman, loop):
self.__foreman = foreman
self.__loop = loop

def __get_cloud_summary(self) -> list:
async def __get_cloud_summary(self) -> list:
"""
This method returns the cloud summary
"""
Expand All @@ -22,28 +22,32 @@ def __get_cloud_summary(self) -> list:
clouds_summary = summary_response.json()
return clouds_summary

def __get_all_hosts(self):
async def __get_all_hosts(self):
"""
This method returns the all hosts
This method returns all hosts
"""
all_hosts = self.__loop.run_until_complete(self.__foreman.get_all_hosts())
all_hosts = await self.__foreman.get_all_hosts()
return all_hosts

def get_managed_nodes(self, cloud):
async def get_managed_nodes(self, cloud):
"""
This method returns the scheduled nodes
"""
managed_hosts = {}
_ass_obj = self.__quads_api.get_active_cloud_assignment(cloud)
_hosts = self.__quads_api.filter_hosts({"cloud": cloud, "retired": False, "broken": False})

hosts = []
for host in _hosts:
_host = await self.__get_current_schedules(host.name)
hosts.append(_host)
if _ass_obj:
managed_hosts = {
"name": cloud,
"owner": _ass_obj.owner,
"count": len(_hosts),
"description": _ass_obj.description.strip(),
"hosts": [self.__get_current_schedules(host.name) for host in _hosts],
"hosts": hosts,
}
elif cloud == Config["spare_pool_name"]:
managed_hosts = {
Expand All @@ -56,7 +60,7 @@ def get_managed_nodes(self, cloud):

return managed_hosts

def get_daily_utilization(self) -> int:
async def get_daily_utilization(self) -> int:
"""
This method returns the daily utilization
"""
Expand All @@ -68,12 +72,12 @@ def get_daily_utilization(self) -> int:
_daily_utilization = _schedules * 100 // _host_count
return int(_daily_utilization)

def get_cloud_summary_report(self) -> list:
async def get_cloud_summary_report(self) -> list:
"""
This method returns the cloud summary
"""
clouds_summary = []
for cloud in self.__get_cloud_summary():
for cloud in await self.__get_cloud_summary():
if cloud.get("count") > 0:
cloud_name = cloud.get("name")
cloud["description"] = (
Expand Down Expand Up @@ -102,7 +106,7 @@ def get_cloud_summary_report(self) -> list:
clouds_summary.append(cloud)
return clouds_summary

def __get_current_schedules(self, host: str) -> dict:
async def __get_current_schedules(self, host: str) -> dict:
"""
This method returns the current schedules
"""
Expand Down Expand Up @@ -140,18 +144,18 @@ def __get_current_schedules(self, host: str) -> dict:
}
return current_schedule

def get_domain_broken_hosts(self, domain: str):
async def get_domain_broken_hosts(self, domain: str):
"""
This method returns the broken hosts
"""
broken_hosts = self.__quads_api.filter_hosts({"broken": True})
return [host.as_dict() for host in broken_hosts if domain in host.name]

def get_unmanaged_hosts(self, exclude_hosts: str):
async def get_unmanaged_hosts(self, exclude_hosts: str):
"""
This method returns the unmanaged hosts
"""
all_hosts = self.__get_all_hosts()
all_hosts = await self.__get_all_hosts()
blacklist = re.compile("|".join([re.escape(word) for word in exclude_hosts.split("|")]))
mgmt_hosts = [
property.get("sp_name")
Expand All @@ -176,7 +180,7 @@ def get_unmanaged_hosts(self, exclude_hosts: str):
)
return unmanaged_hosts

def get_vlans_list(self):
async def get_vlans_list(self):
"""
This method returns the vlans list
"""
Expand Down
2 changes: 2 additions & 0 deletions src/quads/web/static/js/jquery-3.7.1.min.js

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions src/quads/web/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,8 @@
{%- endblock content %}

{% block scripts %}
<script src="https://code.jquery.com/jquery-3.7.1.min.js"
integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
crossorigin="anonymous"></script>
<script type="text/javascript"
src="{{ url_for('static', filename='js/jquery-3.7.1.min.js') }}"></script>
<script type="text/javascript"
src="{{ url_for('static', filename='js/popper.min.js') }}"></script>
<script type="text/javascript"
Expand Down
24 changes: 23 additions & 1 deletion src/quads/web/templates/wiki/assignments.html
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ <h3>
]);
};

const cloudRequests = {};

function updateHostsForCloud(cloudName) {
const $table = $(`#hosts-${cloudName}`);
const host_headers = {{ host_headers|tojson|safe }};
Expand All @@ -113,9 +115,16 @@ <h3>
)
);

$.ajax({
// Abort any existing request for this cloud
if (cloudRequests[cloudName]) {
cloudRequests[cloudName].abort();
}

// Store the new AJAX request
cloudRequests[cloudName] = $.ajax({
url: `/managed/${cloudName}`,
method: 'GET',
async: true,
success: function(data) {
$tbody.empty();

Expand Down Expand Up @@ -164,6 +173,15 @@ <h3>
});
};

$(window).on('beforeunload', function() {
// Abort all cloud-specific requests
for (const cloudName in cloudRequests) {
if (cloudRequests[cloudName]) {
cloudRequests[cloudName].abort();
}
}
});

function updateSummaryData() {
const $table = $(`#summary`);
const headers = {{ headers|tojson|safe }};
Expand Down Expand Up @@ -196,6 +214,7 @@ <h3>
$.ajax({
url: '/summary',
method: 'GET',
async: true,
success: function(data) {
const $tbody = $table.find('tbody').empty();

Expand Down Expand Up @@ -323,6 +342,7 @@ <h3>
$.ajax({
url: '/utilization',
method: 'GET',
async: true,
success: function(data) {
$('#daily-utilization').text(`Daily Utilization: ${data}%`);
},
Expand Down Expand Up @@ -356,6 +376,7 @@ <h3>
$.ajax({
url: '/unmanaged',
method: 'GET',
async: true,
success: function(data) {
$tbody.empty();

Expand Down Expand Up @@ -411,6 +432,7 @@ <h3>
$.ajax({
url: '/broken',
method: 'GET',
async: true,
success: function(data) {
$tbody.empty();

Expand Down
Loading

0 comments on commit 2f15b8f

Please sign in to comment.