diff --git a/.env b/.env index 65c8a17..c7d6d68 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/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" diff --git a/src/aggregate/__init__.py b/src/aggregate/__init__.py index c55c18d..b91fa84 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 + +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/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..f9abe76 --- /dev/null +++ b/src/config/router.py @@ -0,0 +1,22 @@ +import logging + +from fastapi import APIRouter + +from .schema import ConfigReturnSchema, ConfigSchema + +logger = logging.getLogger("uvicorn") + +router = APIRouter(tags=["config"]) + + +@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 diff --git a/src/config/schema.py b/src/config/schema.py new file mode 100644 index 0000000..fb6e2f1 --- /dev/null +++ b/src/config/schema.py @@ -0,0 +1,21 @@ +# contains the schema definitions for the config 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..fcdf606 --- /dev/null +++ b/src/config/service.py @@ -0,0 +1,2 @@ +# contains the business logic for the config service +async def save_config() -> None: ... 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 4c5096d..e9f11f2 100644 --- a/src/example/router.py +++ b/src/example/router.py @@ -1,6 +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 bec035d..7bcf9c7 100644 --- a/src/main.py +++ b/src/main.py @@ -1,30 +1,32 @@ import os -import json import logging -import yaml -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 src.aggregate import aggregate_router +from src.config import config_router + +from contextlib import asynccontextmanager + +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 +42,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) 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")