diff --git a/Changelog.md b/Changelog.md index ac852abafd6257877a7c85f5fb8d5d2579fff948..d8715c301edf264ed448616e6dae6e6b1f671533 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,4 +1,8 @@ # Changelog +## [2.42] - 2025-03-13 +- Send a separate notification email for failed prefix list checks. +- Upgrade `orchestrator-core` to 3.1.1. + ## [2.41] - 2025-02-28 - Check uniqueness of GS, GA and VC IDs against active subscriptions. - `migrate_l3_core_service`: Update in parameters passed to Moodi to target the destination node. diff --git a/gso/cli/imports.py b/gso/cli/imports.py index 6444aba8963fcd8c2364eccc0afb3ee85b40d59b..85db5b756101ab06f7aa86cd54d696586f75641d 100644 --- a/gso/cli/imports.py +++ b/gso/cli/imports.py @@ -12,8 +12,9 @@ import typer import yaml from orchestrator.db import db from orchestrator.services.processes import start_process -from orchestrator.types import SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from pydantic import BaseModel, NonNegativeInt, ValidationError, field_validator, model_validator +from pydantic_forms.types import UUIDstr from sqlalchemy.exc import SQLAlchemyError from gso.db.models import PartnerTable diff --git a/gso/graphql_api/types.py b/gso/graphql_api/types.py index c6fb920048f5080797fb1454a54bd01ee9f4288a..0a1a7ac89c6f26431d591074bcb5318d958b1ee6 100644 --- a/gso/graphql_api/types.py +++ b/gso/graphql_api/types.py @@ -1,11 +1,11 @@ """Map some Orchestrator types to scalars.""" from ipaddress import IPv4Network, IPv6Network -from typing import Any, NewType +from typing import NewType import strawberry from orchestrator.graphql.types import serialize_to_string -from strawberry.custom_scalar import ScalarDefinition, ScalarWrapper +from strawberry.types.scalar import ScalarDefinition, ScalarWrapper IPv4NetworkType = strawberry.scalar( NewType("IPv4NetworkType", str), @@ -21,7 +21,7 @@ IPv6NetworkType = strawberry.scalar( parse_value=lambda v: v, ) -GSO_SCALAR_OVERRIDES: dict[object, Any | ScalarWrapper | ScalarDefinition] = { - IPv4Network: IPv4NetworkType, - IPv6Network: IPv6NetworkType, +GSO_SCALAR_OVERRIDES: dict[object, type | ScalarWrapper | ScalarDefinition] = { + IPv4Network: IPv4NetworkType, # type: ignore[dict-item] + IPv6Network: IPv6NetworkType, # type: ignore[dict-item] } diff --git a/gso/migrations/versions/2025-02-28_931c0d9d81d6_validate_prefix_list.py b/gso/migrations/versions/2025-02-28_931c0d9d81d6_validate_prefix_list.py new file mode 100644 index 0000000000000000000000000000000000000000..e931173b9a2f90b485dbf9d23da1087d3912cf4a --- /dev/null +++ b/gso/migrations/versions/2025-02-28_931c0d9d81d6_validate_prefix_list.py @@ -0,0 +1,45 @@ +"""validate-prefix-list. + +Revision ID: 931c0d9d81d6 +Revises: efebcde91f2f +Create Date: 2025-02-28 14:13:57.181314 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = '931c0d9d81d6' +down_revision = 'efebcde91f2f' +branch_labels = None +depends_on = None + + +from orchestrator.migrations.helpers import create_workflow, delete_workflow + +new_workflows = [ + { + "name": "validate_prefix_list", + "target": "SYSTEM", + "description": "Validate Prefix-List", + "product_type": "L3CoreService" + }, + { + "name": "deploy_prefix_list", + "target": "SYSTEM", + "description": "Deploy Prefix-List", + "product_type": "L3CoreService" + } +] + + +def upgrade() -> None: + conn = op.get_bind() + for workflow in new_workflows: + create_workflow(conn, workflow) + + +def downgrade() -> None: + conn = op.get_bind() + for workflow in new_workflows: + delete_workflow(conn, workflow["name"]) diff --git a/gso/migrations/versions/2025-03-11_b96b0ecf6906_add_orchestrator_3_1_1_migrations.py b/gso/migrations/versions/2025-03-11_b96b0ecf6906_add_orchestrator_3_1_1_migrations.py new file mode 100644 index 0000000000000000000000000000000000000000..df29a0319d2c2507b7c6f628147c2ab4540f9bd1 --- /dev/null +++ b/gso/migrations/versions/2025-03-11_b96b0ecf6906_add_orchestrator_3_1_1_migrations.py @@ -0,0 +1,20 @@ +"""Add upstream migrations from orchestrator-core 3.1.1. + +Revision ID: b96b0ecf6906 +Revises: 844aa61c09ce +Create Date: 2024-10-10 10:00:08.539591 + +""" +# revision identifiers, used by Alembic. +revision = 'b96b0ecf6906' +down_revision = '931c0d9d81d6' +branch_labels = None +depends_on = 'bac6be6f2b4f' + + +def upgrade() -> None: + pass + + +def downgrade() -> None: + pass diff --git a/gso/products/product_blocks/edge_port.py b/gso/products/product_blocks/edge_port.py index 1603f5a39776fbc0ce10ed6ed20d4c5a3a49514f..7fa1b5c706d2e2ecc5a106f6f42e158112c7c06d 100644 --- a/gso/products/product_blocks/edge_port.py +++ b/gso/products/product_blocks/edge_port.py @@ -5,7 +5,8 @@ different technological domain, still managed by GÉANT. In other words, an Edge """ from orchestrator.domain.base import ProductBlockModel -from orchestrator.types import SubscriptionLifecycle, strEnum +from orchestrator.types import SubscriptionLifecycle +from pydantic_forms.types import strEnum from gso.products.product_blocks.router import RouterBlock, RouterBlockInactive, RouterBlockProvisioning from gso.utils.types.interfaces import LAGMemberList, PhysicalPortCapacity diff --git a/gso/products/product_blocks/iptrunk.py b/gso/products/product_blocks/iptrunk.py index 1f67f312a84e16346277ed0bdb84f4e4674628ce..698800d9c7583b0ce9cb55cb5cc747e2fe76ef29 100644 --- a/gso/products/product_blocks/iptrunk.py +++ b/gso/products/product_blocks/iptrunk.py @@ -5,8 +5,9 @@ from typing import Annotated from annotated_types import Len from orchestrator.domain.base import ProductBlockModel, T -from orchestrator.types import SubscriptionLifecycle, strEnum +from orchestrator.types import SubscriptionLifecycle from pydantic import AfterValidator +from pydantic_forms.types import strEnum from pydantic_forms.validators import validate_unique_list from typing_extensions import Doc diff --git a/gso/products/product_blocks/router.py b/gso/products/product_blocks/router.py index de428138a9e305e67cbb0bfbfc705866b5d17135..b45082225bb77700ee079c9dc05cd04557cb2ce6 100644 --- a/gso/products/product_blocks/router.py +++ b/gso/products/product_blocks/router.py @@ -1,7 +1,8 @@ """Product block for `Router` products.""" from orchestrator.domain.base import ProductBlockModel -from orchestrator.types import SubscriptionLifecycle, strEnum +from orchestrator.types import SubscriptionLifecycle +from pydantic_forms.types import strEnum from gso.products.product_blocks.site import ( SiteBlock, diff --git a/gso/products/product_blocks/site.py b/gso/products/product_blocks/site.py index 22cc4940094ca50d494bc90507466a57b125e299..52db8df31321458489c254075cb2e509a9ed9b4e 100644 --- a/gso/products/product_blocks/site.py +++ b/gso/products/product_blocks/site.py @@ -1,7 +1,8 @@ """The product block that describes a site subscription.""" from orchestrator.domain.base import ProductBlockModel -from orchestrator.types import SubscriptionLifecycle, strEnum +from orchestrator.types import SubscriptionLifecycle +from pydantic_forms.types import strEnum from gso.utils.types.coordinates import LatitudeCoordinate, LongitudeCoordinate from gso.utils.types.ip_address import IPAddress diff --git a/gso/services/lso_client.py b/gso/services/lso_client.py index e6268e5c4859d77af7ef14ea91bc86224dc7c27e..2318cffd7396e51c620fa55087faea21e7043187 100644 --- a/gso/services/lso_client.py +++ b/gso/services/lso_client.py @@ -11,11 +11,10 @@ import requests from orchestrator import step from orchestrator.config.assignee import Assignee from orchestrator.forms import SubmitFormPage -from orchestrator.types import State from orchestrator.utils.errors import ProcessFailureError from orchestrator.workflow import Step, StepList, begin, callback_step, conditional, inputstep from pydantic import ConfigDict -from pydantic_forms.types import FormGenerator +from pydantic_forms.types import FormGenerator, State from pydantic_forms.validators import Label, LongText, ReadOnlyField from unidecode import unidecode diff --git a/gso/services/netbox_client.py b/gso/services/netbox_client.py index 2928263c920da52e0993fbb43ddc4dbfa2d20283..7ff8e29c2326dcfbbf60143b0800e2c952282c0a 100644 --- a/gso/services/netbox_client.py +++ b/gso/services/netbox_client.py @@ -5,7 +5,7 @@ from uuid import UUID import pydantic import pynetbox -from orchestrator.types import UUIDstr +from pydantic_forms.types import UUIDstr from pynetbox.models.dcim import Devices, DeviceTypes, Interfaces from gso.products.product_types.router import Router diff --git a/gso/services/processes.py b/gso/services/processes.py index c33f17a285b57513ab7dd7bfe5cf1925a80bb87e..5d6f5105ddc27271e6e87c55ca58362ff57368e4 100644 --- a/gso/services/processes.py +++ b/gso/services/processes.py @@ -5,9 +5,15 @@ or inconsistent when not careful. These methods are related to operations regard """ from orchestrator.db import ProcessTable, WorkflowTable, db -from orchestrator.types import UUIDstr from orchestrator.workflow import ProcessStatus +from pydantic_forms.types import UUIDstr from sqlalchemy import ScalarResult, or_, select +from sqlalchemy.orm import Query + + +def get_processes_by_workflow_name(workflow_name: str) -> Query: + """Get all processes for a given workflow name.""" + return ProcessTable.query.join(WorkflowTable).filter(WorkflowTable.name == workflow_name) def count_incomplete_validate_products() -> int: @@ -16,19 +22,29 @@ def count_incomplete_validate_products() -> int: Returns: The count of incomplete 'validate_geant_products' processes. """ - return ProcessTable.query.filter( - ProcessTable.workflow_name == "validate_geant_products", - ProcessTable.last_status != ProcessStatus.COMPLETED.value, - ).count() + return ( + get_processes_by_workflow_name("validate_geant_products") + .filter(ProcessTable.last_status != ProcessStatus.COMPLETED) + .count() + ) def get_failed_tasks() -> list[ProcessTable]: """Get all tasks that have failed.""" return ProcessTable.query.filter( - ProcessTable.is_task.is_(True), ProcessTable.last_status == ProcessStatus.FAILED.value + ProcessTable.is_task.is_(True), ProcessTable.last_status == ProcessStatus.FAILED ).all() +def get_failed_tasks_by_workflow_name(workflow_name: str) -> list[ProcessTable]: + """Get all tasks that have failed for a specific workflow name.""" + return ( + get_processes_by_workflow_name(workflow_name) + .filter(ProcessTable.is_task.is_(True), ProcessTable.last_status == ProcessStatus.FAILED) + .all() + ) + + def get_all_cleanup_tasks() -> list[WorkflowTable]: """Get a list of all cleanup tasks that run on a schedule.""" return WorkflowTable.query.filter( diff --git a/gso/services/sharepoint.py b/gso/services/sharepoint.py index e846f78c6a92228d75a4a5e2325753ce6f8268e1..567d946c599b976b312b06421d0e0d1292e8dc0a 100644 --- a/gso/services/sharepoint.py +++ b/gso/services/sharepoint.py @@ -3,7 +3,7 @@ import asyncio from azure.identity.aio import CertificateCredential -from msgraph import GraphServiceClient +from msgraph import GraphServiceClient # type: ignore[attr-defined] from msgraph.generated.models.field_value_set import FieldValueSet from msgraph.generated.models.list_item import ListItem from msgraph.generated.models.list_item_collection_response import ListItemCollectionResponse @@ -77,6 +77,6 @@ class SharePointClient: ) # Strip the last part of the URL, since we want the link to the list, not the list item. - return new_item.web_url.rsplit("/", 1)[0] + return new_item.web_url.rsplit("/", 1)[0] # type: ignore[union-attr] return asyncio.run(_new_list_item()) diff --git a/gso/services/subscriptions.py b/gso/services/subscriptions.py index 543fae4a1f24def0afbce8b2a5f5f7607dc56ca7..9f3623b723969e5d7caef5488db951b9e4af0c4a 100644 --- a/gso/services/subscriptions.py +++ b/gso/services/subscriptions.py @@ -18,7 +18,8 @@ from orchestrator.db import ( ) from orchestrator.domain import SubscriptionModel from orchestrator.services.subscriptions import query_in_use_by_subscriptions -from orchestrator.types import SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle +from pydantic_forms.types import UUIDstr from sqlalchemy import and_, text from sqlalchemy.exc import SQLAlchemyError diff --git a/gso/settings.py b/gso/settings.py index e7326dbbcb27b6c0fcef7a5dc15c9b5bbc482fb3..fbd4286411019fe56be9dedfcb615a86dcbbfc81 100644 --- a/gso/settings.py +++ b/gso/settings.py @@ -10,9 +10,8 @@ import logging import os from pathlib import Path -from orchestrator.types import UUIDstr from pydantic import EmailStr -from pydantic_forms.types import strEnum +from pydantic_forms.types import UUIDstr, strEnum from pydantic_settings import BaseSettings from gso.utils.types.ip_address import IPv4Netmask, IPv6Netmask, PortNumber diff --git a/gso/translations/en-GB.json b/gso/translations/en-GB.json index c5d17a43401cfe1b9e454990189a885b0c7d0db1..d59dc7aa58cd0818de9466119d4ca4aeaecb8d34 100644 --- a/gso/translations/en-GB.json +++ b/gso/translations/en-GB.json @@ -84,6 +84,7 @@ "create_imported_l3_core_service": "NOT FOR HUMANS -- Import existing L3 Core Service", "create_imported_switch": "NOT FOR HUMANS -- Import existing Switch", "create_imported_lan_switch_interconnect": "NOT FOR HUMANS -- Import existing LAN Switch Interconnect", + "deploy_prefix_list": "Deploy Prefix-List", "import_site": "NOT FOR HUMANS -- Finalize import into a Site product", "import_router": "NOT FOR HUMANS -- Finalize import into a Router product", "import_iptrunk": "NOT FOR HUMANS -- Finalize import into an IP trunk product", @@ -100,6 +101,7 @@ "validate_edge_port": "Validate Edge Port", "validate_lan_switch_interconnect": "Validate LAN Switch Interconnect", "validate_l3_core_service": "Validate L3 Core Service", + "validate_prefix_list": "Validate Prefix-List", "task_validate_geant_products": "Validation task for GEANT products", "task_send_email_notifications": "Send email notifications for failed tasks", "task_create_partners": "Create partner task", diff --git a/gso/utils/types/netbox_router.py b/gso/utils/types/netbox_router.py index c184c09bebba0583afd5628b55587d9d05f4121d..a0f50a6987b8a57973d206423a634b50dd1ac0ba 100644 --- a/gso/utils/types/netbox_router.py +++ b/gso/utils/types/netbox_router.py @@ -2,8 +2,8 @@ from typing import Annotated, TypeVar -from orchestrator.types import UUIDstr from pydantic import AfterValidator +from pydantic_forms.types import UUIDstr from gso.products.product_types.router import Router from gso.services.netbox_client import NetboxClient diff --git a/gso/utils/workflow_steps.py b/gso/utils/workflow_steps.py index 754af0819ae239fa1064c7320dace6572fd62f32..6c90f051cb19b312a1706db411a6084b5be1b5fe 100644 --- a/gso/utils/workflow_steps.py +++ b/gso/utils/workflow_steps.py @@ -6,12 +6,11 @@ from typing import Any from orchestrator import inputstep, step from orchestrator.config.assignee import Assignee from orchestrator.forms import SubmitFormPage -from orchestrator.types import State, UUIDstr from orchestrator.utils.errors import ProcessFailureError from orchestrator.utils.json import json_dumps from orchestrator.workflow import StepList, conditional from pydantic import ConfigDict -from pydantic_forms.types import FormGenerator +from pydantic_forms.types import FormGenerator, State, UUIDstr from pydantic_forms.validators import Label from gso.products.product_blocks.router import RouterRole diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py index 74f67c01f108350adb5ac7c8ce3667d762c21f66..0e38f2262c75ba89160208432065554788f17c4d 100644 --- a/gso/workflows/__init__.py +++ b/gso/workflows/__init__.py @@ -126,6 +126,8 @@ LazyWorkflowInstance("gso.workflows.l3_core_service.import_l3_core_service", "im LazyWorkflowInstance("gso.workflows.l3_core_service.migrate_l3_core_service", "migrate_l3_core_service") LazyWorkflowInstance("gso.workflows.l3_core_service.validate_l3_core_service", "validate_l3_core_service") LazyWorkflowInstance("gso.workflows.l3_core_service.terminate_l3_core_service", "terminate_l3_core_service") +LazyWorkflowInstance("gso.workflows.l3_core_service.validate_prefix_list", "validate_prefix_list") +LazyWorkflowInstance("gso.workflows.l3_core_service.deploy_prefix_list", "deploy_prefix_list") # Layer 2 Circuit workflows LazyWorkflowInstance("gso.workflows.l2_circuit.create_layer_2_circuit", "create_layer_2_circuit") diff --git a/gso/workflows/edge_port/create_edge_port.py b/gso/workflows/edge_port/create_edge_port.py index 7526ad69dd5de092769108370b328d01f56fd52c..6686e2669472ae68ee7965e726c515ec8d013307 100644 --- a/gso/workflows/edge_port/create_edge_port.py +++ b/gso/workflows/edge_port/create_edge_port.py @@ -7,12 +7,13 @@ from annotated_types import Len from orchestrator import step, workflow from orchestrator.forms import FormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.utils.errors import ProcessFailureError from orchestrator.workflow import StepList, begin, done from orchestrator.workflows.steps import resync, set_status, store_process_subscription from orchestrator.workflows.utils import wrap_create_initial_input_form from pydantic import AfterValidator, ConfigDict, model_validator +from pydantic_forms.types import FormGenerator, State, UUIDstr from pydantic_forms.validators import validate_unique_list from pynetbox.models.dcim import Interfaces diff --git a/gso/workflows/edge_port/create_imported_edge_port.py b/gso/workflows/edge_port/create_imported_edge_port.py index ef5d2d033203e082c67a67f88eb511043d4e2a51..6a0067275fcd032ea936ca57580dfe2bdc482ffe 100644 --- a/gso/workflows/edge_port/create_imported_edge_port.py +++ b/gso/workflows/edge_port/create_imported_edge_port.py @@ -6,11 +6,11 @@ from uuid import uuid4 from orchestrator import workflow from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done, step from orchestrator.workflows.steps import resync, set_status, store_process_subscription from pydantic import AfterValidator, ConfigDict -from pydantic_forms.types import UUIDstr +from pydantic_forms.types import FormGenerator, State, UUIDstr from pydantic_forms.validators import validate_unique_list from gso.products import ProductName diff --git a/gso/workflows/edge_port/import_edge_port.py b/gso/workflows/edge_port/import_edge_port.py index 3193489a6606eaff8f553e4358f5621352bca613..0d5bd41bcfd66df61029ea0ba44acab195e3860a 100644 --- a/gso/workflows/edge_port/import_edge_port.py +++ b/gso/workflows/edge_port/import_edge_port.py @@ -1,10 +1,10 @@ """A modification workflow for migrating an ImportedEdgePort to an EdgePort subscription.""" from orchestrator.targets import Target -from orchestrator.types import State, UUIDstr from orchestrator.workflow import StepList, done, init, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import State, UUIDstr from gso.products import ProductName from gso.products.product_types.edge_port import EdgePort, ImportedEdgePort diff --git a/gso/workflows/edge_port/migrate_edge_port.py b/gso/workflows/edge_port/migrate_edge_port.py index 4602d72dd4dff19598d9932034b25f535d0f7271..afd2d6d0f46e9dd6a81f9fe1e290af0ff3b217d0 100644 --- a/gso/workflows/edge_port/migrate_edge_port.py +++ b/gso/workflows/edge_port/migrate_edge_port.py @@ -10,13 +10,13 @@ from orchestrator import step, workflow from orchestrator.config.assignee import Assignee from orchestrator.forms import FormPage, SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, UUIDstr from orchestrator.utils.errors import ProcessFailureError from orchestrator.utils.json import json_dumps from orchestrator.workflow import StepList, begin, done, inputstep from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form from pydantic import AfterValidator, ConfigDict, Field +from pydantic_forms.types import FormGenerator, State, UUIDstr from pydantic_forms.validators import Divider, Label, ReadOnlyField, validate_unique_list from pynetbox.models.dcim import Interfaces diff --git a/gso/workflows/edge_port/terminate_edge_port.py b/gso/workflows/edge_port/terminate_edge_port.py index 608294b113f842eb8716571c13e1b3d08a37e0ff..8776347b556adffa5ec28f91dd4af240071eda70 100644 --- a/gso/workflows/edge_port/terminate_edge_port.py +++ b/gso/workflows/edge_port/terminate_edge_port.py @@ -2,7 +2,7 @@ from typing import Any -from orchestrator import workflow +from orchestrator import conditional, workflow from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target from orchestrator.types import SubscriptionLifecycle @@ -14,6 +14,7 @@ from pydantic_forms.types import FormGenerator, UUIDstr from gso.products.product_types.edge_port import EdgePort from gso.services.lso_client import LSOState, lso_interaction from gso.services.netbox_client import NetboxClient +from gso.utils.shared_enums import Vendor from gso.utils.types.tt_number import TTNumber @@ -65,7 +66,7 @@ def remove_edge_port_real(subscription: dict[str, Any], tt_number: str, process_ @step("Netbox Clean Up") def netbox_clean_up(subscription: EdgePort) -> None: - """Update Netbox to remove the edge port LAG interface and all the LAG members.""" + """Update Netbox to remove the edge port LAG interface and all the LAG members. This is only for Nokia routers.""" nbclient = NetboxClient() for member in subscription.edge_port.edge_port_ae_members: @@ -81,13 +82,15 @@ def netbox_clean_up(subscription: EdgePort) -> None: ) def terminate_edge_port() -> StepList: """Terminate a new edge port in the network.""" + router_is_nokia = conditional(lambda state: state["subscription"]["edge_port"]["node"]["vendor"] == Vendor.NOKIA) + return ( begin >> store_process_subscription(Target.TERMINATE) >> unsync >> lso_interaction(remove_edge_port_dry) >> lso_interaction(remove_edge_port_real) - >> netbox_clean_up + >> router_is_nokia(netbox_clean_up) >> set_status(SubscriptionLifecycle.TERMINATED) >> resync >> done diff --git a/gso/workflows/edge_port/validate_edge_port.py b/gso/workflows/edge_port/validate_edge_port.py index 6932b2be338c076e6a3299cb98a727c0578cb990..1bbf60e3dd3b7368adf1a085aeaf333ee269fee9 100644 --- a/gso/workflows/edge_port/validate_edge_port.py +++ b/gso/workflows/edge_port/validate_edge_port.py @@ -3,11 +3,11 @@ from typing import Any from orchestrator.targets import Target -from orchestrator.types import State, UUIDstr from orchestrator.utils.errors import ProcessFailureError from orchestrator.workflow import StepList, begin, done, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import State, UUIDstr from gso.products.product_types.edge_port import EdgePort from gso.services.lso_client import LSOState, anonymous_lso_interaction diff --git a/gso/workflows/iptrunk/activate_iptrunk.py b/gso/workflows/iptrunk/activate_iptrunk.py index af37f2f24fa81545cbd9e546b3605703dfe0b7c7..eaca69db1297f5e7c5c0ea9111516c0cc52a701f 100644 --- a/gso/workflows/iptrunk/activate_iptrunk.py +++ b/gso/workflows/iptrunk/activate_iptrunk.py @@ -8,10 +8,11 @@ from orchestrator.config.assignee import Assignee from orchestrator.forms import SubmitFormPage from orchestrator.forms.validators import Label from orchestrator.targets import Target -from orchestrator.types import FormGenerator, SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done, inputstep, workflow from orchestrator.workflows.steps import resync, set_status, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import FormGenerator, UUIDstr from gso.products.product_types.iptrunk import Iptrunk diff --git a/gso/workflows/iptrunk/create_imported_iptrunk.py b/gso/workflows/iptrunk/create_imported_iptrunk.py index db1bdb6eb452a836eefa20216e5167e7c36baf0b..5e00dd29c984d2f7e58e687f5513804af0e7103d 100644 --- a/gso/workflows/iptrunk/create_imported_iptrunk.py +++ b/gso/workflows/iptrunk/create_imported_iptrunk.py @@ -7,10 +7,11 @@ from uuid import uuid4 from orchestrator import workflow from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done, step from orchestrator.workflows.steps import resync, set_status, store_process_subscription from pydantic import AfterValidator, ConfigDict +from pydantic_forms.types import FormGenerator, State from pydantic_forms.validators import validate_unique_list from gso.products import ProductName diff --git a/gso/workflows/iptrunk/create_iptrunk.py b/gso/workflows/iptrunk/create_iptrunk.py index c42ec7024d86d9b85305b7d5383a0e8a5bc97704..b50b50cbb6dcf4a4aea7b96872c81d2403a1e4bf 100644 --- a/gso/workflows/iptrunk/create_iptrunk.py +++ b/gso/workflows/iptrunk/create_iptrunk.py @@ -34,7 +34,7 @@ from annotated_types import Len from orchestrator.forms import FormPage from orchestrator.forms.validators import Choice, Label from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.utils.errors import ProcessFailureError from orchestrator.utils.json import json_dumps from orchestrator.workflow import StepList, begin, conditional, done, step, step_group, workflow @@ -42,6 +42,7 @@ from orchestrator.workflows.steps import resync, set_status, store_process_subsc from orchestrator.workflows.utils import wrap_create_initial_input_form from ping3 import ping from pydantic import ConfigDict +from pydantic_forms.types import FormGenerator, State, UUIDstr from pydantic_forms.validators import ReadOnlyField from pynetbox.models.dcim import Interfaces diff --git a/gso/workflows/iptrunk/deploy_twamp.py b/gso/workflows/iptrunk/deploy_twamp.py index 93c476ec4c38a5f930f2e75731bcdbc5c9d2bbf0..323963e7c3750e8e3d523745d41f4f29ceb84e36 100644 --- a/gso/workflows/iptrunk/deploy_twamp.py +++ b/gso/workflows/iptrunk/deploy_twamp.py @@ -9,11 +9,11 @@ import json from orchestrator.forms import SubmitFormPage from orchestrator.forms.validators import Label from orchestrator.targets import Target -from orchestrator.types import FormGenerator, UUIDstr from orchestrator.utils.json import json_dumps from orchestrator.workflow import StepList, begin, done, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import FormGenerator, UUIDstr from gso.products.product_types.iptrunk import Iptrunk from gso.services.lso_client import LSOState, lso_interaction diff --git a/gso/workflows/iptrunk/import_iptrunk.py b/gso/workflows/iptrunk/import_iptrunk.py index 36dce40de9daa4167b58f3e97458523a507b8203..05fff8c2dc2cc430ed205e1e9ce6cf38edb4a40d 100644 --- a/gso/workflows/iptrunk/import_iptrunk.py +++ b/gso/workflows/iptrunk/import_iptrunk.py @@ -1,10 +1,10 @@ """A modification workflow for migrating an ImportedIptrunk to an Iptrunk subscription.""" from orchestrator.targets import Target -from orchestrator.types import State, UUIDstr from orchestrator.workflow import StepList, done, init, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import State, UUIDstr from gso.products import ProductName from gso.products.product_types.iptrunk import ImportedIptrunk, Iptrunk diff --git a/gso/workflows/iptrunk/migrate_iptrunk.py b/gso/workflows/iptrunk/migrate_iptrunk.py index 60602b8e6b9034c2289948186493e8ba499c32fa..24b1fdb0a89d8f316fa891734d87df3075663238 100644 --- a/gso/workflows/iptrunk/migrate_iptrunk.py +++ b/gso/workflows/iptrunk/migrate_iptrunk.py @@ -17,12 +17,12 @@ from orchestrator.config.assignee import Assignee from orchestrator.forms import FormPage, SubmitFormPage from orchestrator.forms.validators import Choice, Label from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, UUIDstr from orchestrator.utils.json import json_dumps from orchestrator.workflow import StepList, begin, conditional, done, inputstep from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form from pydantic import ConfigDict +from pydantic_forms.types import FormGenerator, State, UUIDstr from pydantic_forms.validators import ReadOnlyField from pynetbox.models.dcim import Interfaces diff --git a/gso/workflows/iptrunk/modify_isis_metric.py b/gso/workflows/iptrunk/modify_isis_metric.py index e144a147a68a4f8f6f5e61b011af132d9fa67c3b..03c73c15c28104072b575b06122dc04dbe8c5182 100644 --- a/gso/workflows/iptrunk/modify_isis_metric.py +++ b/gso/workflows/iptrunk/modify_isis_metric.py @@ -8,11 +8,11 @@ import json from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, UUIDstr from orchestrator.utils.json import json_dumps from orchestrator.workflow import StepList, begin, done, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import FormGenerator, State, UUIDstr from gso.products.product_types.iptrunk import Iptrunk from gso.services.lso_client import LSOState, lso_interaction diff --git a/gso/workflows/iptrunk/modify_trunk_interface.py b/gso/workflows/iptrunk/modify_trunk_interface.py index fd533613d911a9e56b8566c4a0f5de876cbc459c..46de31d9940e1bb542fa23d4f56346aba895a111 100644 --- a/gso/workflows/iptrunk/modify_trunk_interface.py +++ b/gso/workflows/iptrunk/modify_trunk_interface.py @@ -14,12 +14,12 @@ from uuid import UUID, uuid4 from annotated_types import Len from orchestrator.forms import FormPage, SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, UUIDstr from orchestrator.utils.json import json_dumps from orchestrator.workflow import StepList, begin, conditional, done, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form from pydantic import AfterValidator, ConfigDict, Field +from pydantic_forms.types import FormGenerator, State, UUIDstr from pydantic_forms.validators import Label, ReadOnlyField from gso.products.product_blocks.iptrunk import ( diff --git a/gso/workflows/iptrunk/terminate_iptrunk.py b/gso/workflows/iptrunk/terminate_iptrunk.py index 2d0682a1c3006eeaa27cd9c38ed400e30b06b95d..47fceae0695eae68565d619f2894c65ce29d32f7 100644 --- a/gso/workflows/iptrunk/terminate_iptrunk.py +++ b/gso/workflows/iptrunk/terminate_iptrunk.py @@ -17,7 +17,7 @@ import json from orchestrator.forms import SubmitFormPage from orchestrator.forms.validators import Label from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.utils.json import json_dumps from orchestrator.workflow import StepList, begin, conditional, done, step, workflow from orchestrator.workflows.steps import ( @@ -27,6 +27,7 @@ from orchestrator.workflows.steps import ( unsync, ) from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import FormGenerator, State, UUIDstr from gso.products.product_blocks.iptrunk import IptrunkSideBlock from gso.products.product_types.iptrunk import Iptrunk diff --git a/gso/workflows/l2_circuit/create_imported_layer_2_circuit.py b/gso/workflows/l2_circuit/create_imported_layer_2_circuit.py index 6dbd052ebc9d9d9494e42152d4b89a1d0a85cf26..8b835edc12ea4c87ffb22aad7308fdf625f8ba21 100644 --- a/gso/workflows/l2_circuit/create_imported_layer_2_circuit.py +++ b/gso/workflows/l2_circuit/create_imported_layer_2_circuit.py @@ -6,11 +6,11 @@ from uuid import uuid4 from orchestrator import step, workflow from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done from orchestrator.workflows.steps import resync, set_status, store_process_subscription from pydantic import BaseModel, ConfigDict, model_validator -from pydantic_forms.types import UUIDstr +from pydantic_forms.types import FormGenerator, State, UUIDstr from gso.products import ProductName from gso.products.product_blocks.layer_2_circuit import Layer2CircuitSideBlockInactive, Layer2CircuitType diff --git a/gso/workflows/l2_circuit/create_layer_2_circuit.py b/gso/workflows/l2_circuit/create_layer_2_circuit.py index 20cc4c866199dae6bc50b418cf61e78d18597d1b..6007e74cda516795f958310485df8fd9782f81a0 100644 --- a/gso/workflows/l2_circuit/create_layer_2_circuit.py +++ b/gso/workflows/l2_circuit/create_layer_2_circuit.py @@ -52,11 +52,11 @@ def initial_input_generator(product_name: str) -> FormGenerator: def _vlan_range_field(*, is_vlan: bool) -> VLAN_ID: """Return the appropriate field type based on whether the circuit is VLAN.""" - return VLAN_ID if is_vlan else ReadOnlyField(None, default_type=int) + return VLAN_ID if is_vlan else ReadOnlyField(None, default_type=int) # type: ignore[return-value] def _policer_field(*, policer_enabled: bool) -> BandwidthString: """Return the appropriate field type based on whether the policer is enabled.""" - return BandwidthString if policer_enabled else ReadOnlyField(None, default_type=str) + return BandwidthString if policer_enabled else ReadOnlyField(None, default_type=str) # type: ignore[return-value] class Layer2CircuitServiceSidesPage(SubmitFormPage): model_config = ConfigDict(title=f"{product_name} - Configure Edge Ports") diff --git a/gso/workflows/l2_circuit/import_layer_2_circuit.py b/gso/workflows/l2_circuit/import_layer_2_circuit.py index 01224e86cbdb614eb879dfc2622fdbfb2804d19b..35ed7755b47b915cb98ad1f0552f58535fb3b45c 100644 --- a/gso/workflows/l2_circuit/import_layer_2_circuit.py +++ b/gso/workflows/l2_circuit/import_layer_2_circuit.py @@ -1,11 +1,11 @@ """A modification workflow for migrating an ImportedLayer2Circuit to an Layer2Circuit subscription.""" from orchestrator.targets import Target -from orchestrator.types import State, UUIDstr from orchestrator.utils.errors import ProcessFailureError from orchestrator.workflow import StepList, done, init, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import State, UUIDstr from gso.products import ProductName from gso.products.product_types.layer_2_circuit import ImportedLayer2Circuit, Layer2Circuit, Layer2CircuitServiceType diff --git a/gso/workflows/l2_circuit/modify_layer_2_circuit.py b/gso/workflows/l2_circuit/modify_layer_2_circuit.py index 6d7b15ff3a6d72f9e1b19e18a4f75e44c051193b..bd75b14fcf12f1e8dbb993d4e999383c9ab00f04 100644 --- a/gso/workflows/l2_circuit/modify_layer_2_circuit.py +++ b/gso/workflows/l2_circuit/modify_layer_2_circuit.py @@ -3,11 +3,11 @@ from orchestrator import begin, done, workflow from orchestrator.forms import FormPage, SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, UUIDstr from orchestrator.workflow import StepList, step from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form from pydantic import ConfigDict, Field +from pydantic_forms.types import FormGenerator, UUIDstr from pydantic_forms.validators import Divider, Label, ReadOnlyField from gso.products.product_blocks.layer_2_circuit import Layer2CircuitType diff --git a/gso/workflows/l2_circuit/terminate_layer_2_circuit.py b/gso/workflows/l2_circuit/terminate_layer_2_circuit.py index f985c69cc76f83e9263baaec327d7980b1435cd9..e8773a678ea5ce19c0dddef11e19d036f622460e 100644 --- a/gso/workflows/l2_circuit/terminate_layer_2_circuit.py +++ b/gso/workflows/l2_circuit/terminate_layer_2_circuit.py @@ -3,11 +3,11 @@ from orchestrator import begin, workflow from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, done from orchestrator.workflows.steps import resync, set_status, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form -from pydantic_forms.types import FormGenerator +from pydantic_forms.types import FormGenerator, UUIDstr from gso.products.product_types.layer_2_circuit import Layer2Circuit from gso.utils.types.tt_number import TTNumber diff --git a/gso/workflows/l3_core_service/create_imported_l3_core_service.py b/gso/workflows/l3_core_service/create_imported_l3_core_service.py index ebb73fb95687fbd6b31f59861c8f2bc1e27bba6f..70753dd08b427c3fc0930ada60a64713a636e4b8 100644 --- a/gso/workflows/l3_core_service/create_imported_l3_core_service.py +++ b/gso/workflows/l3_core_service/create_imported_l3_core_service.py @@ -5,12 +5,12 @@ from uuid import uuid4 from orchestrator import workflow from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, SubscriptionLifecycle +from orchestrator.types import SubscriptionLifecycle from orchestrator.utils.errors import ProcessFailureError from orchestrator.workflow import StepList, begin, done, step from orchestrator.workflows.steps import resync, set_status, store_process_subscription from pydantic import BaseModel, NonNegativeInt -from pydantic_forms.types import UUIDstr +from pydantic_forms.types import FormGenerator, UUIDstr from gso.products import ProductName from gso.products.product_blocks.bgp_session import BGPSession, IPFamily, IPTypes diff --git a/gso/workflows/l3_core_service/create_l3_core_service.py b/gso/workflows/l3_core_service/create_l3_core_service.py index 3069bdacec12f1a035238a03eb9e17a0e6b4f7fd..ab3114c25a5adaea9fe305a616bb32caeabcf8db 100644 --- a/gso/workflows/l3_core_service/create_l3_core_service.py +++ b/gso/workflows/l3_core_service/create_l3_core_service.py @@ -6,11 +6,12 @@ from uuid import uuid4 from orchestrator.forms import FormPage, SubmitFormPage from orchestrator.forms.validators import Label from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done, step, workflow from orchestrator.workflows.steps import resync, set_status, store_process_subscription from orchestrator.workflows.utils import wrap_create_initial_input_form from pydantic import BaseModel, ConfigDict, Field, NonNegativeInt, computed_field, model_validator +from pydantic_forms.types import FormGenerator, State, UUIDstr from pydantic_forms.validators import Divider from gso.products.product_blocks.bgp_session import BGPSession, IPFamily, IPTypes @@ -78,12 +79,12 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: add_v4_multicast: bool = Field(default=False, exclude=True) send_default_route: bool = False - @computed_field # type: ignore[misc] + @computed_field # type: ignore[prop-decorator] @property def families(self) -> list[IPFamily]: return [IPFamily.V4UNICAST, IPFamily.V4MULTICAST] if self.add_v4_multicast else [IPFamily.V4UNICAST] - @computed_field # type: ignore[misc] + @computed_field # type: ignore[prop-decorator] @property def ip_type(self) -> IPTypes: return IPTypes.IPV4 @@ -99,12 +100,12 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: add_v6_multicast: bool = Field(default=False, exclude=True) send_default_route: bool = False - @computed_field # type: ignore[misc] + @computed_field # type: ignore[prop-decorator] @property def families(self) -> list[IPFamily]: return [IPFamily.V6UNICAST, IPFamily.V6MULTICAST] if self.add_v6_multicast else [IPFamily.V6UNICAST] - @computed_field # type: ignore[misc] + @computed_field # type: ignore[prop-decorator] @property def ip_type(self) -> IPTypes: return IPTypes.IPV6 diff --git a/gso/workflows/l3_core_service/deploy_prefix_list.py b/gso/workflows/l3_core_service/deploy_prefix_list.py new file mode 100644 index 0000000000000000000000000000000000000000..ea0c6b07d7a72e0ac458a5a3415c56e6906710d8 --- /dev/null +++ b/gso/workflows/l3_core_service/deploy_prefix_list.py @@ -0,0 +1,76 @@ +"""Prefix Deployment workflow for L3 Core Service subscription objects.""" + +from typing import Any + +from orchestrator.targets import Target +from orchestrator.workflow import StepList, begin, done, step, workflow +from orchestrator.workflows.steps import resync, store_process_subscription, unsync +from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import State, UUIDstr + +from gso.products.product_types.l3_core_service import L3CoreService +from gso.services.lso_client import LSOState, lso_interaction +from gso.services.partners import get_partner_by_id + + +@step("Prepare list of all Access Ports") +def build_fqdn_list(subscription_id: UUIDstr) -> State: + """Build the list of all FQDNs that are in the list of access ports of a L3 Core Service subscription.""" + subscription = L3CoreService.from_subscription(subscription_id) + ap_fqdn_list = [ap.sbp.edge_port.node.router_fqdn for ap in subscription.l3_core_service.ap_list] + return {"ap_fqdn_list": ap_fqdn_list, "subscription": subscription} + + +@step("[DRY RUN] Deploy Prefix-Lists") +def deploy_prefix_lists_dry(subscription: dict[str, Any], process_id: UUIDstr, ap_fqdn_list: list[str]) -> LSOState: + """Workflow step for running a playbook that deploys prefix-lists in dry run mode.""" + extra_vars = { + "subscription": subscription, + "partner_name": get_partner_by_id(subscription["customer_id"]).name, + "dry_run": True, + "verb": "deploy", + "object": "prefix_list", + "is_verification_workflow": "false", + "commit_comment": f"GSO_PROCESS_ID: {process_id} - Deploy prefix-lists for {subscription["description"]}", + } + + return { + "playbook_name": "gap_ansible/playbooks/deploy_prefix_list.yaml", + "inventory": {"all": {"hosts": dict.fromkeys(ap_fqdn_list)}}, + "extra_vars": extra_vars, + } + + +@step("[REAL] Deploy Prefix-Lists") +def deploy_prefix_lists_real(subscription: dict[str, Any], process_id: UUIDstr, ap_fqdn_list: list[str]) -> LSOState: + """Workflow step for running a playbook that deploys prefix-lists.""" + extra_vars = { + "subscription": subscription, + "partner_name": get_partner_by_id(subscription["customer_id"]).name, + "dry_run": False, + "verb": "deploy", + "object": "prefix_list", + "is_verification_workflow": "false", + "commit_comment": (f"GSO_PROCESS_ID: {process_id} - Deploy prefix-lists for {subscription["description"]}"), + } + + return { + "playbook_name": "gap_ansible/playbooks/deploy_prefix_list.yaml", + "inventory": {"all": {"hosts": dict.fromkeys(ap_fqdn_list)}}, + "extra_vars": extra_vars, + } + + +@workflow("Deploy Prefix-List", target=Target.SYSTEM, initial_input_form=(wrap_modify_initial_input_form(None))) +def deploy_prefix_list() -> StepList: + """Deploy prefix-lists for an existing L3 Core Service subscription.""" + return ( + begin + >> store_process_subscription(Target.SYSTEM) + >> unsync + >> build_fqdn_list + >> lso_interaction(deploy_prefix_lists_dry) + >> lso_interaction(deploy_prefix_lists_real) + >> resync + >> done + ) diff --git a/gso/workflows/l3_core_service/import_l3_core_service.py b/gso/workflows/l3_core_service/import_l3_core_service.py index d7f0d06ee85ebb28e8a1dac60769d8f4014187a7..1c9d85def744dfe543560ef12ce8e309022bb308 100644 --- a/gso/workflows/l3_core_service/import_l3_core_service.py +++ b/gso/workflows/l3_core_service/import_l3_core_service.py @@ -1,11 +1,11 @@ """A modification workflow for migrating an `ImportedGeantIP` to a `GeantIP` subscription.""" from orchestrator.targets import Target -from orchestrator.types import State, UUIDstr from orchestrator.utils.errors import ProcessFailureError from orchestrator.workflow import StepList, done, init, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import State, UUIDstr from gso.products import ProductName from gso.products.product_types.l3_core_service import ( diff --git a/gso/workflows/l3_core_service/modify_l3_core_service.py b/gso/workflows/l3_core_service/modify_l3_core_service.py index 949cc280dc35f3c25dbfa759f917ec7af4f6d3d6..e8f5b8af1bc6d76844b5b2913e7eb9aed9d1dd93 100644 --- a/gso/workflows/l3_core_service/modify_l3_core_service.py +++ b/gso/workflows/l3_core_service/modify_l3_core_service.py @@ -6,12 +6,11 @@ from uuid import uuid4 from orchestrator import begin, conditional, done, step, workflow from orchestrator.forms import FormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, UUIDstr from orchestrator.workflow import StepList from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form from pydantic import AfterValidator, BaseModel, ConfigDict, Field, NonNegativeInt, computed_field -from pydantic_forms.types import State +from pydantic_forms.types import FormGenerator, State, UUIDstr from pydantic_forms.validators import Divider, Label from gso.products.product_blocks.bgp_session import BGPSession, IPFamily, IPTypes @@ -76,12 +75,12 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: add_v4_multicast: bool = Field(default=False, exclude=True) send_default_route: bool = False - @computed_field # type: ignore[misc] + @computed_field # type: ignore[prop-decorator] @property def families(self) -> list[IPFamily]: return [IPFamily.V4UNICAST, IPFamily.V4MULTICAST] if self.add_v4_multicast else [IPFamily.V4UNICAST] - @computed_field # type: ignore[misc] + @computed_field # type: ignore[prop-decorator] @property def ip_type(self) -> IPTypes: return IPTypes.IPV4 @@ -97,12 +96,12 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: add_v6_multicast: bool = Field(default=False, exclude=True) send_default_route: bool = False - @computed_field # type: ignore[misc] + @computed_field # type: ignore[prop-decorator] @property def families(self) -> list[IPFamily]: return [IPFamily.V6UNICAST, IPFamily.V6MULTICAST] if self.add_v6_multicast else [IPFamily.V6UNICAST] - @computed_field # type: ignore[misc] + @computed_field # type: ignore[prop-decorator] @property def ip_type(self) -> IPTypes: return IPTypes.IPV6 @@ -156,8 +155,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: class BindingPortModificationForm(FormPage): model_config = ConfigDict( - title=f"{product_name} - Modify Edge Port configuration " - f"({access_port_index + 1}/{len(input_ap_list)})" + title=f"{product_name} - Modify Edge Port configuration ({access_port_index + 1}/{len(input_ap_list)})" ) current_ep_label: Label = Field( f"Currently configuring on {access_port.sbp.edge_port.edge_port_description} " @@ -230,7 +228,9 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: is_tagged: bool = False vlan_id: VLAN_ID ipv4_address: IPv4AddressType + ipv4_mask: IPv4Netmask ipv6_address: IPv6AddressType + ipv6_mask: IPv6Netmask custom_firewall_filters: bool = False v4_bfd_settings: BFDInputModel v6_bfd_settings: BFDInputModel @@ -297,7 +297,9 @@ def modify_existing_sbp_blocks(subscription: L3CoreService, modified_sbp_list: l current_sbp.gs_id = modified_sbp_data["gs_id"] current_sbp.is_tagged = modified_sbp_data["is_tagged"] current_sbp.ipv4_address = modified_sbp_data["ipv4_address"] + current_sbp.ipv4_mask = modified_sbp_data["ipv4_mask"] current_sbp.ipv6_address = modified_sbp_data["ipv6_address"] + current_sbp.ipv6_mask = modified_sbp_data["ipv6_mask"] current_sbp.custom_firewall_filters = modified_sbp_data["custom_firewall_filters"] access_port.ap_type = modified_sbp_data["new_ap_type"] access_port.custom_service_name = modified_sbp_data["custom_service_name"] diff --git a/gso/workflows/l3_core_service/terminate_l3_core_service.py b/gso/workflows/l3_core_service/terminate_l3_core_service.py index 9cf401a5c89edbef1169e9f642ef603fff9b27bb..8cae6c430b4a635906cf80fb3d67d25444a9a8ef 100644 --- a/gso/workflows/l3_core_service/terminate_l3_core_service.py +++ b/gso/workflows/l3_core_service/terminate_l3_core_service.py @@ -3,11 +3,11 @@ from orchestrator import begin, workflow from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, done from orchestrator.workflows.steps import resync, set_status, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form -from pydantic_forms.types import FormGenerator +from pydantic_forms.types import FormGenerator, UUIDstr from gso.products.product_types.l3_core_service import L3CoreService from gso.utils.types.tt_number import TTNumber diff --git a/gso/workflows/l3_core_service/validate_prefix_list.py b/gso/workflows/l3_core_service/validate_prefix_list.py new file mode 100644 index 0000000000000000000000000000000000000000..e2c6cbbf85a09608cca982ffd63105d94631c53b --- /dev/null +++ b/gso/workflows/l3_core_service/validate_prefix_list.py @@ -0,0 +1,53 @@ +"""Prefix Validation workflow for L3 Core Service subscription objects.""" + +from typing import Any + +from orchestrator.targets import Target +from orchestrator.workflow import StepList, begin, done, step, workflow +from orchestrator.workflows.steps import store_process_subscription +from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import State, UUIDstr + +from gso.products.product_types.l3_core_service import L3CoreService +from gso.services.lso_client import LSOState, anonymous_lso_interaction +from gso.services.partners import get_partner_by_id + + +@step("Prepare list of all Access Ports") +def build_fqdn_list(subscription_id: UUIDstr) -> State: + """Build the list of all FQDNs that are in the list of access ports of a L3 Core Service subscription.""" + subscription = L3CoreService.from_subscription(subscription_id) + ap_fqdn_list = [ap.sbp.edge_port.node.router_fqdn for ap in subscription.l3_core_service.ap_list] + return {"ap_fqdn_list": ap_fqdn_list, "subscription": subscription} + + +@step("[DRY RUN] Validate Prefix-Lists") +def validate_prefix_lists_dry(subscription: dict[str, Any], process_id: UUIDstr, ap_fqdn_list: list[str]) -> LSOState: + """Workflow step for running a playbook that validates prefix-lists in dry run mode.""" + extra_vars = { + "subscription": subscription, + "partner_name": get_partner_by_id(subscription["customer_id"]).name, + "dry_run": True, + "verb": "deploy", + "object": "prefix_list", + "is_verification_workflow": "true", + "commit_comment": f"GSO_PROCESS_ID: {process_id} - Validate prefix-lists for {subscription["description"]}", + } + + return { + "playbook_name": "gap_ansible/playbooks/validate_prefix_list.yaml", + "inventory": {"all": {"hosts": dict.fromkeys(ap_fqdn_list)}}, + "extra_vars": extra_vars, + } + + +@workflow("Validate Prefix-List", target=Target.SYSTEM, initial_input_form=(wrap_modify_initial_input_form(None))) +def validate_prefix_list() -> StepList: + """Validate prefix-lists for an existing L3 Core Service subscription.""" + return ( + begin + >> store_process_subscription(Target.SYSTEM) + >> build_fqdn_list + >> anonymous_lso_interaction(validate_prefix_lists_dry) + >> done + ) diff --git a/gso/workflows/lan_switch_interconnect/create_imported_lan_switch_interconnect.py b/gso/workflows/lan_switch_interconnect/create_imported_lan_switch_interconnect.py index 03c6e8aa8216809b82d10c51186a2bb56f25634e..12bcff95047f27e385655766bbbfa0f3a4020bbb 100644 --- a/gso/workflows/lan_switch_interconnect/create_imported_lan_switch_interconnect.py +++ b/gso/workflows/lan_switch_interconnect/create_imported_lan_switch_interconnect.py @@ -5,9 +5,10 @@ from uuid import uuid4 from orchestrator import step, workflow from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done from orchestrator.workflows.steps import resync, set_status, store_process_subscription +from pydantic_forms.types import FormGenerator, State from gso.cli.imports import LanSwitchInterconnectRouterSideImportModel, LanSwitchInterconnectSwitchSideImportModel from gso.products import ProductName diff --git a/gso/workflows/lan_switch_interconnect/create_lan_switch_interconnect.py b/gso/workflows/lan_switch_interconnect/create_lan_switch_interconnect.py index 0c5f74850a8c518740d1eb73510e8aa0af80a198..4ebd46d9accd4992e29ead9a5afd78ab232b1ce1 100644 --- a/gso/workflows/lan_switch_interconnect/create_lan_switch_interconnect.py +++ b/gso/workflows/lan_switch_interconnect/create_lan_switch_interconnect.py @@ -7,12 +7,13 @@ from uuid import uuid4 from annotated_types import Len from orchestrator.forms import FormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.utils.errors import ProcessFailureError from orchestrator.workflow import StepList, begin, done, step, workflow from orchestrator.workflows.steps import resync, set_status, store_process_subscription from orchestrator.workflows.utils import wrap_create_initial_input_form from pydantic import AfterValidator, ConfigDict +from pydantic_forms.types import FormGenerator, State, UUIDstr from pydantic_forms.validators import ReadOnlyField from gso.products.product_blocks.lan_switch_interconnect import ( diff --git a/gso/workflows/lan_switch_interconnect/import_lan_switch_interconnect.py b/gso/workflows/lan_switch_interconnect/import_lan_switch_interconnect.py index 0de05997aad2dc5ffd930c98f6f271174add6b82..32d47addfb270856f00ca4ccc2493f654899e0e4 100644 --- a/gso/workflows/lan_switch_interconnect/import_lan_switch_interconnect.py +++ b/gso/workflows/lan_switch_interconnect/import_lan_switch_interconnect.py @@ -2,10 +2,10 @@ from orchestrator import step, workflow from orchestrator.targets import Target -from orchestrator.types import State, UUIDstr from orchestrator.workflow import StepList, begin, done from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import State, UUIDstr from gso.products import ProductName from gso.products.product_types.lan_switch_interconnect import ImportedLanSwitchInterconnect, LanSwitchInterconnect diff --git a/gso/workflows/lan_switch_interconnect/terminate_lan_switch_interconnect.py b/gso/workflows/lan_switch_interconnect/terminate_lan_switch_interconnect.py index 47e1865478179b1baffb0542dc5020aabe5f44d3..b878b7d6afc27ee0eebbdf76b18708b586a72148 100644 --- a/gso/workflows/lan_switch_interconnect/terminate_lan_switch_interconnect.py +++ b/gso/workflows/lan_switch_interconnect/terminate_lan_switch_interconnect.py @@ -3,11 +3,11 @@ from orchestrator import begin, workflow from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, done, step from orchestrator.workflows.steps import resync, set_status, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form -from pydantic_forms.types import FormGenerator +from pydantic_forms.types import FormGenerator, UUIDstr from pydantic_forms.validators import Label from gso.products.product_types.lan_switch_interconnect import LanSwitchInterconnect diff --git a/gso/workflows/office_router/create_imported_office_router.py b/gso/workflows/office_router/create_imported_office_router.py index c1280c13d0b1e0e729843078939b091573267f14..9dbef9f32fd5f5b7eb4a0b1c4f09f54eb556e0d8 100644 --- a/gso/workflows/office_router/create_imported_office_router.py +++ b/gso/workflows/office_router/create_imported_office_router.py @@ -3,10 +3,11 @@ from orchestrator import workflow from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done, step from orchestrator.workflows.steps import resync, set_status, store_process_subscription from pydantic import ConfigDict +from pydantic_forms.types import FormGenerator, State from gso.products import ProductName from gso.products.product_types.office_router import ImportedOfficeRouterInactive diff --git a/gso/workflows/office_router/import_office_router.py b/gso/workflows/office_router/import_office_router.py index 0d1d67abdd28b7a67c68cec92f05d0268e52de1f..6ae3240b5d976a18357608e49123bb500a3237da 100644 --- a/gso/workflows/office_router/import_office_router.py +++ b/gso/workflows/office_router/import_office_router.py @@ -1,10 +1,10 @@ """A modification workflow for migrating an ImportedOfficeRouter to an OfficeRouter subscription.""" from orchestrator.targets import Target -from orchestrator.types import State, UUIDstr from orchestrator.workflow import StepList, done, init, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import State, UUIDstr from gso.products import ProductName from gso.products.product_types.office_router import ImportedOfficeRouter, OfficeRouter diff --git a/gso/workflows/opengear/create_imported_opengear.py b/gso/workflows/opengear/create_imported_opengear.py index a7433f2bfed6a6b5400893d0f52b669a1e70fc9a..47f157f0f11e22f7181bd0eb623d4e16b4703ee8 100644 --- a/gso/workflows/opengear/create_imported_opengear.py +++ b/gso/workflows/opengear/create_imported_opengear.py @@ -3,10 +3,11 @@ from orchestrator import workflow from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, done, init, step from orchestrator.workflows.steps import resync, set_status, store_process_subscription from pydantic import ConfigDict +from pydantic_forms.types import FormGenerator, State from gso.products import ProductName from gso.products.product_types.opengear import ImportedOpengearInactive diff --git a/gso/workflows/opengear/import_opengear.py b/gso/workflows/opengear/import_opengear.py index d9bb75306a39161dc03c78ca47cc70449cd56850..1b34b55a7e4598e07d8c61a41de7c95c0b26661d 100644 --- a/gso/workflows/opengear/import_opengear.py +++ b/gso/workflows/opengear/import_opengear.py @@ -1,10 +1,10 @@ """A modification workflow for migrating an ImportedOpengear to an Opengear subscription.""" from orchestrator.targets import Target -from orchestrator.types import State, UUIDstr from orchestrator.workflow import StepList, done, init, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import State, UUIDstr from gso.products import ProductName from gso.products.product_types.opengear import ImportedOpengear, Opengear diff --git a/gso/workflows/router/activate_router.py b/gso/workflows/router/activate_router.py index 683292d764c332ce665faaaf1000645633e54957..e5b65ca5c4e0e674b9f7700fcc54dc59ff98fe0a 100644 --- a/gso/workflows/router/activate_router.py +++ b/gso/workflows/router/activate_router.py @@ -8,10 +8,11 @@ from orchestrator.config.assignee import Assignee from orchestrator.forms import SubmitFormPage from orchestrator.forms.validators import Label from orchestrator.targets import Target -from orchestrator.types import FormGenerator, SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done, inputstep, workflow from orchestrator.workflows.steps import resync, set_status, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import FormGenerator, UUIDstr from gso.products.product_types.router import Router diff --git a/gso/workflows/router/create_imported_router.py b/gso/workflows/router/create_imported_router.py index 81acba1aedb5209a3e113ba5542a57492270667b..56640256ab700eb0207f25e18b72b3bd591fd96e 100644 --- a/gso/workflows/router/create_imported_router.py +++ b/gso/workflows/router/create_imported_router.py @@ -3,10 +3,11 @@ from orchestrator import workflow from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done, step from orchestrator.workflows.steps import resync, set_status, store_process_subscription from pydantic import ConfigDict +from pydantic_forms.types import FormGenerator, State from gso.products import ProductName from gso.products.product_blocks.router import RouterRole diff --git a/gso/workflows/router/create_router.py b/gso/workflows/router/create_router.py index 6cd77a108e8c3bf3aaf62827f3b69156b20c1fbe..8c7a8434424c7f6ee4d7801597dd9975a4ebd27a 100644 --- a/gso/workflows/router/create_router.py +++ b/gso/workflows/router/create_router.py @@ -46,12 +46,13 @@ from orchestrator.config.assignee import Assignee from orchestrator.forms import FormPage, SubmitFormPage from orchestrator.forms.validators import Label from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.utils.errors import ProcessFailureError from orchestrator.workflow import StepList, begin, conditional, done, inputstep, step, workflow from orchestrator.workflows.steps import resync, set_status, store_process_subscription from orchestrator.workflows.utils import wrap_create_initial_input_form from pydantic import ConfigDict, model_validator +from pydantic_forms.types import FormGenerator, State, UUIDstr from pydantic_forms.validators import ReadOnlyField from gso.products.product_blocks.router import RouterRole diff --git a/gso/workflows/router/import_router.py b/gso/workflows/router/import_router.py index 1421ff0faac2638d15e15bea867865ea887e16ec..efe39a1ae4c400924ec948205ccb269d89721ae9 100644 --- a/gso/workflows/router/import_router.py +++ b/gso/workflows/router/import_router.py @@ -1,10 +1,10 @@ """A modification workflow for setting a new ISIS metric for an IP trunk.""" from orchestrator.targets import Target -from orchestrator.types import State, UUIDstr from orchestrator.workflow import StepList, done, init, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import State, UUIDstr from gso.products import ProductName from gso.products.product_types.router import ImportedRouter, Router diff --git a/gso/workflows/router/modify_connection_strategy.py b/gso/workflows/router/modify_connection_strategy.py index e571cfc44243a3a172ad9ce86377e380ef4e06b9..93cca08daf5f8cd6294f00a0f8657fd8712bc628 100644 --- a/gso/workflows/router/modify_connection_strategy.py +++ b/gso/workflows/router/modify_connection_strategy.py @@ -6,11 +6,11 @@ loopback interface. from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, UUIDstr from orchestrator.workflow import StepList, begin, done, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form from pydantic import ConfigDict +from pydantic_forms.types import FormGenerator, State, UUIDstr from gso.products.product_types.router import Router from gso.utils.shared_enums import ConnectionStrategy diff --git a/gso/workflows/router/modify_kentik_license.py b/gso/workflows/router/modify_kentik_license.py index 3a809fc0379072bb1bbd110608afcc7c24846bec..5b15f62fa6c4ae77e0ff13cf8cef66dda772a4c7 100644 --- a/gso/workflows/router/modify_kentik_license.py +++ b/gso/workflows/router/modify_kentik_license.py @@ -9,12 +9,12 @@ from typing import Any from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, UUIDstr from orchestrator.utils.errors import ProcessFailureError from orchestrator.workflow import StepList, begin, done, step, workflow from orchestrator.workflows.steps import store_process_subscription from orchestrator.workflows.utils import wrap_modify_initial_input_form from pydantic import model_validator +from pydantic_forms.types import FormGenerator, State, UUIDstr from pydantic_forms.validators import Choice from gso.products.product_blocks.router import RouterRole diff --git a/gso/workflows/router/promote_p_to_pe.py b/gso/workflows/router/promote_p_to_pe.py index 63f59a24cf66e68962278b2b6d305d570c042a08..ce961ed10581017bc7a592031fd36c1197c8cad7 100644 --- a/gso/workflows/router/promote_p_to_pe.py +++ b/gso/workflows/router/promote_p_to_pe.py @@ -7,12 +7,12 @@ from orchestrator.config.assignee import Assignee from orchestrator.forms import SubmitFormPage from orchestrator.forms.validators import Label from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, UUIDstr from orchestrator.utils.json import json_dumps from orchestrator.workflow import StepList, begin, done, inputstep, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form from pydantic import ConfigDict, model_validator +from pydantic_forms.types import FormGenerator, State, UUIDstr from gso.products.product_blocks.router import RouterRole from gso.products.product_types.router import Router diff --git a/gso/workflows/router/redeploy_base_config.py b/gso/workflows/router/redeploy_base_config.py index bd64fb7e276a940af816c81aeda3f29b23bc90e6..11c4264dc754f2e7289241a2324ba7b00dabf2c7 100644 --- a/gso/workflows/router/redeploy_base_config.py +++ b/gso/workflows/router/redeploy_base_config.py @@ -11,10 +11,10 @@ run. After confirmation by an operator, the configuration is committed to the ma from orchestrator.forms import SubmitFormPage from orchestrator.forms.validators import Label from orchestrator.targets import Target -from orchestrator.types import FormGenerator, UUIDstr from orchestrator.workflow import StepList, begin, done, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import FormGenerator, UUIDstr from gso.products.product_types.router import Router from gso.services.lso_client import lso_interaction diff --git a/gso/workflows/router/terminate_router.py b/gso/workflows/router/terminate_router.py index a908bc8e184a147023fff8b81052feba81da300d..9c73b13e5ca3ae08dc6ef8e1c6bbffd133fd37e9 100644 --- a/gso/workflows/router/terminate_router.py +++ b/gso/workflows/router/terminate_router.py @@ -22,7 +22,7 @@ import logging from orchestrator.forms import SubmitFormPage from orchestrator.forms.validators import Label from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.utils.errors import ProcessFailureError from orchestrator.utils.json import json_dumps from orchestrator.workflow import StepList, begin, conditional, done, step, workflow @@ -33,6 +33,7 @@ from orchestrator.workflows.steps import ( unsync, ) from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import FormGenerator, State, UUIDstr from requests import HTTPError from gso.products.product_blocks.router import RouterRole diff --git a/gso/workflows/router/update_ibgp_mesh.py b/gso/workflows/router/update_ibgp_mesh.py index b0fa37c6da14f778074e5f05fc7e5dd9115e4f44..2c8c9db00fbc54649a8182f236e48648a899e359 100644 --- a/gso/workflows/router/update_ibgp_mesh.py +++ b/gso/workflows/router/update_ibgp_mesh.py @@ -24,11 +24,12 @@ from orchestrator.config.assignee import Assignee from orchestrator.forms import SubmitFormPage from orchestrator.forms.validators import Label from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, conditional, done, inputstep, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form from pydantic import ConfigDict, model_validator +from pydantic_forms.types import FormGenerator, State, UUIDstr from gso.products.product_blocks.router import RouterRole from gso.products.product_types.router import Router diff --git a/gso/workflows/router/validate_router.py b/gso/workflows/router/validate_router.py index a469237e5a4bdf365934203edaf90a7cfad17d16..25bd3176c50b8506117dc3c09767944c85707baa 100644 --- a/gso/workflows/router/validate_router.py +++ b/gso/workflows/router/validate_router.py @@ -3,11 +3,11 @@ from typing import Any from orchestrator.targets import Target -from orchestrator.types import State, UUIDstr from orchestrator.utils.errors import ProcessFailureError from orchestrator.workflow import StepList, begin, conditional, done, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import State, UUIDstr from gso.products.product_blocks.router import RouterRole from gso.products.product_types.router import Router diff --git a/gso/workflows/site/create_imported_site.py b/gso/workflows/site/create_imported_site.py index 16f2471f7fd97b0d6918be70da8e0645415cf3b4..05d73eff24afd1fdaf4ff56c3953ef87c89f3786 100644 --- a/gso/workflows/site/create_imported_site.py +++ b/gso/workflows/site/create_imported_site.py @@ -4,10 +4,11 @@ from uuid import UUID from orchestrator.forms import FormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done, step, workflow from orchestrator.workflows.steps import resync, set_status, store_process_subscription from pydantic import ConfigDict +from pydantic_forms.types import FormGenerator, State from gso.products import ProductName from gso.products.product_blocks.site import SiteTier diff --git a/gso/workflows/site/create_site.py b/gso/workflows/site/create_site.py index 149178e346c5526f2d42bcee95aae89e977a3c68..40f5ae0a148c968054cce5755f8ca0cc5ccf036d 100644 --- a/gso/workflows/site/create_site.py +++ b/gso/workflows/site/create_site.py @@ -6,11 +6,12 @@ The `create_site` workflow creates a new site object in the service database, an from orchestrator.forms import FormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done, step, workflow from orchestrator.workflows.steps import resync, set_status, store_process_subscription from orchestrator.workflows.utils import wrap_create_initial_input_form from pydantic import ConfigDict +from pydantic_forms.types import FormGenerator, State, UUIDstr from pydantic_forms.validators import ReadOnlyField from gso.products.product_blocks import site as site_pb diff --git a/gso/workflows/site/import_site.py b/gso/workflows/site/import_site.py index f2130354c35095082dd1fcd2444f9ad924eaf719..20f19ff266504d41c56c04dccdd9df8f1506a6b4 100644 --- a/gso/workflows/site/import_site.py +++ b/gso/workflows/site/import_site.py @@ -1,10 +1,10 @@ """A modification workflow for migrating an ImportedSite to a Site subscription.""" from orchestrator.targets import Target -from orchestrator.types import State, UUIDstr from orchestrator.workflow import StepList, done, init, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import State, UUIDstr from gso.products import ProductName from gso.products.product_types.site import ImportedSite, Site diff --git a/gso/workflows/site/modify_site.py b/gso/workflows/site/modify_site.py index 2a1913f6bbdeb56ff2b9e4ed7b26025566ccc9f3..8776bc3e4ea7f5acc78ec783be663465a1cd19da 100644 --- a/gso/workflows/site/modify_site.py +++ b/gso/workflows/site/modify_site.py @@ -17,7 +17,7 @@ from typing import Annotated from orchestrator.forms import FormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done, step, workflow from orchestrator.workflows.steps import ( resync, @@ -27,6 +27,7 @@ from orchestrator.workflows.steps import ( ) from orchestrator.workflows.utils import wrap_modify_initial_input_form from pydantic import AfterValidator, ConfigDict +from pydantic_forms.types import FormGenerator, State, UUIDstr from pydantic_forms.validators import ReadOnlyField from gso.products.product_blocks.site import SiteTier diff --git a/gso/workflows/site/terminate_site.py b/gso/workflows/site/terminate_site.py index 0e34f209cb50e1cd5f4ac1b446f30baabd9d7dbc..5e21a4cbfc610ce83cb237e4424a891f912190b0 100644 --- a/gso/workflows/site/terminate_site.py +++ b/gso/workflows/site/terminate_site.py @@ -8,7 +8,7 @@ unavailable for an operator to run, accompanied by an error message explaining t from orchestrator.forms import SubmitFormPage from orchestrator.forms.validators import Label from orchestrator.targets import Target -from orchestrator.types import FormGenerator, SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done, workflow from orchestrator.workflows.steps import ( resync, @@ -17,6 +17,7 @@ from orchestrator.workflows.steps import ( unsync, ) from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import FormGenerator, UUIDstr from gso.products.product_types.site import Site diff --git a/gso/workflows/super_pop_switch/create_imported_super_pop_switch.py b/gso/workflows/super_pop_switch/create_imported_super_pop_switch.py index 6b34253173ce2b5ec72963cd274b78141fba2504..e0e1eb7526f5ad45366311af98b466dd62960fa4 100644 --- a/gso/workflows/super_pop_switch/create_imported_super_pop_switch.py +++ b/gso/workflows/super_pop_switch/create_imported_super_pop_switch.py @@ -3,10 +3,11 @@ from orchestrator import workflow from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done, step from orchestrator.workflows.steps import resync, set_status, store_process_subscription from pydantic import ConfigDict +from pydantic_forms.types import FormGenerator, State from gso.products import ProductName from gso.products.product_types.super_pop_switch import ImportedSuperPopSwitchInactive diff --git a/gso/workflows/super_pop_switch/import_super_pop_switch.py b/gso/workflows/super_pop_switch/import_super_pop_switch.py index 63c7148f978a7d11eb9b97f0d3bfabb9dc71b30c..e29e9d9fc74360dcae256e963cb3a2bcadbff28f 100644 --- a/gso/workflows/super_pop_switch/import_super_pop_switch.py +++ b/gso/workflows/super_pop_switch/import_super_pop_switch.py @@ -1,10 +1,10 @@ """A modification workflow for migrating an ImportedSuperPoPSwitch to a SuperPopSwitch subscription.""" from orchestrator.targets import Target -from orchestrator.types import State, UUIDstr from orchestrator.workflow import StepList, done, init, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import State, UUIDstr from gso.products import ProductName from gso.products.product_types.super_pop_switch import ImportedSuperPopSwitch, SuperPopSwitch diff --git a/gso/workflows/switch/activate_switch.py b/gso/workflows/switch/activate_switch.py index dc71cfa7d821765032f4f062e79919c39a2700ec..2957faa58d3020510b2c2c2c7c4054bf288cac3b 100644 --- a/gso/workflows/switch/activate_switch.py +++ b/gso/workflows/switch/activate_switch.py @@ -4,10 +4,11 @@ from orchestrator.config.assignee import Assignee from orchestrator.forms import SubmitFormPage from orchestrator.forms.validators import Label from orchestrator.targets import Target -from orchestrator.types import FormGenerator, SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done, inputstep, workflow from orchestrator.workflows.steps import resync, set_status, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import FormGenerator, UUIDstr from gso.products.product_types.switch import Switch diff --git a/gso/workflows/switch/create_imported_switch.py b/gso/workflows/switch/create_imported_switch.py index 30d7ea46360d01d431c6c635bc8813af39146e07..76133b5f8930cd4540b90091b7e6e9e08d684797 100644 --- a/gso/workflows/switch/create_imported_switch.py +++ b/gso/workflows/switch/create_imported_switch.py @@ -3,9 +3,10 @@ from orchestrator import step, workflow from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done from orchestrator.workflows.steps import resync, set_status, store_process_subscription +from pydantic_forms.types import FormGenerator, State, UUIDstr from gso.products import ProductName from gso.products.product_blocks.switch import SwitchModel diff --git a/gso/workflows/switch/create_switch.py b/gso/workflows/switch/create_switch.py index 09b0a03ea25af0880dbf6ef89ac02e6bd49f1a45..d2b7ec337a6dcd99098348da3eeb612933579469 100644 --- a/gso/workflows/switch/create_switch.py +++ b/gso/workflows/switch/create_switch.py @@ -5,11 +5,12 @@ from typing import Self from orchestrator.config.assignee import Assignee from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done, inputstep, step, workflow from orchestrator.workflows.steps import resync, set_status, store_process_subscription from orchestrator.workflows.utils import wrap_create_initial_input_form from pydantic import ConfigDict, model_validator +from pydantic_forms.types import FormGenerator, State, UUIDstr from pydantic_forms.validators import Choice, Label, ReadOnlyField from gso.products.product_blocks.switch import SwitchModel diff --git a/gso/workflows/switch/import_switch.py b/gso/workflows/switch/import_switch.py index 8ecf2d43930b1e40f84d1866f2db0a7cf8115306..f0caa5eb6c1eaae8f3899e7dfaa8c55bb100c506 100644 --- a/gso/workflows/switch/import_switch.py +++ b/gso/workflows/switch/import_switch.py @@ -2,10 +2,10 @@ from orchestrator import step, workflow from orchestrator.targets import Target -from orchestrator.types import State, UUIDstr from orchestrator.workflow import StepList, begin, done from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic_forms.types import State, UUIDstr from gso.products import ProductName from gso.products.product_types.switch import ImportedSwitch, Switch diff --git a/gso/workflows/switch/terminate_switch.py b/gso/workflows/switch/terminate_switch.py index 84bea41dc5543631cec790a7dacb9bd729ea1f0d..01f149b1dae9717a955afbb78636942ce5d26e71 100644 --- a/gso/workflows/switch/terminate_switch.py +++ b/gso/workflows/switch/terminate_switch.py @@ -3,11 +3,11 @@ from orchestrator import begin, done, workflow from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, step from orchestrator.workflows.steps import resync, set_status, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form -from pydantic_forms.types import FormGenerator +from pydantic_forms.types import FormGenerator, UUIDstr from pydantic_forms.validators import Label from gso.products.product_types.switch import Switch diff --git a/gso/workflows/tasks/create_partners.py b/gso/workflows/tasks/create_partners.py index e6e4e4b04703de62b46a74081327163bbde0bd56..3b82e41fba5a93cb2f744396e6ee82a34f5dc8e7 100644 --- a/gso/workflows/tasks/create_partners.py +++ b/gso/workflows/tasks/create_partners.py @@ -2,9 +2,9 @@ from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State from orchestrator.workflow import StepList, begin, done, step, workflow from pydantic import ConfigDict +from pydantic_forms.types import FormGenerator, State from gso.services.partners import PartnerEmail, PartnerName, PartnerSchema, create_partner diff --git a/gso/workflows/tasks/delete_partners.py b/gso/workflows/tasks/delete_partners.py index 15cbe2ac2f932ad2de5fc0d584ce5e00eb3325e5..eaa97d7e9fb443fef9efa9758de6043b1fb6996c 100644 --- a/gso/workflows/tasks/delete_partners.py +++ b/gso/workflows/tasks/delete_partners.py @@ -4,9 +4,9 @@ from enum import Enum from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, UUIDstr from orchestrator.workflow import StepList, begin, done, step, workflow from pydantic import ConfigDict, EmailStr, field_validator +from pydantic_forms.types import FormGenerator, State, UUIDstr from gso.services.partners import delete_partner, get_partner_by_name from gso.services.subscriptions import get_subscriptions diff --git a/gso/workflows/tasks/modify_partners.py b/gso/workflows/tasks/modify_partners.py index 0445ceeeb65f82bb4e7ea8e1124bc2bc8613c34d..5910a7bd8e6fe6190bb5bccfd0c1428e63c43d7c 100644 --- a/gso/workflows/tasks/modify_partners.py +++ b/gso/workflows/tasks/modify_partners.py @@ -2,9 +2,9 @@ from orchestrator.forms import FormPage, SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, UUIDstr from orchestrator.workflow import StepList, begin, done, step, workflow from pydantic import ConfigDict, EmailStr, field_validator +from pydantic_forms.types import FormGenerator, State, UUIDstr from gso.services.partners import ( ModifiedPartnerSchema, diff --git a/gso/workflows/tasks/send_email_notifications.py b/gso/workflows/tasks/send_email_notifications.py index 42b94bd421f4d2e45af354efa00205f975530c07..527ea7f985d7d09f064d14a1b98565de4246c738 100644 --- a/gso/workflows/tasks/send_email_notifications.py +++ b/gso/workflows/tasks/send_email_notifications.py @@ -1,27 +1,37 @@ -"""Send email notifications for all tasks that have failed.""" +"""Send email notifications for all tasks that have failed. + +When validation tasks have failed, at most two separate emails will be sent. The first will contain all general failures +of subscription validation workflows. The second email contains an overview of the prefix list validations that have +failed. +""" + +from typing import Any from orchestrator.targets import Target -from orchestrator.types import State from orchestrator.workflow import StepList, conditional, done, init, step, workflow +from pydantic_forms.types import State from gso.services.mailer import send_mail -from gso.services.processes import get_failed_tasks +from gso.services.processes import get_failed_tasks, get_failed_tasks_by_workflow_name from gso.services.subscriptions import get_subscription_by_process_id from gso.settings import load_oss_params -@step("Gather all tasks that recently failed") +@step("Gather all tasks that have failed") def gather_failed_tasks() -> State: """Gather all tasks that have failed.""" - return {"failed_tasks": get_failed_tasks()} + failed_prefix_list_tasks = get_failed_tasks_by_workflow_name("validate_prefix_list") + all_other_tasks = list(set(get_failed_tasks()) - set(failed_prefix_list_tasks)) + return {"failed_tasks": all_other_tasks, "failed_prefix_list_checks": failed_prefix_list_tasks} -@step("Send notification emails for all failed tasks") -def send_email_notifications(state: State) -> None: + +@step("Send notification email for failed tasks") +def send_email_notification(failed_tasks: list[dict[str, Any]]) -> None: """Send out an email notification for all tasks that have failed.""" general_settings = load_oss_params().GENERAL all_alerts = "" - for failure in state["failed_tasks"]: + for failure in failed_tasks: failed_task_url = f"{general_settings.public_hostname}/workflows/{failure["process_id"]}" failed_subscription = get_subscription_by_process_id(failure["process_id"]) all_alerts = f"{all_alerts}------\n\n" @@ -45,9 +55,45 @@ def send_email_notifications(state: State) -> None: ) +@step("Send notification emails for failed prefix list tasks") +def send_prefix_list_email_notification(failed_prefix_list_checks: list[dict[str, Any]]) -> None: + """Send out an email notification for all prefix list validation tasks that have failed.""" + general_settings = load_oss_params().GENERAL + all_alerts = "" + for failure in failed_prefix_list_checks: + failed_task_url = f"{general_settings.public_hostname}/workflows/{failure["process_id"]}" + failed_subscription = get_subscription_by_process_id(failure["process_id"]) + all_alerts = f"{all_alerts}------\n\n" + if failed_subscription: + all_alerts = ( + f"{all_alerts}Product name: {failed_subscription.product.name}\n" + f"Description: {failed_subscription.description}\n" + ) + all_alerts = ( + f'{all_alerts}The step "{failure["last_step"]}" failed for the following reason: ' + f'"{failure["failed_reason"]}".\n\nPlease inspect the full workflow at the following link: ' + f'{failed_task_url}.\n\n' + ) + + send_mail( + f"GAP {general_settings.environment} environment - One or more prefix lists have diverged!", + ( + f"Please check the following tasks in GAP which have failed.\n\n{all_alerts}------" + f"\n\nRegards, the GÉANT Automation Platform.\n\n" + ), + ) + + @workflow("Send email notifications for all failed tasks", target=Target.SYSTEM) def task_send_email_notifications() -> StepList: """Gather all failed tasks, and send an email notification if needed.""" tasks_have_failed = conditional(lambda state: len(state["failed_tasks"]) > 0) + prefix_tasks_have_failed = conditional(lambda state: len(state["failed_prefix_list_checks"]) > 0) - return init >> gather_failed_tasks >> tasks_have_failed(send_email_notifications) >> done + return ( + init + >> gather_failed_tasks + >> tasks_have_failed(send_email_notification) + >> prefix_tasks_have_failed(send_prefix_list_email_notification) + >> done + ) diff --git a/gso/workflows/vrf/create_vrf.py b/gso/workflows/vrf/create_vrf.py index 8844ea0c0d3911c406446a25268882f383b2b830..29b46a561e3de85676e6daf4c32600c79e3b4e73 100644 --- a/gso/workflows/vrf/create_vrf.py +++ b/gso/workflows/vrf/create_vrf.py @@ -2,11 +2,12 @@ from orchestrator.forms import FormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done, step, workflow from orchestrator.workflows.steps import resync, set_status, store_process_subscription from orchestrator.workflows.utils import wrap_create_initial_input_form from pydantic import ConfigDict +from pydantic_forms.types import FormGenerator, State, UUIDstr from pydantic_forms.validators import ReadOnlyField from gso.products.product_types.vrf import VRFInactive diff --git a/gso/workflows/vrf/modify_vrf_router_list.py b/gso/workflows/vrf/modify_vrf_router_list.py index 200912317a115ede0cd63a56147e3716d9da4cdf..1dde31b7cffb1373c46dd89c2bd8269a06fcaa21 100644 --- a/gso/workflows/vrf/modify_vrf_router_list.py +++ b/gso/workflows/vrf/modify_vrf_router_list.py @@ -4,11 +4,11 @@ from typing import Annotated, Any from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, UUIDstr from orchestrator.workflow import StepList, begin, done, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form from pydantic import AfterValidator, BaseModel, ConfigDict, Field +from pydantic_forms.types import FormGenerator, State, UUIDstr from pydantic_forms.validators import validate_unique_list from gso.products.product_types.router import Router diff --git a/gso/workflows/vrf/terminate_vrf.py b/gso/workflows/vrf/terminate_vrf.py index 1b985206b68f788219212ba3676ab245abd6a61d..1b1a6ea7ffd3ba5ce170547f64b7e0788a6b9305 100644 --- a/gso/workflows/vrf/terminate_vrf.py +++ b/gso/workflows/vrf/terminate_vrf.py @@ -5,7 +5,7 @@ from typing import Any from orchestrator.forms import SubmitFormPage from orchestrator.forms.validators import Label from orchestrator.targets import Target -from orchestrator.types import FormGenerator, SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done, workflow from orchestrator.workflows.steps import ( resync, @@ -15,6 +15,7 @@ from orchestrator.workflows.steps import ( ) from orchestrator.workflows.utils import wrap_modify_initial_input_form from pydantic import model_validator +from pydantic_forms.types import FormGenerator, UUIDstr from gso.products.product_types.vrf import VRF from gso.utils.types.tt_number import TTNumber diff --git a/pyproject.toml b/pyproject.toml index c31bcaefc059e0800ece9d21387068d25f74f887..0a81ec7ba50a8d6bc1baf2bb248900e4488bf312 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -119,6 +119,7 @@ filterwarnings = [ "ignore", "default:::gso", ] +asyncio_default_fixture_loop_scope = "function" [tool.coverage.run] omit = ["gso/migrations/*"] diff --git a/requirements.txt b/requirements.txt index a885793c4b4fece3e88a9d00e99129ab331e6719..f625624487ed20b459af5e53bc9c758ceafc7e05 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,30 +1,26 @@ -# Temporary hotfix while version 1.9.1 is still broken -microsoft-kiota-abstractions==1.9.0 - -orchestrator-core==2.8.0 +orchestrator-core==3.1.1 +graphql-core==3.2.* # TODO: this could probably get removed the next time orchestrator-core is upgraded requests==2.32.3 -infoblox-client~=0.6.0 -pycountry==23.12.11 -pynetbox==7.3.3 -celery-redbeat==2.2.0 -celery==5.3.6 -azure-identity==1.19.0 -msgraph-sdk==1.2.0 +infoblox-client==0.6.2 +pycountry==24.6.1 +pynetbox==7.4.1 +celery-redbeat==2.3.2 +celery==5.4.0 +azure-identity==1.21.0 +msgraph-sdk==1.24.0 ping3==4.0.8 unidecode==1.3.8 # Test and linting dependencies celery-stubs==0.1.3 -types-requests==2.31.0.20240406 -types-PyYAML==6.0.12.20240311 -pytest==8.1.1 -faker==24.8.0 -responses==0.25.0 -mypy==1.9.0 +types-requests==2.32.0.20250306 +types-PyYAML==6.0.12.20241230 +pytest==8.3.5 +faker==37.0.0 +responses==0.25.7 +mypy==1.15.0 ruff==0.3.5 -sphinx==7.2.6 -sphinx-rtd-theme==2.0.0 urllib3_mock==0.3.3 -pytest-asyncio==0.23.6 -pre-commit~=3.7.0 +pytest-asyncio==0.25.3 +pre-commit==4.1.0 pytest-xdist==3.6.1 diff --git a/setup.py b/setup.py index 6815cae22a25215df58cf854c2861bf975d504e1..18d09e620cd17a274e1388c54e7fa9cdfcf9d8f6 100644 --- a/setup.py +++ b/setup.py @@ -4,25 +4,25 @@ from setuptools import find_packages, setup setup( name="geant-service-orchestrator", - version="2.41", + version="2.42", author="GÉANT Orchestration and Automation Team", author_email="goat@geant.org", description="GÉANT Service Orchestrator", url="https://gitlab.software.geant.org/goat/gap/geant-service-orchestrator", packages=find_packages(), install_requires=[ - "orchestrator-core==2.8.0", - "requests==2.31.0", - "infoblox-client~=0.6.0", - "pycountry==23.12.11", - "pynetbox==7.3.3", - "celery-redbeat==2.2.0", - "celery==5.3.6", - "azure-identity==1.16.0", - "msgraph-sdk==1.2.0", + "orchestrator-core==3.1.1", + "graphql-core==3.2.*", # TODO: this could probably get removed the next time orchestrator-core is upgraded + "requests==2.32.3", + "infoblox-client==0.6.2", + "pycountry==24.6.1", + "pynetbox==7.4.1", + "celery-redbeat==2.3.2", + "celery==5.4.0", + "azure-identity==1.21.0", + "msgraph-sdk==1.24.0", "ping3==4.0.8", "unidecode==1.3.8", - "microsoft-kiota-abstractions==1.9.0", ], include_package_data=True, ) diff --git a/test/cli/test_imports.py b/test/cli/test_imports.py index 39c9f31efbdf205afba48cc908096d172a67c4c9..bc882410526e73dde39025e5da0adb91bd8881a5 100644 --- a/test/cli/test_imports.py +++ b/test/cli/test_imports.py @@ -554,12 +554,14 @@ def test_import_iptrunk_invalid_router_id_side_a_and_b(mock_start_process, mock_ import_iptrunks(broken_data["path"]) captured_output, _ = capfd.readouterr() + assert "Validation error: 2 validation errors for IptrunkImportModel" in captured_output + assert ( + """side_a_node_id + Value error, Router not found [type=value_error, input_value='', input_type=str]""" + in captured_output + ) assert ( - """Validation error: 2 validation errors for IptrunkImportModel -side_a_node_id - Value error, Router not found [type=value_error, input_value='', input_type=str] - For further information visit https://errors.pydantic.dev/2.7/v/value_error -side_b_node_id + """side_b_node_id Value error, Router not found [type=value_error, input_value='', input_type=str]""" in captured_output ) diff --git a/test/conftest.py b/test/conftest.py index 731e0a3c1894d10f73a29cbbc4f6c79ec045108a..233a0921ef67d5b1a58c14619ece9a8b1fb9c129 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -25,7 +25,8 @@ from orchestrator.db import ( from orchestrator.db.database import ENGINE_ARGUMENTS, SESSION_ARGUMENTS, BaseModel from orchestrator.domain import SUBSCRIPTION_MODEL_REGISTRY, SubscriptionModel from orchestrator.domain.base import ProductBlockModel -from orchestrator.types import SubscriptionLifecycle, strEnum +from orchestrator.types import SubscriptionLifecycle +from pydantic_forms.types import strEnum from sqlalchemy import create_engine, select, text from sqlalchemy.engine import make_url from sqlalchemy.orm import scoped_session, sessionmaker @@ -168,9 +169,9 @@ def db_uri(): database_host = os.getenv("DATABASE_HOST", "localhost") if worker_id: - return f"postgresql://nwa:nwa@{database_host}/gso-test-db_{worker_id}" + return f"postgresql+psycopg://nwa:nwa@{database_host}/gso-test-db_{worker_id}" - return os.environ.get("DATABASE_URI_TEST", f"postgresql://nwa:nwa@{database_host}/gso-test-db") + return os.environ.get("DATABASE_URI_TEST", f"postgresql+psycopg://nwa:nwa@{database_host}/gso-test-db") def run_migrations(db_uri: str) -> None: @@ -211,15 +212,14 @@ def _database(db_uri): engine = create_engine(url) with engine.connect() as conn: - conn.execute(text("COMMIT;")) conn.execute( text("SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname=:db_name").bindparams( db_name=db_to_create, ), ) - - conn.execute(text(f'DROP DATABASE IF EXISTS "{db_to_create}";')) - conn.execute(text("COMMIT;")) + conn.commit() + conn.execution_options(isolation_level="AUTOCOMMIT").execute(text(f'DROP DATABASE IF EXISTS "{db_to_create}";')) + conn.commit() conn.execute(text(f'CREATE DATABASE "{db_to_create}";')) run_migrations(db_uri) @@ -230,12 +230,13 @@ def _database(db_uri): finally: db.wrapped_database.engine.dispose() with engine.connect() as conn: - conn.execute(text("COMMIT;")) - # Terminate all connections to the database conn.execute( text(f"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='{db_to_create}';"), # noqa: S608 ) - conn.execute(text(f'DROP DATABASE IF EXISTS "{db_to_create}";')) + conn.commit() + conn.execution_options(isolation_level="AUTOCOMMIT").execute( + text(f'DROP DATABASE IF EXISTS "{db_to_create}";') + ) @pytest.fixture(autouse=True) diff --git a/test/fixtures.py b/test/fixtures.py index 4d503bf11c4cb639ba97cf4e0a9a94f8232e2a55..6687d05e1695bfc821cb6a31abb5b331f3a7fad3 100644 --- a/test/fixtures.py +++ b/test/fixtures.py @@ -6,9 +6,8 @@ import pytest from orchestrator import step, workflow from orchestrator.config.assignee import Assignee from orchestrator.forms import SubmitFormPage -from orchestrator.types import UUIDstr from orchestrator.workflow import done, init, inputstep -from pydantic_forms.types import FormGenerator +from pydantic_forms.types import FormGenerator, UUIDstr from pydantic_forms.validators import Choice from test.workflows import WorkflowInstanceForTests diff --git a/test/fixtures/lan_switch_interconnect_fixtures.py b/test/fixtures/lan_switch_interconnect_fixtures.py index 40f95374d3cb04da15995e7ab7601eaadc51a6a4..3879441ee8390cc6d2ee0b7af8a6b11ae8195e5e 100644 --- a/test/fixtures/lan_switch_interconnect_fixtures.py +++ b/test/fixtures/lan_switch_interconnect_fixtures.py @@ -3,7 +3,8 @@ from uuid import uuid4 import pytest from orchestrator.db import db from orchestrator.domain import SubscriptionModel -from orchestrator.types import SubscriptionLifecycle, UUIDstr +from orchestrator.types import SubscriptionLifecycle +from pydantic_forms.types import UUIDstr from gso.products import ProductName from gso.products.product_blocks.lan_switch_interconnect import ( diff --git a/test/services/subscriptions.py b/test/services/test_subscriptions.py similarity index 100% rename from test/services/subscriptions.py rename to test/services/test_subscriptions.py diff --git a/test/workflows/__init__.py b/test/workflows/__init__.py index 4edb847047b22ed4e4201836054a25606cb0f8cc..33a0e3f1a97f07f0c2e9ef27d84951f4fbd7ee8f 100644 --- a/test/workflows/__init__.py +++ b/test/workflows/__init__.py @@ -8,12 +8,12 @@ from uuid import uuid4 import structlog from orchestrator.db import ProcessTable, WorkflowTable, db from orchestrator.services.processes import StateMerger, _db_create_process -from orchestrator.types import State from orchestrator.utils.json import json_dumps, json_loads from orchestrator.workflow import Process, ProcessStat, Step, Success, Workflow, runwf from orchestrator.workflow import Process as WFProcess from orchestrator.workflows import ALL_WORKFLOWS, LazyWorkflowInstance, get_workflow from pydantic_forms.core import post_form +from pydantic_forms.types import State from test import LSO_RESULT_FAILURE, LSO_RESULT_SUCCESS, USER_CONFIRM_EMPTY_FORM diff --git a/test/workflows/l3_core_service/test_deploy_prefix_list.py b/test/workflows/l3_core_service/test_deploy_prefix_list.py new file mode 100644 index 0000000000000000000000000000000000000000..4935a353b1f0b885338d2bc0b8f0f82e8e3e2f87 --- /dev/null +++ b/test/workflows/l3_core_service/test_deploy_prefix_list.py @@ -0,0 +1,27 @@ +from unittest.mock import patch + +import pytest + +from gso.products.product_types.l3_core_service import L3_CORE_SERVICE_TYPES, L3CoreService +from test.workflows import assert_complete, assert_lso_interaction_success, extract_state, run_workflow + + +@pytest.mark.workflow() +@patch("gso.services.lso_client._send_request") +@pytest.mark.parametrize("l3_core_service_type", L3_CORE_SERVICE_TYPES) +def test_deploy_prefix_list(mock_lso_interaction, l3_core_service_subscription_factory, faker, l3_core_service_type): + subscription_id = str( + l3_core_service_subscription_factory(l3_core_service_type=l3_core_service_type).subscription_id + ) + initial_l3_core_service_data = [{"subscription_id": subscription_id}] + result, process_stat, step_log = run_workflow("deploy_prefix_list", initial_l3_core_service_data) + result, step_log = assert_lso_interaction_success(result, process_stat, step_log) + result, _ = assert_lso_interaction_success(result, process_stat, step_log) + assert_complete(result) + + state = extract_state(result) + subscription_id = state["subscription_id"] + subscription = L3CoreService.from_subscription(subscription_id) + assert subscription.status == "active" + assert subscription.insync is True + assert mock_lso_interaction.call_count == 2 diff --git a/test/workflows/l3_core_service/test_modify_l3_core_service.py b/test/workflows/l3_core_service/test_modify_l3_core_service.py index 75cfe87b40114c0554626d6c7682c0a853fda72f..ebcf99339de1391eaaf3f24fc6d90ae74e296756 100644 --- a/test/workflows/l3_core_service/test_modify_l3_core_service.py +++ b/test/workflows/l3_core_service/test_modify_l3_core_service.py @@ -70,7 +70,9 @@ def test_modify_l3_core_service_add_new_edge_port_success( "gs_id": faker.gs_id(), "vlan_id": faker.vlan_id(), "ipv4_address": faker.ipv4(), + "ipv4_mask": faker.ipv4_netmask(), "ipv6_address": faker.ipv6(), + "ipv6_mask": faker.ipv6_netmask(), "v4_bgp_peer": { "authentication_key": faker.password(), "peer_address": faker.ipv4(), @@ -91,6 +93,14 @@ def test_modify_l3_core_service_add_new_edge_port_success( state = extract_state(result) subscription = L3CoreService.from_subscription(state["subscription_id"]) + new_ap = subscription.l3_core_service.ap_list[-1] + assert new_ap.ap_type == APType.BACKUP + assert new_ap.sbp.gs_id == input_form_data[4]["gs_id"] + assert new_ap.sbp.vlan_id == input_form_data[4]["vlan_id"] + assert str(new_ap.sbp.ipv4_address) == input_form_data[4]["ipv4_address"] + assert new_ap.sbp.ipv4_mask == input_form_data[4]["ipv4_mask"] + assert str(new_ap.sbp.ipv6_address) == input_form_data[4]["ipv6_address"] + assert new_ap.sbp.ipv6_mask == input_form_data[4]["ipv6_mask"] assert len(subscription.l3_core_service.ap_list) == 3 @@ -102,7 +112,9 @@ def sbp_input_form_data(faker): "is_tagged": True, "vlan_id": faker.vlan_id(), "ipv4_address": faker.ipv4(), + "ipv4_mask": faker.ipv4_netmask(), "ipv6_address": faker.ipv6(), + "ipv6_mask": faker.ipv6_netmask(), "custom_firewall_filters": True, "v4_bfd_settings": { "bfd_enabled": True, @@ -175,7 +187,9 @@ def test_modify_l3_core_service_modify_edge_port_success( assert subscription.l3_core_service.ap_list[i].sbp.is_tagged == new_sbp_data[i]["is_tagged"] assert subscription.l3_core_service.ap_list[i].sbp.vlan_id == new_sbp_data[i]["vlan_id"] assert str(subscription.l3_core_service.ap_list[i].sbp.ipv4_address) == new_sbp_data[i]["ipv4_address"] + assert subscription.l3_core_service.ap_list[i].sbp.ipv4_mask == new_sbp_data[i]["ipv4_mask"] assert str(subscription.l3_core_service.ap_list[i].sbp.ipv6_address) == new_sbp_data[i]["ipv6_address"] + assert subscription.l3_core_service.ap_list[i].sbp.ipv6_mask == new_sbp_data[i]["ipv6_mask"] assert ( subscription.l3_core_service.ap_list[i].sbp.custom_firewall_filters == new_sbp_data[i]["custom_firewall_filters"] diff --git a/test/workflows/l3_core_service/test_validate_prefix_list.py b/test/workflows/l3_core_service/test_validate_prefix_list.py new file mode 100644 index 0000000000000000000000000000000000000000..3e19deee5076c9506082b2e342cc595ae67faff5 --- /dev/null +++ b/test/workflows/l3_core_service/test_validate_prefix_list.py @@ -0,0 +1,26 @@ +from unittest.mock import patch + +import pytest + +from gso.products.product_types.l3_core_service import L3_CORE_SERVICE_TYPES, L3CoreService +from test.workflows import assert_complete, assert_lso_success, extract_state, run_workflow + + +@pytest.mark.workflow() +@patch("gso.services.lso_client._send_request") +@pytest.mark.parametrize("l3_core_service_type", L3_CORE_SERVICE_TYPES) +def test_validate_prefix_list(mock_lso_interaction, l3_core_service_subscription_factory, faker, l3_core_service_type): + subscription_id = str( + l3_core_service_subscription_factory(l3_core_service_type=l3_core_service_type).subscription_id + ) + initial_l3_core_service_data = [{"subscription_id": subscription_id}] + result, process_stat, step_log = run_workflow("validate_prefix_list", initial_l3_core_service_data) + result, step_log = assert_lso_success(result, process_stat, step_log) + assert_complete(result) + + state = extract_state(result) + subscription_id = state["subscription_id"] + subscription = L3CoreService.from_subscription(subscription_id) + assert subscription.status == "active" + assert subscription.insync is True + assert mock_lso_interaction.call_count == 1 diff --git a/test/workflows/router/test_promote_p_to_pe.py b/test/workflows/router/test_promote_p_to_pe.py index 71ffd639224094a08815f186bcbedba7a4d309ce..46ab40b7fa7215de2b94bbfd3980c5bea0506268 100644 --- a/test/workflows/router/test_promote_p_to_pe.py +++ b/test/workflows/router/test_promote_p_to_pe.py @@ -92,7 +92,7 @@ def test_promote_p_to_pe_missing_tt_number(router_subscription_factory): with pytest.raises(FormValidationError) as error: run_workflow("promote_p_to_pe", [{"subscription_id": router_id}, {}]) error = error.value.errors[0] - assert error["msg"] == "Field required" + assert error["msg"] == "field required" assert error["loc"][0] == "tt_number"