From 579c62319cb2ec30ebb3e7496b36e7d817c90a83 Mon Sep 17 00:00:00 2001 From: Pascal Scheiben Date: Thu, 18 Sep 2025 11:49:34 +0200 Subject: [PATCH 1/7] Adding config endpoint --- src/config/__init__.py | 3 +++ src/config/config.http | 14 ++++++++++++++ src/config/router.py | 16 ++++++++++++++++ src/config/schema.py | 21 +++++++++++++++++++++ src/config/service.py | 3 +++ src/main.py | 32 ++++++++++++++++---------------- 6 files changed, 73 insertions(+), 16 deletions(-) create mode 100644 src/config/__init__.py create mode 100644 src/config/config.http create mode 100644 src/config/router.py create mode 100644 src/config/schema.py create mode 100644 src/config/service.py diff --git a/src/config/__init__.py b/src/config/__init__.py new file mode 100644 index 0000000..c97d2ea --- /dev/null +++ b/src/config/__init__.py @@ -0,0 +1,3 @@ +from src.config.router import router as config_router + +__all__ = ["config_router"] diff --git a/src/config/config.http b/src/config/config.http new file mode 100644 index 0000000..6c70986 --- /dev/null +++ b/src/config/config.http @@ -0,0 +1,14 @@ +POST http://127.0.0.1:8000/config +Content-Type: application/json + +{ + "cluster_list": [ + { + "hostname": "cluster1.demo.netapp.com", + "username": "admin", + "password": "Netapp1!" + } + ] +} + +### diff --git a/src/config/router.py b/src/config/router.py new file mode 100644 index 0000000..0c9b0c2 --- /dev/null +++ b/src/config/router.py @@ -0,0 +1,16 @@ +from fastapi import APIRouter +from .schema import ConfigSchema, ConfigReturnSchema +import logging + +logger = logging.getLogger("uvicorn") + +router = APIRouter(tags=["config"]) + + +@router.post("/config", response_model=ConfigReturnSchema) +async def create_config(config: ConfigSchema) -> ConfigSchema: + """ + Endpoint to receive and return configuration data. + """ + logger.info("Received configuration data") + return config diff --git a/src/config/schema.py b/src/config/schema.py new file mode 100644 index 0000000..d59c311 --- /dev/null +++ b/src/config/schema.py @@ -0,0 +1,21 @@ +# contains the schema definitions for the aggregate service +from pydantic import BaseModel + + +class ConfigEntrySchema(BaseModel): + hostname: str + username: str + password: str + + +class ConfigOutSchema(BaseModel): + hostname: str + username: str + + +class ConfigReturnSchema(BaseModel): + cluster_list: list[ConfigOutSchema] + + +class ConfigSchema(BaseModel): + cluster_list: list[ConfigEntrySchema] diff --git a/src/config/service.py b/src/config/service.py new file mode 100644 index 0000000..cd81419 --- /dev/null +++ b/src/config/service.py @@ -0,0 +1,3 @@ +# contains the business logic for the aggregate service +async def example_service() -> str: + return "This is an aggregate service" diff --git a/src/main.py b/src/main.py index bec035d..9aaa436 100644 --- a/src/main.py +++ b/src/main.py @@ -1,30 +1,30 @@ import os -import json import logging -import yaml +from src.aggregate import aggregate_router +from src.config import config_router -from pathlib import Path -from dotenv import load_dotenv -from redis import Redis from contextlib import asynccontextmanager -from pydantic import BaseModel, ValidationError, SecretStr, AnyHttpUrl -from typing import Optional, Literal, List, Union from fastapi import FastAPI +from database import setup_db_conn, get_config_from_db +from src.initialize import initialize_config +from utils import setup_logging +logger = logging.getLogger("uvicorn") +logger.info("Starting application") + +app = FastAPI() +app.include_router(aggregate_router) +app.include_router(config_router) -from database import setup_db_conn, get_inventory_from_redis, get_config_from_db -from src.initialize import initialize_config -from utils import setup_logging - @asynccontextmanager async def lifespan(app: FastAPI): - ''' make loading it async''' - log = logging.getLogger('uvicorn') + """make loading it async""" + log = logging.getLogger("uvicorn") cfg_init_result = initialize_config() - - shared_redis_conn = setup_db_conn(os.getenv('redis_host'), os.getenv('redis_port')) + + shared_redis_conn = setup_db_conn(os.getenv("redis_host"), os.getenv("redis_port")) if not shared_redis_conn: log.error("Cannot connect to Redis DB. Exiting...") exit(1) @@ -40,7 +40,7 @@ async def lifespan(app: FastAPI): setup_logging() -log = logging.getLogger('uvicorn') +log = logging.getLogger("uvicorn") log.info("Starting FastAPI app...") app = FastAPI(lifespan=lifespan) -- 2.49.1 From 1a4e2ff68855e43f66373c5852a8189924f94b77 Mon Sep 17 00:00:00 2001 From: Pascal Scheiben Date: Thu, 18 Sep 2025 12:05:03 +0200 Subject: [PATCH 2/7] Rewriting comments --- src/config/router.py | 2 +- src/config/schema.py | 2 +- src/config/service.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/config/router.py b/src/config/router.py index 0c9b0c2..b2da53f 100644 --- a/src/config/router.py +++ b/src/config/router.py @@ -10,7 +10,7 @@ router = APIRouter(tags=["config"]) @router.post("/config", response_model=ConfigReturnSchema) async def create_config(config: ConfigSchema) -> ConfigSchema: """ - Endpoint to receive and return configuration data. + Endpoint to receive and store configuration data. """ logger.info("Received configuration data") return config diff --git a/src/config/schema.py b/src/config/schema.py index d59c311..fb6e2f1 100644 --- a/src/config/schema.py +++ b/src/config/schema.py @@ -1,4 +1,4 @@ -# contains the schema definitions for the aggregate service +# contains the schema definitions for the config service from pydantic import BaseModel diff --git a/src/config/service.py b/src/config/service.py index cd81419..e5b650f 100644 --- a/src/config/service.py +++ b/src/config/service.py @@ -1,3 +1,3 @@ -# contains the business logic for the aggregate service -async def example_service() -> str: - return "This is an aggregate service" +# contains the business logic for the config service +async def save_config() -> None: + ... -- 2.49.1 From ab521699870ebaadb07e44678e13636de3af3716 Mon Sep 17 00:00:00 2001 From: Pascal Scheiben Date: Thu, 18 Sep 2025 12:09:33 +0200 Subject: [PATCH 3/7] Enhancing comments, adding stub for business logic --- src/config/service.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/config/service.py b/src/config/service.py index e5b650f..fcdf606 100644 --- a/src/config/service.py +++ b/src/config/service.py @@ -1,3 +1,2 @@ # contains the business logic for the config service -async def save_config() -> None: - ... +async def save_config() -> None: ... -- 2.49.1 From 72992d651d3d1763aaf893787253ea4a308f2a3c Mon Sep 17 00:00:00 2001 From: Pascal Scheiben Date: Thu, 18 Sep 2025 12:10:29 +0200 Subject: [PATCH 4/7] Fixing typos --- src/config/router.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/config/router.py b/src/config/router.py index b2da53f..57679a6 100644 --- a/src/config/router.py +++ b/src/config/router.py @@ -7,10 +7,14 @@ logger = logging.getLogger("uvicorn") router = APIRouter(tags=["config"]) -@router.post("/config", response_model=ConfigReturnSchema) +@router.post( + "/config", summary="Upload a configuration", response_model=ConfigReturnSchema +) async def create_config(config: ConfigSchema) -> ConfigSchema: """ Endpoint to receive and store configuration data. + + ⚠️ at this time the configuration is not stored anywhere. It's like logging to /dev/null """ logger.info("Received configuration data") return config -- 2.49.1 From 9d12045b812a4fafaae0c88047ea87f2bfd9659d Mon Sep 17 00:00:00 2001 From: Pascal Scheiben Date: Thu, 18 Sep 2025 13:49:54 +0200 Subject: [PATCH 5/7] Arranging imports --- src/aggregate/__init__.py | 1 + src/config/router.py | 6 ++++-- src/example/router.py | 1 + src/main.py | 4 +++- src/service.py | 3 ++- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/aggregate/__init__.py b/src/aggregate/__init__.py index c55c18d..e94ee4f 100644 --- a/src/aggregate/__init__.py +++ b/src/aggregate/__init__.py @@ -1,4 +1,5 @@ from src.example.router import router as example_router + from .aggregate_router import router as aggregate_router __all__ = ["example_router", "aggregate_router"] diff --git a/src/config/router.py b/src/config/router.py index 57679a6..f9abe76 100644 --- a/src/config/router.py +++ b/src/config/router.py @@ -1,7 +1,9 @@ -from fastapi import APIRouter -from .schema import ConfigSchema, ConfigReturnSchema import logging +from fastapi import APIRouter + +from .schema import ConfigReturnSchema, ConfigSchema + logger = logging.getLogger("uvicorn") router = APIRouter(tags=["config"]) diff --git a/src/example/router.py b/src/example/router.py index 4c5096d..ab4f0f0 100644 --- a/src/example/router.py +++ b/src/example/router.py @@ -1,5 +1,6 @@ # contains the router for the aggregate endpoint from fastapi import APIRouter + from .schema import ExampleSchema router = APIRouter(tags=["aggregate"]) diff --git a/src/main.py b/src/main.py index 9aaa436..7863649 100644 --- a/src/main.py +++ b/src/main.py @@ -1,11 +1,13 @@ import os import logging + +from fastapi import FastAPI + from src.aggregate import aggregate_router from src.config import config_router from contextlib import asynccontextmanager -from fastapi import FastAPI from database import setup_db_conn, get_config_from_db from src.initialize import initialize_config from utils import setup_logging diff --git a/src/service.py b/src/service.py index 379bb09..fb0bc27 100644 --- a/src/service.py +++ b/src/service.py @@ -1,6 +1,7 @@ -from dotenv import dotenv_values import logging +from dotenv import dotenv_values + from src.schema import ConfigSchema logger = logging.getLogger("uvicorn") -- 2.49.1 From 767f43551e10b511daf1e8b084f29309811a7553 Mon Sep 17 00:00:00 2001 From: "Magel, Denis" Date: Thu, 18 Sep 2025 14:23:19 +0200 Subject: [PATCH 6/7] rebased --- .env | 11 +++-------- src/aggregate/__init__.py | 2 +- src/aggregate/aggregate_service.py | 4 ++-- src/database.py | 2 +- src/example/router.py | 2 +- src/example/schema.py | 11 +++++++---- src/initialize.py | 27 ++++++++++++++------------- src/main.py | 4 ++-- 8 files changed, 31 insertions(+), 32 deletions(-) diff --git a/.env b/.env index 65c8a17..cc0ff43 100644 --- a/.env +++ b/.env @@ -1,8 +1,3 @@ -# Environment variables for NetApp ONTAP clusters -CLUSTER1_HOSTNAME=172.16.57.2 -CLUSTER1_USERNAME=admin -CLUSTER1_PASSWORD=Netapp12 - -CLUSTER2_HOSTNAME=172.16.56.2 -CLUSTER2_USERNAME=admin -CLUSTER2_PASSWORD=Netapp12 \ No newline at end of file +cluster_inventory_path = ../config/inventory.yml +redis_host = '172.16.0.208' +redis_port = '6379' \ No newline at end of file diff --git a/src/aggregate/__init__.py b/src/aggregate/__init__.py index e94ee4f..b91fa84 100644 --- a/src/aggregate/__init__.py +++ b/src/aggregate/__init__.py @@ -1,5 +1,5 @@ from src.example.router import router as example_router -from .aggregate_router import router as aggregate_router +from src.aggregate.aggregate_router import router as aggregate_router __all__ = ["example_router", "aggregate_router"] diff --git a/src/aggregate/aggregate_service.py b/src/aggregate/aggregate_service.py index 7a37d87..af35121 100644 --- a/src/aggregate/aggregate_service.py +++ b/src/aggregate/aggregate_service.py @@ -3,9 +3,9 @@ from typing import List from fastapi import Request -from .aggregate_schema import AggregateSchema, MetricEnum +from src.aggregate.aggregate_schema import AggregateSchema, MetricEnum from logging import getLogger -from ..utils import round_bytes, get_data_from_ontap +from src.utils import round_bytes, get_data_from_ontap logger = getLogger("uvicorn") logger.setLevel("DEBUG") diff --git a/src/database.py b/src/database.py index a278d98..c014138 100644 --- a/src/database.py +++ b/src/database.py @@ -3,7 +3,7 @@ import logging from redis import Redis, ConnectionError from typing import List from pydantic import TypeAdapter -from schema import ConfigSchema +from src.schema import ConfigSchema def setup_db_conn(redishost, redisport: str): diff --git a/src/example/router.py b/src/example/router.py index ab4f0f0..e9f11f2 100644 --- a/src/example/router.py +++ b/src/example/router.py @@ -1,7 +1,7 @@ # contains the router for the aggregate endpoint from fastapi import APIRouter -from .schema import ExampleSchema +from src.example.schema import ExampleSchema router = APIRouter(tags=["aggregate"]) diff --git a/src/example/schema.py b/src/example/schema.py index d0ae38a..0e564e0 100644 --- a/src/example/schema.py +++ b/src/example/schema.py @@ -1,15 +1,18 @@ # contains the schema definitions for the aggregate service from pydantic import BaseModel +from pathlib import Path class ExampleSchema(BaseModel): example_field: str another_field: int + class ClusterCreds(BaseModel): """A structure to hold basic auth cluster credentials for a cluster""" - username: str - password: str - hostname: str = None + + username: str + password: str + hostname: str = None cert_filepath: Path = None - key_filepath: Path = None + key_filepath: Path = None diff --git a/src/initialize.py b/src/initialize.py index dcdd84d..b6c6298 100644 --- a/src/initialize.py +++ b/src/initialize.py @@ -5,40 +5,41 @@ import yaml from pathlib import Path from dotenv import load_dotenv -from database import setup_db_conn -from schema import ConfigSchema +from src.database import setup_db_conn +from src.schema import ConfigSchema from typing import List from pydantic import TypeAdapter + def initialize_config(): load_dotenv() - log = logging.getLogger('uvicorn') - ENV_INVENTORYPATH = os.getenv('cluster_inventory_path') - ENV_REDISHOST = os.getenv('redis_host') - ENV_REDISPORT = os.getenv('redis_port') + log = logging.getLogger("uvicorn") + ENV_INVENTORYPATH = os.getenv("cluster_inventory_path") + ENV_REDISHOST = os.getenv("redis_host") + ENV_REDISPORT = os.getenv("redis_port") log.info(f"Found Cluster Inventory file at: {ENV_INVENTORYPATH}") if not ENV_INVENTORYPATH or not Path(ENV_INVENTORYPATH).is_file(): print(f"FATAL: Inventory file {ENV_INVENTORYPATH} is missing or not a file.") return False try: - with open(ENV_INVENTORYPATH, 'r') as f: + with open(ENV_INVENTORYPATH, "r") as f: inv = yaml.safe_load(f) - inventory = json.dumps(inv) + inventory = json.dumps(inv) except Exception as e: print(f"FATAL: Cannot read inventory file {ENV_INVENTORYPATH}. Err: {e}") return False - print(f'[INFO] Importing configuration to DB...') + print(f"[INFO] Importing configuration to DB...") try: GLOBAL_INVENTORY_VALID = TypeAdapter(List[ConfigSchema]).validate_python(inv) redis_conn = setup_db_conn(ENV_REDISHOST, ENV_REDISPORT) - redis_conn.hset('cluster_inventory', mapping={'inventory': inventory}) + redis_conn.hset("cluster_inventory", mapping={"inventory": inventory}) redis_conn.close() - log.info("Configuration has been loaded.") + log.info("Configuration has been loaded.") return True - except Exception as e: + except Exception as e: print(f"FATAL: Redis DB error: {e}") - return False \ No newline at end of file + return False diff --git a/src/main.py b/src/main.py index 7863649..7bcf9c7 100644 --- a/src/main.py +++ b/src/main.py @@ -8,9 +8,9 @@ from src.config import config_router from contextlib import asynccontextmanager -from database import setup_db_conn, get_config_from_db +from .database import setup_db_conn, get_config_from_db from src.initialize import initialize_config -from utils import setup_logging +from .utils import setup_logging logger = logging.getLogger("uvicorn") logger.info("Starting application") -- 2.49.1 From 60008fa9478432fa9f912c5ed9e836e79969b8a5 Mon Sep 17 00:00:00 2001 From: "Magel, Denis" Date: Thu, 18 Sep 2025 14:33:30 +0200 Subject: [PATCH 7/7] fix: adjusted paths to run from root dir updated inventory.yaml --- .env | 2 +- config/inventory.yml | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.env b/.env index cc0ff43..c7d6d68 100644 --- a/.env +++ b/.env @@ -1,3 +1,3 @@ -cluster_inventory_path = ../config/inventory.yml +cluster_inventory_path = config/inventory.yml redis_host = '172.16.0.208' redis_port = '6379' \ No newline at end of file diff --git a/config/inventory.yml b/config/inventory.yml index 6433930..415015a 100644 --- a/config/inventory.yml +++ b/config/inventory.yml @@ -1,8 +1,6 @@ -- 1: - hostname: '172.16.57.2' - username: 'admin' - password: 'Netapp12' -- 2: - hostname: '172.16.56.2' - username: 'admin' - password: 'Netapp12' +- hostname: "172.16.57.2" + username: "admin" + password: "Netapp12" +- hostname: "172.16.56.2" + username: "admin" + password: "Netapp12" -- 2.49.1