diff --git a/Changelog.md b/Changelog.md index 5d26dcf751d10574727b67aae2a73f24f994b473..d151d640e871746771be86027194f79b037f8dcb 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,21 @@ # Changelog +## [2.47] - 2025-04-15 +- Make generation of new virtual circuit ID optional in layer 2 circuit migration workflow +- Add REST API endpoint for getting the list of available versions + +## [2.46] - 2025-04-08 +- Integrated a new l2circuit migration workflow within the database. +- Enhancements to Layer 2 Circuit Workflows +- Fixed a bug in the prefix list validation logic when an FQDN list is unexpectedly empty. +- Code Improvements and Utility Updates +- Added SDP steps to the test_terminate_router to ensure proper execution. +- Reworked SDP functions within workflow_steps for improved process clarity. +- Integrated additional SDP steps in the terminate_router process. +- Adjusted SDP steps in the promote_p_to_pe workflow. +- Updated SDP steps within the update_ibgp_mesh workflow. + + ## [2.45] - 2025-04-02 - Add email notifications to Kentik-related steps in workflows. - Improve Kentik license handling in Router termination workflow. diff --git a/gso/api/v1/__init__.py b/gso/api/v1/__init__.py index 8772f54efc9ece49a95f43b3df3cdde92269b58f..e2493364c7349d5223c01585999967de6d6d59d3 100644 --- a/gso/api/v1/__init__.py +++ b/gso/api/v1/__init__.py @@ -4,8 +4,10 @@ from fastapi import APIRouter from gso.api.v1.network import router as network_router from gso.api.v1.subscriptions import router as subscriptions_router +from gso.api.v1.version import router as version_router router = APIRouter() router.include_router(subscriptions_router) router.include_router(network_router) +router.include_router(version_router) diff --git a/gso/api/v1/version.py b/gso/api/v1/version.py new file mode 100644 index 0000000000000000000000000000000000000000..fe9f8c7f14f46a983e83b6a962c9dbfc09e14d2c --- /dev/null +++ b/gso/api/v1/version.py @@ -0,0 +1,19 @@ +"""API for getting the version of the orchestrator.""" + +from fastapi import APIRouter +from orchestrator.graphql.resolvers.version import VERSIONS +from starlette import status + +router = APIRouter(prefix="/version", tags=["Version"]) + + +@router.get("/", status_code=status.HTTP_200_OK) +def version() -> dict[str, str]: + """Get the version of deployed services.""" + version_dict = {} + for item in VERSIONS: + if ":" in item: + key, value = item.split(":", 1) + version_dict[key.strip()] = value.strip() + + return version_dict diff --git a/gso/workflows/l2_circuit/migrate_layer_2_circuit.py b/gso/workflows/l2_circuit/migrate_layer_2_circuit.py index 70a91e5aaaf24c274ca510bb9121e3b2ac37a882..661e27d32a00589c87d33ec0c199d4fcb154c0f3 100644 --- a/gso/workflows/l2_circuit/migrate_layer_2_circuit.py +++ b/gso/workflows/l2_circuit/migrate_layer_2_circuit.py @@ -87,6 +87,7 @@ def input_form_generator(subscription_id: UUIDstr) -> FormGenerator: model_config = ConfigDict(title="Migrating Layer 2 Circuit") new_edge_port: active_edge_port_selector(partner_id=replace_side_partner.partner_id) # type: ignore[valid-type] + generate_new_vc_id: bool = False user_input = yield SelectNewEdgePortForm @@ -98,11 +99,17 @@ def input_form_generator(subscription_id: UUIDstr) -> FormGenerator: "subscription_id": subscription_id, "old_edge_port": initial_user_input.replace_side, "new_edge_port": user_input.new_edge_port, + "generate_new_vc_id": user_input.generate_new_vc_id, } @step("Update subscription model") -def update_subscription_model(subscription: Layer2Circuit, old_edge_port: UUIDstr, new_edge_port: UUIDstr) -> State: +def update_subscription_model( + subscription: Layer2Circuit, + old_edge_port: UUIDstr, + new_edge_port: UUIDstr, + generate_new_vc_id: bool, # noqa: FBT001 +) -> State: """Replace the old Edge Port with the newly selected one in the subscription model.""" replace_index = ( 0 @@ -114,12 +121,12 @@ def update_subscription_model(subscription: Layer2Circuit, old_edge_port: UUIDst new_edge_port ).edge_port - vc_id = generate_unique_vc_id(l2c_type=subscription.layer_2_circuit.layer_2_circuit_type) - if not vc_id: - msg = "Failed to generate unique Virtual Circuit ID." - raise ProcessFailureError(msg) - - subscription.layer_2_circuit.virtual_circuit_id = vc_id + if generate_new_vc_id: + vc_id = generate_unique_vc_id(l2c_type=subscription.layer_2_circuit.layer_2_circuit_type) + if not vc_id: + msg = "Failed to generate unique Virtual Circuit ID." + raise ProcessFailureError(msg) + subscription.layer_2_circuit.virtual_circuit_id = vc_id return {"subscription": subscription} diff --git a/setup.py b/setup.py index fb8eab0f1e24782746e85d98d123c871fb79fc2f..f6a1d79b7d6a3f5dc76ec0522547dd1dfe148002 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages, setup setup( name="geant-service-orchestrator", - version="2.46", + version="2.47", author="GÉANT Orchestration and Automation Team", author_email="goat@geant.org", description="GÉANT Service Orchestrator", diff --git a/test/__init__.py b/test/__init__.py index d9001c9314bacef63647c108731562e2f4194f99..433e89b0fcb66b0730237cc78cded4048140751c 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,4 +1,3 @@ -import os from uuid import uuid4 LSO_RESULT_SUCCESS = { @@ -20,5 +19,3 @@ LSO_RESULT_FAILURE = { } USER_CONFIRM_EMPTY_FORM = [{}] - -os.environ["TESTING"] = "true" diff --git a/test/api/test_version.py b/test/api/test_version.py new file mode 100644 index 0000000000000000000000000000000000000000..ffcf7acb118fccdf06e4a06430496d435a0ae494 --- /dev/null +++ b/test/api/test_version.py @@ -0,0 +1,21 @@ +from importlib import metadata + + +def test_version_endpoint(test_client, monkeypatch): + """ + Test that the /version endpoint returns the correct versions: + """ + response = test_client.get("api/v1/version") + + assert response.status_code == 200 + + data = response.json() + + assert data == { + "GAP Ansible collection": "Unknown", + "GÉANT Service Orchestrator": metadata.version("geant-service-orchestrator"), + "GÉANT Service Orchestrator GUI": "Unknown", + "LSO": "Unknown", + "Moodi Ansible collection": "Unknown", + "orchestrator-core": "3.1.1", + } diff --git a/test/conftest.py b/test/conftest.py index 2c015bd1b54c0467322e716daa6fb3dbf53c8c5e..926cd183b0b50593f65b390d8a357eb73e337fd6 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -54,13 +54,16 @@ class UseJuniperSide(strEnum): def pytest_configure(config): """Set an environment variable before loading any test modules.""" - config_filename = "gso/oss-params-example.json" - os.environ["OSS_PARAMS_FILENAME"] = config_filename + # Set environment variables for the test session + os.environ["OSS_PARAMS_FILENAME"] = "gso/oss-params-example.json" + os.environ["TESTING"] = "true" + # Register finalizers to clean up after tests are done + def cleanup() -> None: + del os.environ["OSS_PARAMS_FILENAME"] + del os.environ["TESTING"] -def pytest_unconfigure(config): - """Clean up the environment variable after all tests.""" - os.environ.pop("OSS_PARAMS_FILENAME", None) + pytest.session_cleanup = cleanup class FakerProvider(BaseProvider): diff --git a/test/workflows/l2_circuit/test_migrate_layer_2_circuit.py b/test/workflows/l2_circuit/test_migrate_layer_2_circuit.py index 9c016cd28a5abbbd1a3d9cfd8572eeb78c5fedff..a0f05946cde87647941cb5421af0399a30ce0e47 100644 --- a/test/workflows/l2_circuit/test_migrate_layer_2_circuit.py +++ b/test/workflows/l2_circuit/test_migrate_layer_2_circuit.py @@ -12,9 +12,11 @@ from test.workflows import assert_complete, assert_lso_interaction_success, extr @pytest.mark.parametrize("layer_2_circuit_service_type", LAYER_2_CIRCUIT_SERVICE_TYPES) @pytest.mark.parametrize("run_old_side_ansible", [False, True]) @pytest.mark.parametrize("run_new_side_ansible", [False, True]) +@pytest.mark.parametrize("generate_new_vc_id", [False, True]) @patch("gso.services.lso_client._send_request") def test_migrate_layer_2_circuit( mock_lso_interaction, + generate_new_vc_id, run_new_side_ansible, run_old_side_ansible, layer_2_circuit_service_type, @@ -31,6 +33,7 @@ def test_migrate_layer_2_circuit( ) new_edge_port = edge_port_subscription_factory(node=side_b_router.router) + old_vc_id = subscription.layer_2_circuit.virtual_circuit_id initial_layer_2_circuit_data = [ {"subscription_id": subscription.subscription_id}, { @@ -40,7 +43,10 @@ def test_migrate_layer_2_circuit( "run_old_side_ansible": run_old_side_ansible, "run_new_side_ansible": run_new_side_ansible, }, - {"new_edge_port": new_edge_port.subscription_id}, + { + "new_edge_port": new_edge_port.subscription_id, + "generate_new_vc_id": generate_new_vc_id, + }, ] result, process_stat, step_log = run_workflow("migrate_layer_2_circuit", initial_layer_2_circuit_data) @@ -70,3 +76,4 @@ def test_migrate_layer_2_circuit( assert replaced_edge_port.edge_port_ae_members[1].model_dump( exclude="owner_subscription_id" ) == new_edge_port.edge_port.edge_port_ae_members[1].model_dump(exclude="owner_subscription_id") + assert (old_vc_id == subscription.layer_2_circuit.virtual_circuit_id) is not generate_new_vc_id