diff --git a/gso/oss-params-example.json b/gso/oss-params-example.json index 9c48d63db1dd48d0fbc489b8260aae536ee3b641..cc46f4f52f82ac0d768772ee642733bb9873b4db 100644 --- a/gso/oss-params-example.json +++ b/gso/oss-params-example.json @@ -2,7 +2,9 @@ "GENERAL": { "public_hostname": "https://gap.geant.org" }, - "RESOURCE_MANAGER_API_PREFIX": "http://localhost:44444", + "RESOURCE_MANAGEMENT": { + "todo": "todo" + }, "IPAM": { "INFOBLOX": { "scheme": "https", diff --git a/gso/services/resource_manager.py b/gso/services/resource_manager.py index 2fc41f625f7595ac02dac7de4af6cb5e290f9ac2..3dadd19c08365d6b82ae6b91e45b483e775965a5 100644 --- a/gso/services/resource_manager.py +++ b/gso/services/resource_manager.py @@ -1,31 +1,140 @@ # mypy: ignore-errors -import requests +from enum import Enum, auto +from typing import List from gso import settings +# TODO +# - fill in the implementations +# - consider the additional api methods +# - decided what to do with various error conditions (currently assertions) -def import_new_router(router_name: str, oss_params=settings.OSSParams) -> None: - r = requests.post( - f"{oss_params.RESOURCE_MANAGER_API_PREFIX}" f"/api/interfaces/initialize-router/{router_name}", timeout=10000 - ) - r.raise_for_status() +class InterfaceAllocationState(Enum): + AVAILABLE = auto() + RESERVED = auto() + ALLOCATED = auto() -def next_lag(router_name: str, oss_params=settings.OSSParams) -> dict: - r = requests.post( - f"{oss_params.RESOURCE_MANAGER_API_PREFIX}" f"/api/interfaces/next-lag/{router_name}", timeout=10000 - ) - r.raise_for_status() - response = r.json() - return response["name"] +def _dummy_router_interfaces(): + return { + 'lags': [], + 'physical': [ + { + 'name': f'ifc-{x}', + 'state': InterfaceAllocationState.AVAILABLE + } for x in range(250)] + } -def next_physical(router_name: str, lag_name: str, oss_params=settings.OSSParams) -> dict: - # TODO: speed needed (if first interface) - r = requests.post( - f"{oss_params.RESOURCE_MANAGER_API_PREFIX}" f"/api/interfaces/next-physical/{router_name}/{lag_name}", - timeout=10000, - ) - r.raise_for_status() - response = r.json() - return response["name"] + +_DUMMY_INVENTORY = { + 'fqdn-a': _dummy_router_interfaces(), + 'fqdn-b': _dummy_router_interfaces(), + 'fqdn-c': _dummy_router_interfaces(), + 'fqdn-d': _dummy_router_interfaces() +} + + +def import_new_router( + new_router_fqdn: str, oss_params=settings.OSSParams): + # TODO: this is a dummy implementation + + # TODO: specifiy if this should be an error (and if now, what it means) + assert new_router_fqdn not in _DUMMY_INVENTORY + _DUMMY_INVENTORY[new_router_fqdn] = _dummy_router_interfaces() + + +def next_lag(router_fqdn: str, oss_params=settings.OSSParams) -> str: + # TODO: this is a dummy implementation + + assert router_fqdn in _DUMMY_INVENTORY + + lag_idx = 0 + while True: + lag_name = f'ae-{lag_idx}' + if lag_name not in _DUMMY_INVENTORY[router_fqdn]['lags']: + _DUMMY_INVENTORY[router_fqdn]['lags'].append(lag_name) + return lag_name + lag_idx += 1 + + +def available_physical_interfaces( + router_fqdn: str, oss_params=settings.OSSParams) -> List[str]: + # TODO: this is a dummy implementation + + assert router_fqdn in _DUMMY_INVENTORY + + return [ + ifc['name'] for ifc in _DUMMY_INVENTORY[router_fqdn]['physical'] + if ifc['state'] == InterfaceAllocationState.AVAILABLE + ] + + +def _find_physical(router_fqdn: str, interface_name: str) -> dict: + assert router_fqdn in _DUMMY_INVENTORY + for ifc in _DUMMY_INVENTORY[router_fqdn]['physical']: + if ifc['name'] == interface_name: + return ifc + assert False, f'interface {interface_name} not found on {router_fqdn}' + + +def reserve_physical_interface( + router_fqdn: str, + interface_name: str, + oss_params=settings.OSSParams): + # TODO: this is a dummy implementation + + ifc = _find_physical(router_fqdn, interface_name) + assert ifc['state'] == InterfaceAllocationState.AVAILABLE, \ + f'interface {router_fqdn}:{interface_name} is not available' + ifc['state'] = InterfaceAllocationState.RESERVED + + +def allocate_physical_interface( + router_fqdn: str, + interface_name: str, + oss_params=settings.OSSParams): + # TODO: this is a dummy implementation + + ifc = _find_physical(router_fqdn, interface_name) + # TODO: is there a use case for moving + # directly from AVAILABLE to ALLOCATED? + assert ifc['state'] == InterfaceAllocationState.RESERVED, \ + f'interface {router_fqdn}:{interface_name} is not reserved' + ifc['state'] = InterfaceAllocationState.RESERVED + + +def free_physical_interface( + router_fqdn: str, + interface_name: str, + oss_params=settings.OSSParams): + # TODO: this is a dummy implementation + + ifc = _find_physical(router_fqdn, interface_name) + # TODO: is this really an error that should be handled? + # ... or is it ok to ignore this? + assert ifc['state'] != InterfaceAllocationState.AVAILABLE, \ + f'interface {router_fqdn}:{interface_name} is already available' + ifc['state'] = InterfaceAllocationState.AVAILABLE + + +def free_lag( + router_fqdn: str, + lag_name: str, + oss_params=settings.OSSParams): + # TODO: is this a use case that should be handled? + # e.g. who keeps track of the bundled physical interfaces? + pass + + +def remove_router( + router_fqdn: str, oss_params=settings.OSSParams): + # TODO: is this a use case that should be handled? + pass + + +def all_lags( + router_fqdn: str, oss_params=settings.OSSParams) -> List[str]: + # TODO: is this a use case that should be handled? + assert router_fqdn in _DUMMY_INVENTORY + return _DUMMY_INVENTORY[router_fqdn]['lags'] diff --git a/gso/settings.py b/gso/settings.py index 88f5dc924a4108e8dbd6aed6c49d2f5a78c64c2a..2d527ff1eddb19ef95ba1068d2deda6b604e9c4f 100644 --- a/gso/settings.py +++ b/gso/settings.py @@ -75,12 +75,17 @@ class ProvisioningProxyParams(BaseSettings): api_version: int +class ResourceManagementParams(BaseSettings): + """TO DO: resource management parameters""" + todo: str + + class OSSParams(BaseSettings): """The set of parameters required for running GSO.""" GENERAL: GeneralParams IPAM: IPAMParams - RESOURCE_MANAGER_API_PREFIX: str + RESOURCE_MANAGEMENT: ResourceManagementParams PROVISIONING_PROXY: ProvisioningProxyParams diff --git a/test/conftest.py b/test/conftest.py index a53a4c7971d0340dcd2bc193ca4dd7b5e50a95aa..1ef473a1c3cbcadb18a98932945ca3bb6c6b4ede 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -14,7 +14,9 @@ def configuration_data() -> dict: s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) yield { "GENERAL": {"public_hostname": "https://gap.geant.org"}, - "RESOURCE_MANAGER_API_PREFIX": "http://localhost:44444", + "RESOURCE_MANAGEMENT": { + 'todo': 'todo' + }, "IPAM": { "INFOBLOX": { "scheme": "https", diff --git a/test/test_resource_manager.py b/test/test_resource_manager.py new file mode 100644 index 0000000000000000000000000000000000000000..b99d547f81084544630ffb766b886d2570fb80d3 --- /dev/null +++ b/test/test_resource_manager.py @@ -0,0 +1,56 @@ +import random +import string + +from gso.services import resource_manager + + +def _random_string( + n=None, + letters=string.ascii_letters + string.digits + string.punctuation): + if not n: + n = random.randint(1, 20) + return ''.join(random.choices(letters, k=n)) + + +def test_new_router(): + router_name = _random_string(10) + assert router_name not in resource_manager._DUMMY_INVENTORY + resource_manager.import_new_router(new_router_fqdn=router_name) + assert router_name in resource_manager._DUMMY_INVENTORY + + +def test_new_lag(): + router_name = list(resource_manager._DUMMY_INVENTORY.keys())[0] + new_lags = { + resource_manager.next_lag(router_fqdn=router_name) + for _ in range(10) + } + assert len(new_lags) == 10 + assert new_lags <= set( + resource_manager._DUMMY_INVENTORY[router_name]['lags']) + + +def test_physical_allocation_lifecycle_happy(): + router_name = list(resource_manager._DUMMY_INVENTORY.keys())[0] + + def _interfaces(): + return resource_manager.available_physical_interfaces( + router_fqdn=router_name) + + initial_available = _interfaces() + + interface_name = initial_available[0] + + resource_manager.reserve_physical_interface( + router_fqdn=router_name, interface_name=interface_name) + + current_available = _interfaces() + assert interface_name not in current_available + + resource_manager.allocate_physical_interface( + router_fqdn=router_name, interface_name=interface_name) + resource_manager.free_physical_interface( + router_fqdn=router_name, interface_name=interface_name) + + current_available = _interfaces() + assert interface_name in current_available