Skip to content
Snippets Groups Projects
Commit 5e80096d authored by Erik Reid's avatar Erik Reid
Browse files

Finished feature interface-states.

parents 6548545b 5b5d2aaa
Branches
No related tags found
No related merge requests found
...@@ -5,10 +5,11 @@ Resources Database ...@@ -5,10 +5,11 @@ Resources Database
This database holds information about router port resources This database holds information about router port resources
""" """
import enum
import logging import logging
from typing import Any from typing import Any
from sqlalchemy import Column, ForeignKey, Integer, String import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
...@@ -18,10 +19,16 @@ logger = logging.getLogger(__name__) ...@@ -18,10 +19,16 @@ logger = logging.getLogger(__name__)
base_schema: Any = declarative_base() base_schema: Any = declarative_base()
class AvalabilityStates(enum.Enum):
AVAILABLE = enum.auto()
USED = enum.auto()
RESERVED = enum.auto()
class Router(base_schema): class Router(base_schema):
__tablename__ = 'routers' __tablename__ = 'routers'
id = Column('id', Integer, primary_key=True) id = sa.Column('id', sa.Integer, primary_key=True)
fqdn = Column('fqdn', String, nullable=False) fqdn = sa.Column('fqdn', sa.String, nullable=False)
physical = relationship( physical = relationship(
"PhysicalInterface", "PhysicalInterface",
...@@ -36,13 +43,18 @@ class Router(base_schema): ...@@ -36,13 +43,18 @@ class Router(base_schema):
class LAG(base_schema): class LAG(base_schema):
__tablename__ = 'lags' __tablename__ = 'lags'
id = Column('id', Integer, primary_key=True) id = sa.Column('id', sa.Integer, primary_key=True)
name = Column('name', String, nullable=False) 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', 'router_id',
Integer, sa.Integer,
ForeignKey('routers.id'), sa.ForeignKey('routers.id'),
nullable=False) nullable=False)
router = relationship('Router', back_populates='lags') router = relationship('Router', back_populates='lags')
...@@ -54,20 +66,25 @@ class LAG(base_schema): ...@@ -54,20 +66,25 @@ class LAG(base_schema):
class PhysicalInterface(base_schema): class PhysicalInterface(base_schema):
__tablename__ = 'physical_interfaces' __tablename__ = 'physical_interfaces'
id = Column('id', Integer, primary_key=True) id = sa.Column('id', sa.Integer, primary_key=True)
name = Column('name', String, nullable=False) 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', 'router_id',
Integer, sa.Integer,
ForeignKey('routers.id'), sa.ForeignKey('routers.id'),
nullable=False) nullable=False)
router = relationship('Router', back_populates='physical') router = relationship('Router', back_populates='physical')
lag_id = Column( lag_id = sa.Column(
'lag_id', 'lag_id',
Integer, sa.Integer,
ForeignKey('lags.id'), sa.ForeignKey('lags.id'),
nullable=True) nullable=True)
lag = relationship('LAG', back_populates='physical') lag = relationship('LAG', back_populates='physical')
...@@ -79,11 +96,11 @@ class PhysicalInterface(base_schema): ...@@ -79,11 +96,11 @@ class PhysicalInterface(base_schema):
class LogicalInterface(base_schema): class LogicalInterface(base_schema):
__tablename__ = 'logical_interfaces' __tablename__ = 'logical_interfaces'
id = Column('id', Integer, primary_key=True) id = sa.Column('id', sa.Integer, primary_key=True)
name = Column('name', String, nullable=False) name = sa.Column('name', sa.String, nullable=False)
physical_id = Column( physical_id = sa.Column(
'physical_id', 'physical_id',
Integer, sa.Integer,
ForeignKey('physical_interfaces.id'), sa.ForeignKey('physical_interfaces.id'),
nullable=False) nullable=False)
physical = relationship('PhysicalInterface', back_populates='logical') physical = relationship('PhysicalInterface', back_populates='logical')
...@@ -6,12 +6,10 @@ from resource_management.db import model ...@@ -6,12 +6,10 @@ from resource_management.db import model
logger = logging.getLogger(__name__) 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 load all interface info from the router and
update the db with the current information update the db with the current information
TODO: merge, not just create new
""" """
params = config.load() params = config.load()
...@@ -25,11 +23,19 @@ def load_router_interfaces(fqdn: str): ...@@ -25,11 +23,19 @@ def load_router_interfaces(fqdn: str):
physical = juniper.load_installed_ethernet_ports(dev) physical = juniper.load_installed_ethernet_ports(dev)
logical = juniper.load_logical_interfaces(dev) logical = juniper.load_logical_interfaces(dev)
def _find_lag_name(interface_name): _inverted_aggregate_map = {}
for name, interfaces in aggregates.items(): for lag_name, physical_interfaces in aggregates.items():
if interface_name in interfaces: for pn in physical_interfaces:
return name _inverted_aggregate_map[pn] = lag_name
return None
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: with db.session_scope() as session:
...@@ -38,13 +44,15 @@ def load_router_interfaces(fqdn: str): ...@@ -38,13 +44,15 @@ def load_router_interfaces(fqdn: str):
# add all the lag records # add all the lag records
# keep a map for referencing below when adding physical interfaces # keep a map for referencing below when adding physical interfaces
agg_records = {} lag_records = {}
for a in aggregates.keys(): for a in aggregates.keys():
record = model.LAG( record = model.LAG(
name=a, name=a,
# if present, then used
availability=model.AvalabilityStates.USED.name,
router=router_record) router=router_record)
session.add(record) session.add(record)
agg_records[a] = record lag_records[a] = record
# add all the physical interface records # add all the physical interface records
# keep a map for referencing below when adding logical interfaces # keep a map for referencing below when adding logical interfaces
...@@ -52,10 +60,11 @@ def load_router_interfaces(fqdn: str): ...@@ -52,10 +60,11 @@ def load_router_interfaces(fqdn: str):
for physical_name, _ in physical.items(): for physical_name, _ in physical.items():
record = model.PhysicalInterface( record = model.PhysicalInterface(
name=physical_name, name=physical_name,
availability=_interface_availability(physical_name),
router=router_record) router=router_record)
lag_name = _find_lag_name(physical_name) lag_name = _inverted_aggregate_map.get(physical_name, None)
if lag_name: if lag_name:
record.lag = agg_records[lag_name] record.lag = lag_records[lag_name]
physical_records[physical_name] = record physical_records[physical_name] = record
session.add(record) session.add(record)
......
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException
import pydantic import pydantic
import sqlalchemy as sa
from resource_management import db from resource_management import db
from resource_management import config from resource_management import config
from resource_management.db import model from resource_management.db import model
...@@ -8,6 +10,8 @@ from resource_management import router_interfaces ...@@ -8,6 +10,8 @@ from resource_management import router_interfaces
router = APIRouter() router = APIRouter()
FIRST_LAG_INDEX = 0
class InterfaceCounts(pydantic.BaseModel): class InterfaceCounts(pydantic.BaseModel):
total: int total: int
...@@ -31,13 +35,21 @@ class NextPhysicalInterface(pydantic.BaseModel): ...@@ -31,13 +35,21 @@ class NextPhysicalInterface(pydantic.BaseModel):
name: str name: str
@router.post('/import/{fqdn}') @router.post('/initialize-router/{fqdn}')
async def load_router_interfaces(fqdn: str) -> InterfacesSummary: async def load_new_router_interfaces(fqdn: str) -> InterfacesSummary:
params = config.load() params = config.load()
db.init_db_model(params['db']) 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: with db.session_scope() as session:
router = session.query(model.Router).filter_by(fqdn=fqdn).one() router = session.query(model.Router).filter_by(fqdn=fqdn).one()
...@@ -55,6 +67,13 @@ async def load_router_interfaces(fqdn: str) -> InterfacesSummary: ...@@ -55,6 +67,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='not implemented')
@router.post('/next-lag/{fqdn}') @router.post('/next-lag/{fqdn}')
async def reserve_next_lag(fqdn: str) -> NextLAG: async def reserve_next_lag(fqdn: str) -> NextLAG:
""" """
...@@ -68,22 +87,29 @@ async def reserve_next_lag(fqdn: str) -> NextLAG: ...@@ -68,22 +87,29 @@ async def reserve_next_lag(fqdn: str) -> NextLAG:
db.init_db_model(params['db']) db.init_db_model(params['db'])
with db.session_scope() as session: with db.session_scope() as session:
records = session.query(model.LAG.name) \ router_record = session.query(model.Router.id) \
.filter_by(fqdn=fqdn).one()
lag_rows = session.query(model.LAG.name) \
.join(model.Router).filter_by(fqdn=fqdn).all() .join(model.Router).filter_by(fqdn=fqdn).all()
names = set(r[0] for r in records) lag_names = set(r[0] for r in lag_rows)
def _next_lag_name(): def _next_lag_name():
index = 1 index = FIRST_LAG_INDEX
while True: while True:
candidate = f'ae{index}' candidate_name = f'ae{index}'
if candidate not in names: if candidate_name not in lag_names:
return candidate new_lag_record = model.LAG(
index += 1 name=candidate_name,
router_id=router_record[0],
availability=model.AvalabilityStates.RESERVED.name)
session.add(new_lag_record)
return candidate_name
index += 1
return { return {
'fqdn': fqdn, 'fqdn': fqdn,
'name': _next_lag_name() 'name': _next_lag_name()
} }
@router.post('/next-physical/{fqdn}/{lag_name}') @router.post('/next-physical/{fqdn}/{lag_name}')
...@@ -104,8 +130,12 @@ async def reserve_physical_bundle_member( ...@@ -104,8 +130,12 @@ async def reserve_physical_bundle_member(
def _find_available_physical(): def _find_available_physical():
for ifc in router.physical: for ifc in router.physical:
if not ifc.lag: if ifc.availability \
== model.AvalabilityStates.AVAILABLE.name:
ifc.availability = model.AvalabilityStates.RESERVED.name
session.add(ifc)
return ifc.name return ifc.name
raise HTTPException( raise HTTPException(
status_code=404, status_code=404,
detail=f'no available physical ports for "{lag_name}"') detail=f'no available physical ports for "{lag_name}"')
......
...@@ -3,6 +3,10 @@ import jsonschema ...@@ -3,6 +3,10 @@ import jsonschema
from resource_management.routes.default import Version from resource_management.routes.default import Version
from resource_management.routes import interfaces from resource_management.routes import interfaces
from resource_management import router_interfaces from resource_management import router_interfaces
from resource_management import db
from resource_management.db import model
import pytest
def test_bad_method(client): def test_bad_method(client):
...@@ -18,24 +22,57 @@ def test_version_request(client): ...@@ -18,24 +22,57 @@ def test_version_request(client):
def test_update_router_interfaces( def test_update_router_interfaces(
client, resources_db, mocked_router, router_name): 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 assert rv.status_code == 200
jsonschema.validate(rv.json(), interfaces.InterfacesSummary.schema()) jsonschema.validate(rv.json(), interfaces.InterfacesSummary.schema())
def test_next_lag(client, resources_db, mocked_router, router_name): 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}') rv = client.post(f'/api/interfaces/next-lag/{router_name}')
assert rv.status_code == 200 assert rv.status_code == 200
jsonschema.validate(rv.json(), interfaces.NextLAG.schema()) response = rv.json()
jsonschema.validate(response, interfaces.NextLAG.schema())
with db.session_scope() as session:
row = session.query(model.LAG). \
filter_by(name=response['name']). \
join(model.Router). \
filter_by(fqdn=router_name).one()
assert row.availability == model.AvalabilityStates.RESERVED.name
def test_next_physical(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) if '.lab.' in router_name:
pytest.skip('not all lab routers have available ports')
if router_name.startswith('rt'):
pytest.skip('not all rt* have available ports')
router_interfaces.load_new_router_interfaces(router_name)
with db.session_scope() as session:
rows = session.query(model.LAG.name). \
join(model.Router). \
filter_by(fqdn=router_name).all()
lag_name = rows[0][0]
rv = client.post(f'/api/interfaces/next-physical/{router_name}/ae123123') rv = client.post(f'/api/interfaces/next-physical/{router_name}/{lag_name}')
assert rv.status_code == 200 assert rv.status_code == 200
jsonschema.validate(rv.json(), interfaces.NextPhysicalInterface.schema())
response = rv.json()
jsonschema.validate(response, interfaces.NextPhysicalInterface.schema())
assert response['fqdn'] == router_name
assert response['lag'] == lag_name
with db.session_scope() as session:
ifc_row = session.query(model.PhysicalInterface). \
filter_by(name=response['name']). \
join(model.Router). \
filter_by(fqdn=router_name). \
join(model.LAG). \
filter_by(name=lag_name).one()
assert ifc_row.availability == model.AvalabilityStates.RESERVED.name
...@@ -83,7 +83,7 @@ def test_load_interfaces( ...@@ -83,7 +83,7 @@ def test_load_interfaces(
new design for this app: no chassis info for now 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: with db.session_scope() as session:
router = session.query(model.Router).filter_by(fqdn=router_name).one() router = session.query(model.Router).filter_by(fqdn=router_name).one()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment