From e8efde98920d18bb4a9ca0c0084b45a2d8c547ab Mon Sep 17 00:00:00 2001 From: "Magel, Denis" Date: Thu, 18 Sep 2025 12:16:30 +0200 Subject: [PATCH 1/2] feat: added functionality inside GET/aggregates --- src/aggregate/aggregate_router.py | 8 +------- src/aggregate/aggregate_schema.py | 9 ++++++++- src/aggregate/aggregate_service.py | 32 ++++++++++++++++++++---------- src/utils.py | 23 +++++++++++++++++++++ 4 files changed, 54 insertions(+), 18 deletions(-) create mode 100644 src/utils.py diff --git a/src/aggregate/aggregate_router.py b/src/aggregate/aggregate_router.py index b33556b..fdb87af 100644 --- a/src/aggregate/aggregate_router.py +++ b/src/aggregate/aggregate_router.py @@ -1,16 +1,10 @@ # contains the router for the aggregates endpoint from fastapi import APIRouter, Query -from enum import Enum from typing import List -from .aggregate_schema import AggregateSchema +from .aggregate_schema import AggregateSchema, MetricEnum from .aggregate_service import get_aggregates -class MetricEnum(str, Enum): - relative = "relative" - absolute = "absolute" - - router = APIRouter(tags=["aggregates"]) diff --git a/src/aggregate/aggregate_schema.py b/src/aggregate/aggregate_schema.py index 4edd30a..d197e4f 100644 --- a/src/aggregate/aggregate_schema.py +++ b/src/aggregate/aggregate_schema.py @@ -1,8 +1,15 @@ # contains the schema definitions for aggregates from pydantic import BaseModel +from enum import Enum class AggregateSchema(BaseModel): aggregate: str node: str - available: str + available: int + available_str: str + + +class MetricEnum(str, Enum): + relative = "relative" + absolute = "absolute" diff --git a/src/aggregate/aggregate_service.py b/src/aggregate/aggregate_service.py index 0a40557..8142bae 100644 --- a/src/aggregate/aggregate_service.py +++ b/src/aggregate/aggregate_service.py @@ -1,24 +1,36 @@ # contains the business logic for aggregates + from typing import List -from .aggregate_schema import AggregateSchema +from .aggregate_schema import AggregateSchema, MetricEnum +from logging import getLogger +from ..utils import round_bytes, get_data_from_ontap + +logger = getLogger("uvicorn") +logger.setLevel("DEBUG") async def get_aggregates(metric: str = "relative") -> List[AggregateSchema]: # Dummy data for demonstration # You can use the metric parameter to filter or modify results as needed # For now, just return the same data and show metric usage - print(f"Metric used: {metric}") + logger.debug(f"Metric used: {metric}") + + __aggregates = get_data_from_ontap(logger, "172.16.57.2", "admin", "Netapp12", "storage/aggregates", "fields=name,uuid,space,node,home_node") + logger.debug(__aggregates) + __aggregates = __aggregates.get("records") + if metric == MetricEnum.relative: + __aggregates = sorted(__aggregates, key=lambda r: r["space"]["block_storage"].get("used_percent"), reverse=True) + elif metric == MetricEnum.absolute: + __aggregates = sorted(__aggregates, key=lambda r: r["space"]["block_storage"].get("available"), reverse=False) aggregates: list = [ AggregateSchema( - aggregate="Aggregate A", node="cluster01-01", available="100.0TB" - ), - AggregateSchema( - aggregate="Aggregate B", node="cluster01-01", available="200.5GB" - ), - AggregateSchema( - aggregate="Aggregate C", node="cluster01-02", available="300.75MB" - ), + aggregate=a["name"], + node=a["node"]["name"], + available=a["space"]["block_storage"]["available"], + available_str=round_bytes(a["space"]["block_storage"]["available"]), + ) + for a in __aggregates ] return aggregates diff --git a/src/utils.py b/src/utils.py new file mode 100644 index 0000000..09f14e1 --- /dev/null +++ b/src/utils.py @@ -0,0 +1,23 @@ +import httpx + +def round_bytes(size_in_bytes: int) -> str: + # Helper function to convert bytes to a human-readable format + for unit in ["B", "KiB", "MiB", "GiB", "TiB", "PiB"]: + if size_in_bytes < 1024: + return f"{size_in_bytes:.2f}{unit}" + size_in_bytes /= 1024 + return f"{size_in_bytes:.2f}EB" + + +def get_data_from_ontap(logger, hostname: str, username: str, password: str, endpoint: str, query_string: str = ""): + url = f"https://{hostname}/api/{endpoint}" + if query_string: + url += f"?{query_string}" + try: + logger.debug(f"Fetching data from ONTAP: {url}") + response = httpx.get(url, auth=(username, password), verify=False) + response.raise_for_status() + return response.json() + except httpx.HTTPError as e: + logger.error(f"HTTP error occurred: {e}") + return None -- 2.49.1 From fc71950039d8d9e329e7f61efd847bcdc262dee5 Mon Sep 17 00:00:00 2001 From: "Magel, Denis" Date: Thu, 18 Sep 2025 13:39:22 +0200 Subject: [PATCH 2/2] refactor: added async await to ONTAP call --- src/aggregate/aggregate_router.py | 5 +++-- src/aggregate/aggregate_service.py | 8 +++++--- src/main.py | 18 +++++++++++++++--- src/utils.py | 19 ++++++++++--------- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/aggregate/aggregate_router.py b/src/aggregate/aggregate_router.py index fdb87af..81864e3 100644 --- a/src/aggregate/aggregate_router.py +++ b/src/aggregate/aggregate_router.py @@ -1,5 +1,5 @@ # contains the router for the aggregates endpoint -from fastapi import APIRouter, Query +from fastapi import APIRouter, Query, Request from typing import List from .aggregate_schema import AggregateSchema, MetricEnum from .aggregate_service import get_aggregates @@ -10,6 +10,7 @@ router = APIRouter(tags=["aggregates"]) @router.get("/aggregates", response_model=List[AggregateSchema]) async def aggregates_endpoint( + request: Request, metric: MetricEnum = Query(MetricEnum.relative, description="Metric type"), ): - return await get_aggregates(metric) + return await get_aggregates(request, metric) diff --git a/src/aggregate/aggregate_service.py b/src/aggregate/aggregate_service.py index 8142bae..7a37d87 100644 --- a/src/aggregate/aggregate_service.py +++ b/src/aggregate/aggregate_service.py @@ -1,6 +1,8 @@ # contains the business logic for aggregates from typing import List + +from fastapi import Request from .aggregate_schema import AggregateSchema, MetricEnum from logging import getLogger from ..utils import round_bytes, get_data_from_ontap @@ -9,13 +11,13 @@ logger = getLogger("uvicorn") logger.setLevel("DEBUG") -async def get_aggregates(metric: str = "relative") -> List[AggregateSchema]: +async def get_aggregates(request: Request, metric: str = "relative") -> List[AggregateSchema]: # Dummy data for demonstration # You can use the metric parameter to filter or modify results as needed # For now, just return the same data and show metric usage logger.debug(f"Metric used: {metric}") - - __aggregates = get_data_from_ontap(logger, "172.16.57.2", "admin", "Netapp12", "storage/aggregates", "fields=name,uuid,space,node,home_node") + client = request.app.requests_client + __aggregates = await get_data_from_ontap(client, logger, "172.16.57.2", "admin", "Netapp12", "storage/aggregates", "fields=name,uuid,space,node,home_node") logger.debug(__aggregates) __aggregates = __aggregates.get("records") if metric == MetricEnum.relative: diff --git a/src/main.py b/src/main.py index 8e902bf..ea4abdf 100644 --- a/src/main.py +++ b/src/main.py @@ -1,14 +1,26 @@ -from src.service import load_config -from fastapi import FastAPI import logging + +from fastapi import FastAPI +from contextlib import asynccontextmanager +import httpx + from src.aggregate import aggregate_router +from src.service import load_config logger = logging.getLogger("uvicorn") logger.info("Starting application") config = load_config() -app = FastAPI() + +@asynccontextmanager +async def lifespan(app: FastAPI): + app.requests_client = httpx.AsyncClient(verify=False) + yield + await app.requests_client.aclose() + + +app = FastAPI(lifespan=lifespan) app.include_router(aggregate_router) diff --git a/src/utils.py b/src/utils.py index 09f14e1..febeea5 100644 --- a/src/utils.py +++ b/src/utils.py @@ -9,15 +9,16 @@ def round_bytes(size_in_bytes: int) -> str: return f"{size_in_bytes:.2f}EB" -def get_data_from_ontap(logger, hostname: str, username: str, password: str, endpoint: str, query_string: str = ""): +async def get_data_from_ontap(client, logger, hostname: str, username: str, password: str, endpoint: str, query_string: str = ""): url = f"https://{hostname}/api/{endpoint}" if query_string: url += f"?{query_string}" - try: - logger.debug(f"Fetching data from ONTAP: {url}") - response = httpx.get(url, auth=(username, password), verify=False) - response.raise_for_status() - return response.json() - except httpx.HTTPError as e: - logger.error(f"HTTP error occurred: {e}") - return None + async with client as _client: + try: + logger.debug(f"Fetching data from ONTAP: {url}") + response = await _client.get(url, auth=(username, password)) + response.raise_for_status() + return response.json() + except httpx.HTTPError as e: + logger.error(f"HTTP error occurred: {e}") + return None -- 2.49.1