From 1aabc59e83aa4c137c38ad8de2895c00833f304a Mon Sep 17 00:00:00 2001 From: Erik Reid <erik.reid@geant.org> Date: Mon, 20 Mar 2023 05:31:46 +0100 Subject: [PATCH] added state to schema, and initialize correct value --- resource_management/db/model.py | 59 +++++++++++++++--------- resource_management/router_interfaces.py | 33 ++++++++----- resource_management/routes/interfaces.py | 23 +++++++-- test/test_interfaces_routes.py | 6 +-- test/test_parse_router.py | 2 +- 5 files changed, 83 insertions(+), 40 deletions(-) diff --git a/resource_management/db/model.py b/resource_management/db/model.py index 4203d87..b8e3c54 100644 --- a/resource_management/db/model.py +++ b/resource_management/db/model.py @@ -5,10 +5,11 @@ Resources Database This database holds information about router port resources """ +import enum import logging from typing import Any -from sqlalchemy import Column, ForeignKey, Integer, String +import sqlalchemy as sa from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship @@ -18,10 +19,16 @@ logger = logging.getLogger(__name__) base_schema: Any = declarative_base() +class AvalabilityStates(enum.Enum): + AVAILABLE = enum.auto() + USED = enum.auto() + RESERVED = enum.auto() + + class Router(base_schema): __tablename__ = 'routers' - id = Column('id', Integer, primary_key=True) - fqdn = Column('fqdn', String, nullable=False) + id = sa.Column('id', sa.Integer, primary_key=True) + fqdn = sa.Column('fqdn', sa.String, nullable=False) physical = relationship( "PhysicalInterface", @@ -36,13 +43,18 @@ class Router(base_schema): class LAG(base_schema): __tablename__ = 'lags' - id = Column('id', Integer, primary_key=True) - name = Column('name', String, nullable=False) + id = sa.Column('id', sa.Integer, primary_key=True) + name = sa.Column('name', sa.String, nullable=False) + + availability = sa.Column( + 'availability', + sa.Enum(*[a.name for a in AvalabilityStates]), + nullable=False) - router_id = Column( + router_id = sa.Column( 'router_id', - Integer, - ForeignKey('routers.id'), + sa.Integer, + sa.ForeignKey('routers.id'), nullable=False) router = relationship('Router', back_populates='lags') @@ -54,20 +66,25 @@ class LAG(base_schema): class PhysicalInterface(base_schema): __tablename__ = 'physical_interfaces' - id = Column('id', Integer, primary_key=True) - name = Column('name', String, nullable=False) + id = sa.Column('id', sa.Integer, primary_key=True) + name = sa.Column('name', sa.String, nullable=False) + + availability = sa.Column( + 'availability', + sa.Enum(*[a.name for a in AvalabilityStates]), + nullable=False) - router_id = Column( + router_id = sa.Column( 'router_id', - Integer, - ForeignKey('routers.id'), + sa.Integer, + sa.ForeignKey('routers.id'), nullable=False) router = relationship('Router', back_populates='physical') - lag_id = Column( + lag_id = sa.Column( 'lag_id', - Integer, - ForeignKey('lags.id'), + sa.Integer, + sa.ForeignKey('lags.id'), nullable=True) lag = relationship('LAG', back_populates='physical') @@ -79,11 +96,11 @@ class PhysicalInterface(base_schema): class LogicalInterface(base_schema): __tablename__ = 'logical_interfaces' - id = Column('id', Integer, primary_key=True) - name = Column('name', String, nullable=False) - physical_id = Column( + id = sa.Column('id', sa.Integer, primary_key=True) + name = sa.Column('name', sa.String, nullable=False) + physical_id = sa.Column( 'physical_id', - Integer, - ForeignKey('physical_interfaces.id'), + sa.Integer, + sa.ForeignKey('physical_interfaces.id'), nullable=False) physical = relationship('PhysicalInterface', back_populates='logical') diff --git a/resource_management/router_interfaces.py b/resource_management/router_interfaces.py index d0cbc88..5ef8d6c 100644 --- a/resource_management/router_interfaces.py +++ b/resource_management/router_interfaces.py @@ -6,12 +6,10 @@ from resource_management.db import model logger = logging.getLogger(__name__) -def load_router_interfaces(fqdn: str): +def load_new_router_interfaces(fqdn: str): """ load all interface info from the router and update the db with the current information - - TODO: merge, not just create new """ params = config.load() @@ -25,11 +23,19 @@ def load_router_interfaces(fqdn: str): physical = juniper.load_installed_ethernet_ports(dev) logical = juniper.load_logical_interfaces(dev) - def _find_lag_name(interface_name): - for name, interfaces in aggregates.items(): - if interface_name in interfaces: - return name - return None + _inverted_aggregate_map = {} + for lag_name, physical_interfaces in aggregates.items(): + for pn in physical_interfaces: + _inverted_aggregate_map[pn] = lag_name + + def _interface_availability(ifc_name): + if physical[ifc_name]['oper']: + return model.AvalabilityStates.USED.name + if ifc_name in _inverted_aggregate_map: + return model.AvalabilityStates.USED.name + if ifc_name in logical: + return model.AvalabilityStates.USED.name + return model.AvalabilityStates.AVAILABLE.name with db.session_scope() as session: @@ -38,13 +44,15 @@ def load_router_interfaces(fqdn: str): # add all the lag records # keep a map for referencing below when adding physical interfaces - agg_records = {} + lag_records = {} for a in aggregates.keys(): record = model.LAG( name=a, + # if present, then used + availability=model.AvalabilityStates.USED.name, router=router_record) session.add(record) - agg_records[a] = record + lag_records[a] = record # add all the physical interface records # keep a map for referencing below when adding logical interfaces @@ -52,10 +60,11 @@ def load_router_interfaces(fqdn: str): for physical_name, _ in physical.items(): record = model.PhysicalInterface( name=physical_name, + availability=_interface_availability(physical_name), router=router_record) - lag_name = _find_lag_name(physical_name) + lag_name = _inverted_aggregate_map.get(physical_name, None) if lag_name: - record.lag = agg_records[lag_name] + record.lag = lag_records[lag_name] physical_records[physical_name] = record session.add(record) diff --git a/resource_management/routes/interfaces.py b/resource_management/routes/interfaces.py index 8a480c3..edf26cd 100644 --- a/resource_management/routes/interfaces.py +++ b/resource_management/routes/interfaces.py @@ -1,6 +1,8 @@ from fastapi import APIRouter, HTTPException import pydantic +import sqlalchemy as sa + from resource_management import db from resource_management import config from resource_management.db import model @@ -31,13 +33,21 @@ class NextPhysicalInterface(pydantic.BaseModel): name: str -@router.post('/import/{fqdn}') -async def load_router_interfaces(fqdn: str) -> InterfacesSummary: +@router.post('/initialize-router/{fqdn}') +async def load_new_router_interfaces(fqdn: str) -> InterfacesSummary: params = config.load() db.init_db_model(params['db']) - router_interfaces.load_router_interfaces(fqdn) + with db.session_scope() as session: + num_routers = session.query(sa.func.count(model.Router.fqdn)) \ + .filter_by(fqdn=fqdn).scalar() + if num_routers != 0: + raise HTTPException( + status_code=400, + detail=f'router "{fqdn}" already exists') + + router_interfaces.load_new_router_interfaces(fqdn) with db.session_scope() as session: router = session.query(model.Router).filter_by(fqdn=fqdn).one() @@ -55,6 +65,13 @@ async def load_router_interfaces(fqdn: str) -> InterfacesSummary: } +@router.post('/reconcile-router/{fqdn}') +async def reconcile_current_router_interfaces(fqdn: str): + raise HTTPException( + status_code=501, + detail=f'not implemented') + + @router.post('/next-lag/{fqdn}') async def reserve_next_lag(fqdn: str) -> NextLAG: """ diff --git a/test/test_interfaces_routes.py b/test/test_interfaces_routes.py index 6ffc443..e9923a1 100644 --- a/test/test_interfaces_routes.py +++ b/test/test_interfaces_routes.py @@ -18,14 +18,14 @@ def test_version_request(client): def test_update_router_interfaces( client, resources_db, mocked_router, router_name): - rv = client.post(f'/api/interfaces/import/{router_name}') + rv = client.post(f'/api/interfaces/initialize-router/{router_name}') assert rv.status_code == 200 jsonschema.validate(rv.json(), interfaces.InterfacesSummary.schema()) def test_next_lag(client, resources_db, mocked_router, router_name): - router_interfaces.load_router_interfaces(router_name) + router_interfaces.load_new_router_interfaces(router_name) rv = client.post(f'/api/interfaces/next-lag/{router_name}') assert rv.status_code == 200 @@ -34,7 +34,7 @@ def test_next_lag(client, resources_db, mocked_router, router_name): def test_next_physical(client, resources_db, mocked_router, router_name): - router_interfaces.load_router_interfaces(router_name) + router_interfaces.load_new_router_interfaces(router_name) rv = client.post(f'/api/interfaces/next-physical/{router_name}/ae123123') assert rv.status_code == 200 diff --git a/test/test_parse_router.py b/test/test_parse_router.py index e6723d5..ffe6157 100644 --- a/test/test_parse_router.py +++ b/test/test_parse_router.py @@ -83,7 +83,7 @@ def test_load_interfaces( new design for this app: no chassis info for now """ - router_interfaces.load_router_interfaces(router_name) + router_interfaces.load_new_router_interfaces(router_name) with db.session_scope() as session: router = session.query(model.Router).filter_by(fqdn=router_name).one() -- GitLab