diff --git a/Changelog.md b/Changelog.md index caa750310b00321a244f49e80f2a236f51d27bc5..a696905e59c451aed5443afc340c8208a8071340 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,17 @@ All notable changes to this project will be documented in this file. +## [1.5] - 2024-04-22 +- Added deploy TWAMP functionality. +- Made some changes on IP Trunk creation/modification workflow including: + - Replaced `minimum link` with `number of members` in the IP trunks workflows. + - Added a confirmation page for users to confirm the `minimum links` value. + - Made `interface description` optional. + - Made `GA-GID` and `GA-SID` optional. +- Added a rule to OPA policies to give access to Inventory Provider. +- Improved the codebase to include OIDC client ID in userinfo. +- Added switch, LAN switch interconnect and Pop VLAN domain models. + ## [1.1] - 2024-04-04 - Fixed the AttributeError in the migrate_iptrunk workflow. - Improved the delete device in Netbox functionality. diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst index 4844b8049b2fff9c98561ac6e9f56b92a63ef4ae..4dc66b8eac5e2e408f220c93b8c4be80cf8e3301 100644 --- a/docs/source/glossary.rst +++ b/docs/source/glossary.rst @@ -81,3 +81,6 @@ Glossary of terms WFO `Workflow Orchestrator <https://workfloworchestrator.org/>`_ + + LAN + Local Area Network diff --git a/gso/api/v1/imports.py b/gso/api/v1/imports.py index 0b2b6b1624c06acb657cca57ebb03a0817972cf0..643b4bc09a8295a35ca1b6f4ee1bcc08a978371e 100644 --- a/gso/api/v1/imports.py +++ b/gso/api/v1/imports.py @@ -62,7 +62,7 @@ class IptrunkImportModel(BaseModel): """Required fields for importing an existing :class:`gso.products.product_types.iptrunk`.""" partner: str - geant_s_sid: str + geant_s_sid: str | None iptrunk_type: IptrunkType iptrunk_description: str iptrunk_speed: PhysicalPortCapacity @@ -70,11 +70,11 @@ class IptrunkImportModel(BaseModel): iptrunk_isis_metric: int side_a_node_id: str side_a_ae_iface: str - side_a_ae_geant_a_sid: str + side_a_ae_geant_a_sid: str | None side_a_ae_members: list[LAGMember] side_b_node_id: str side_b_ae_iface: str - side_b_ae_geant_a_sid: str + side_b_ae_geant_a_sid: str | None side_b_ae_members: list[LAGMember] iptrunk_ipv4_network: ipaddress.IPv4Network diff --git a/gso/auth/oidc_policy_helper.py b/gso/auth/oidc_policy_helper.py index 241641dcfe3a93ffd41bb2962255c6557b95d235..1ba2eb3b8cdb3db63babe30beabf2c6186e3ae3c 100644 --- a/gso/auth/oidc_policy_helper.py +++ b/gso/auth/oidc_policy_helper.py @@ -92,6 +92,11 @@ class OIDCUserModel(dict): return self.get(key) raise error from None + @property + def client_id(self) -> str: + """Return the client id.""" + return self.get("client_id") or "" + @property def user_name(self) -> str: """Return the username of the user.""" @@ -236,6 +241,8 @@ class OIDCUser(HTTPBearer): user_info = await self.userinfo(async_request, token) + user_info["client_id"] = intercepted_token.get("client_id") + logger.debug("OIDCUserModel object.", intercepted_token=intercepted_token) return user_info @@ -418,7 +425,7 @@ def opa_decision( **(opa_kwargs or {}), **user_info, "resource": request.url.path, - "method": request_method, + "method": request_method.upper(), "arguments": {"path": request.path_params, "query": {**request.query_params}, "json": json}, } } diff --git a/gso/migrations/versions/2024-04-17_393acfa175c0_add_switch_products.py b/gso/migrations/versions/2024-04-17_393acfa175c0_add_switch_products.py new file mode 100644 index 0000000000000000000000000000000000000000..7444c84530978213d33a8fc8ec255f1b70c5c8c5 --- /dev/null +++ b/gso/migrations/versions/2024-04-17_393acfa175c0_add_switch_products.py @@ -0,0 +1,356 @@ +"""Add switch products.. + +Revision ID: 393acfa175c0 +Revises: +Create Date: 2024-04-17 15:09:13.716522 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = '393acfa175c0' +down_revision = None +branch_labels = ('data',) +depends_on = '4ec89ab289c0' + + +def upgrade() -> None: + conn = op.get_bind() + conn.execute(sa.text(""" +INSERT INTO products (name, description, product_type, tag, status) VALUES ('Switch', 'Switch', 'Switch', 'SWITCH', 'active') RETURNING products.product_id + """)) + conn.execute(sa.text(""" +INSERT INTO products (name, description, product_type, tag, status) VALUES ('LAN Switch Interconnect', 'LAN Switch Interconnect', 'LanSwitchInterconnect', 'LSI', 'active') RETURNING products.product_id + """)) + conn.execute(sa.text(""" +INSERT INTO products (name, description, product_type, tag, status) VALUES ('Pop VLAN', 'Pop VLAN', 'PopVlan', 'POP_VLAN', 'active') RETURNING products.product_id + """)) + conn.execute(sa.text(""" +INSERT INTO product_blocks (name, description, tag, status) VALUES ('SwitchBlock', 'SwitchBlock', 'SWITCH_BLOCK', 'active') RETURNING product_blocks.product_block_id + """)) + conn.execute(sa.text(""" +INSERT INTO product_blocks (name, description, tag, status) VALUES ('LanSwitchInterconnectBlock', 'LanSwitchInterconnectBlock', 'LSI_BLOCK', 'active') RETURNING product_blocks.product_block_id + """)) + conn.execute(sa.text(""" +INSERT INTO product_blocks (name, description, tag, status) VALUES ('LanSwitchInterconnectRouterSideBlock', 'LanSwitchInterconnectRouterSideBlock', 'LSI_RTR_SIDE_BLOCK', 'active') RETURNING product_blocks.product_block_id + """)) + conn.execute(sa.text(""" +INSERT INTO product_blocks (name, description, tag, status) VALUES ('LanSwitchInterconnectSwitchSideBlock', 'LanSwitchInterconnectSwitchSideBlock', 'LSI_SW_SIDE_BLOCK', 'active') RETURNING product_blocks.product_block_id + """)) + conn.execute(sa.text(""" +INSERT INTO product_blocks (name, description, tag, status) VALUES ('LanSwitchInterconnectInterfaceBlock', 'LanSwitchInterconnectInterfaceBlock', 'LSI_IFACE_BLOCK', 'active') RETURNING product_blocks.product_block_id + """)) + conn.execute(sa.text(""" +INSERT INTO product_blocks (name, description, tag, status) VALUES ('PopVlanBlock', 'PopVlanBlock', 'POP_VLAN_BLOCK', 'active') RETURNING product_blocks.product_block_id + """)) + conn.execute(sa.text(""" +INSERT INTO product_blocks (name, description, tag, status) VALUES ('PopVlanPortBlock', 'PopVlanPortBlock', 'POP_VLAN_PORT_BLOCK', 'active') RETURNING product_blocks.product_block_id + """)) + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('switch_vendor', 'The vendor of the switch.') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('vlan_id', 'The VLAN ID of the Pop VLAN.') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('switch_model', 'The model of the switch.') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('lan_switch_interconnect_description', 'Description') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('address_space', 'The address space of the VLAN Switch Interconnect. It can be private or public.') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('switch_hostname', 'The hostname of the swirch') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('ipv4_network', 'IPv4 network for the Pop VLAN if layer_preference is L3.') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('port_name', 'The port name') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('layer_preference', 'L2/L3') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('ae_iface', 'AE interface') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('ipv6_network', 'IPv6 network for the Pop VLAN if layer_preference is L3.') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('port_description', 'The port description') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('minimum_links', 'The minimum amount of links') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('switch_ts_port', 'The port of the terminal server that this switch is connected to. Used to offer out of band access.') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('tagged', 'If the port is tagged') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('pop_vlan_description', 'POP VLAN description') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO product_product_blocks (product_id, product_block_id) VALUES ((SELECT products.product_id FROM products WHERE products.name IN ('Switch')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SwitchBlock'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_product_blocks (product_id, product_block_id) VALUES ((SELECT products.product_id FROM products WHERE products.name IN ('LAN Switch Interconnect')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_product_blocks (product_id, product_block_id) VALUES ((SELECT products.product_id FROM products WHERE products.name IN ('Pop VLAN')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanBlock'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_relations (in_use_by_id, depends_on_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SwitchBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_relations (in_use_by_id, depends_on_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectRouterSideBlock'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_relations (in_use_by_id, depends_on_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectSwitchSideBlock'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_relations (in_use_by_id, depends_on_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectRouterSideBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('RouterBlock'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_relations (in_use_by_id, depends_on_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectSwitchSideBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectInterfaceBlock'))), ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectRouterSideBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectInterfaceBlock'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_relations (in_use_by_id, depends_on_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectSwitchSideBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SwitchBlock'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_relations (in_use_by_id, depends_on_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_relations (in_use_by_id, depends_on_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanPortBlock'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SwitchBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('switch_hostname'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SwitchBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('switch_ts_port'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SwitchBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('switch_vendor'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SwitchBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('switch_model'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('lan_switch_interconnect_description'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('address_space'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('minimum_links'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectSwitchSideBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ae_iface'))), ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectRouterSideBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ae_iface'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectInterfaceBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('interface_name'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectInterfaceBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('interface_description'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('vlan_id'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('pop_vlan_description'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('layer_preference'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ipv4_network'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ipv6_network'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanPortBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('port_name'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanPortBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('port_description'))) + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanPortBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('tagged'))) + """)) + + +def downgrade() -> None: + conn = op.get_bind() + conn.execute(sa.text(""" +DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SwitchBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('switch_hostname')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SwitchBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('switch_hostname')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SwitchBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('switch_ts_port')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SwitchBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('switch_ts_port')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SwitchBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('switch_vendor')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SwitchBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('switch_vendor')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SwitchBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('switch_model')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SwitchBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('switch_model')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('lan_switch_interconnect_description')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('lan_switch_interconnect_description')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('address_space')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('address_space')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('minimum_links')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('minimum_links')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectSwitchSideBlock', 'LanSwitchInterconnectRouterSideBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ae_iface')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectSwitchSideBlock', 'LanSwitchInterconnectRouterSideBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ae_iface')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectInterfaceBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('interface_name')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectInterfaceBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('interface_name')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectInterfaceBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('interface_description')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectInterfaceBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('interface_description')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('vlan_id')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('vlan_id')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('pop_vlan_description')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('pop_vlan_description')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('layer_preference')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('layer_preference')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ipv4_network')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ipv4_network')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ipv6_network')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ipv6_network')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanPortBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('port_name')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanPortBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('port_name')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanPortBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('port_description')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanPortBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('port_description')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanPortBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('tagged')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanPortBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('tagged')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values WHERE subscription_instance_values.resource_type_id IN (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('switch_vendor', 'vlan_id', 'switch_model', 'lan_switch_interconnect_description', 'address_space', 'switch_hostname', 'ipv4_network', 'port_name', 'layer_preference', 'ae_iface', 'ipv6_network', 'port_description', 'minimum_links', 'switch_ts_port', 'tagged', 'pop_vlan_description')) + """)) + conn.execute(sa.text(""" +DELETE FROM resource_types WHERE resource_types.resource_type IN ('switch_vendor', 'vlan_id', 'switch_model', 'lan_switch_interconnect_description', 'address_space', 'switch_hostname', 'ipv4_network', 'port_name', 'layer_preference', 'ae_iface', 'ipv6_network', 'port_description', 'minimum_links', 'switch_ts_port', 'tagged', 'pop_vlan_description') + """)) + conn.execute(sa.text(""" +DELETE FROM product_product_blocks WHERE product_product_blocks.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Switch')) AND product_product_blocks.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SwitchBlock')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_product_blocks WHERE product_product_blocks.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('LAN Switch Interconnect')) AND product_product_blocks.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_product_blocks WHERE product_product_blocks.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Pop VLAN')) AND product_product_blocks.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanBlock')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_relations WHERE product_block_relations.in_use_by_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SwitchBlock')) AND product_block_relations.depends_on_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_relations WHERE product_block_relations.in_use_by_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock')) AND product_block_relations.depends_on_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectRouterSideBlock')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_relations WHERE product_block_relations.in_use_by_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock')) AND product_block_relations.depends_on_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectSwitchSideBlock')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_relations WHERE product_block_relations.in_use_by_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectRouterSideBlock')) AND product_block_relations.depends_on_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('RouterBlock')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_relations WHERE product_block_relations.in_use_by_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectSwitchSideBlock', 'LanSwitchInterconnectRouterSideBlock')) AND product_block_relations.depends_on_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectInterfaceBlock')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_relations WHERE product_block_relations.in_use_by_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectSwitchSideBlock')) AND product_block_relations.depends_on_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SwitchBlock')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_relations WHERE product_block_relations.in_use_by_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanBlock')) AND product_block_relations.depends_on_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectBlock')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_relations WHERE product_block_relations.in_use_by_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanBlock')) AND product_block_relations.depends_on_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PopVlanPortBlock')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instances WHERE subscription_instances.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectRouterSideBlock', 'LanSwitchInterconnectInterfaceBlock', 'SwitchBlock', 'PopVlanPortBlock', 'LanSwitchInterconnectBlock', 'LanSwitchInterconnectSwitchSideBlock', 'PopVlanBlock')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_blocks WHERE product_blocks.name IN ('LanSwitchInterconnectRouterSideBlock', 'LanSwitchInterconnectInterfaceBlock', 'SwitchBlock', 'PopVlanPortBlock', 'LanSwitchInterconnectBlock', 'LanSwitchInterconnectSwitchSideBlock', 'PopVlanBlock') + """)) + conn.execute(sa.text(""" +DELETE FROM processes WHERE processes.pid IN (SELECT processes_subscriptions.pid FROM processes_subscriptions WHERE processes_subscriptions.subscription_id IN (SELECT subscriptions.subscription_id FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Switch', 'LAN Switch Interconnect', 'Pop VLAN')))) + """)) + conn.execute(sa.text(""" +DELETE FROM processes_subscriptions WHERE processes_subscriptions.subscription_id IN (SELECT subscriptions.subscription_id FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Switch', 'LAN Switch Interconnect', 'Pop VLAN'))) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instances WHERE subscription_instances.subscription_id IN (SELECT subscriptions.subscription_id FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Switch', 'LAN Switch Interconnect', 'Pop VLAN'))) + """)) + conn.execute(sa.text(""" +DELETE FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Switch', 'LAN Switch Interconnect', 'Pop VLAN')) + """)) + conn.execute(sa.text(""" +DELETE FROM products WHERE products.name IN ('Switch', 'LAN Switch Interconnect', 'Pop VLAN') + """)) diff --git a/gso/products/__init__.py b/gso/products/__init__.py index 32350d65cc3a9b0e92e4757ffc8a7faf632f7379..cab8e801f207c7617c2e204cb9fdde0aa0d5a366 100644 --- a/gso/products/__init__.py +++ b/gso/products/__init__.py @@ -9,10 +9,13 @@ from orchestrator.domain import SUBSCRIPTION_MODEL_REGISTRY from pydantic_forms.types import strEnum from gso.products.product_types.iptrunk import Iptrunk +from gso.products.product_types.lan_switch_interconnect import LanSwitchInterconnect from gso.products.product_types.office_router import OfficeRouter +from gso.products.product_types.pop_vlan import PopVlan from gso.products.product_types.router import Router from gso.products.product_types.site import Site from gso.products.product_types.super_pop_switch import SuperPopSwitch +from gso.products.product_types.switch import Switch class ProductName(strEnum): @@ -23,6 +26,9 @@ class ProductName(strEnum): SITE = "Site" SUPER_POP_SWITCH = "Super PoP switch" OFFICE_ROUTER = "Office router" + SWITCH = "Switch" + LAN_SWITCH_INTERCONNECT = "LAN Switch Interconnect" + POP_VLAN = "Pop VLAN" class ProductType(strEnum): @@ -33,6 +39,9 @@ class ProductType(strEnum): SITE = Site.__name__ SUPER_POP_SWITCH = SuperPopSwitch.__name__ OFFICE_ROUTER = OfficeRouter.__name__ + SWITCH = Switch.__name__ + LAN_SWITCH_INTERCONNECT = LanSwitchInterconnect.__name__ + POP_VLAN = PopVlan.__name__ SUBSCRIPTION_MODEL_REGISTRY.update( @@ -42,5 +51,8 @@ SUBSCRIPTION_MODEL_REGISTRY.update( ProductName.SITE.value: Site, ProductName.SUPER_POP_SWITCH.value: SuperPopSwitch, ProductName.OFFICE_ROUTER.value: OfficeRouter, + ProductName.SWITCH.value: Switch, + ProductName.LAN_SWITCH_INTERCONNECT.value: LanSwitchInterconnect, + ProductName.POP_VLAN.value: PopVlan, }, ) diff --git a/gso/products/product_blocks/iptrunk.py b/gso/products/product_blocks/iptrunk.py index 901f37e787805e68307fc598a95080b0e6cdbd08..cac186262f641fc52fdc62a4e4c556c7d924f22b 100644 --- a/gso/products/product_blocks/iptrunk.py +++ b/gso/products/product_blocks/iptrunk.py @@ -55,14 +55,14 @@ class IptrunkInterfaceBlockProvisioning(IptrunkInterfaceBlockInactive, lifecycle """An IP trunk interface that is being provisioned.""" interface_name: str - interface_description: str + interface_description: str | None = None class IptrunkInterfaceBlock(IptrunkInterfaceBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]): """An active IP trunk interface.""" interface_name: str - interface_description: str + interface_description: str | None = None class IptrunkSides(UniqueConstrainedList[T_co]): # type: ignore[type-var] @@ -139,7 +139,7 @@ class IptrunkBlock(IptrunkBlockProvisioning, lifecycle=[SubscriptionLifecycle.AC """A trunk that's currently deployed in the network.""" #: GÉANT service ID associated with this trunk. - geant_s_sid: str + geant_s_sid: str | None = None #: A human-readable description of this trunk. iptrunk_description: str #: The type of trunk, can be either dark fibre or leased capacity. diff --git a/gso/products/product_blocks/lan_switch_interconnect.py b/gso/products/product_blocks/lan_switch_interconnect.py new file mode 100644 index 0000000000000000000000000000000000000000..55a7b97a3ca788dbe3e61f5ddd9c8a5344e5976f --- /dev/null +++ b/gso/products/product_blocks/lan_switch_interconnect.py @@ -0,0 +1,149 @@ +"""LAN Switch Interconnect product block that has all parameters of a subscription throughout its lifecycle.""" + +from orchestrator.domain.base import ProductBlockModel +from orchestrator.types import SubscriptionLifecycle, strEnum + +from gso.products.product_blocks.iptrunk import LAGMemberList +from gso.products.product_blocks.router import RouterBlock, RouterBlockInactive, RouterBlockProvisioning +from gso.products.product_blocks.switch import SwitchBlock, SwitchBlockInactive, SwitchBlockProvisioning + + +class LanSwitchInterconnectAddressSpace(strEnum): + """Types of LAN Switch Interconnect. Can be private or public.""" + + PRIVATE = "Private" + PUBLIC = "Public" + + +class LanSwitchInterconnectInterfaceBlockInactive( + ProductBlockModel, + lifecycle=[SubscriptionLifecycle.INITIAL], + product_block_name="LanSwitchInterconnectInterfaceBlock", +): + """An inactive LAN Switch Interconnect interface.""" + + interface_name: str | None = None + interface_description: str | None = None + + +class LanSwitchInterconnectInterfaceBlockProvisioning( + LanSwitchInterconnectInterfaceBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING] +): + """A LAN Switch Interconnect interface that is being provisioned.""" + + interface_name: str + interface_description: str + + +class LanSwitchInterconnectInterfaceBlock( + LanSwitchInterconnectInterfaceBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE] +): + """An active Switch Interconnect interface.""" + + interface_name: str + interface_description: str + + +class LanSwitchInterconnectRouterSideBlockInactive( + ProductBlockModel, + lifecycle=[SubscriptionLifecycle.INITIAL], + product_block_name="LanSwitchInterconnectRouterSideBlock", +): + """An inactive LAN Switch Interconnect router side.""" + + node: RouterBlockInactive + ae_iface: str | None = None + ae_members: LAGMemberList[LanSwitchInterconnectInterfaceBlockInactive] + + +class LanSwitchInterconnectRouterSideBlockProvisioning( + LanSwitchInterconnectRouterSideBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING] +): + """An LAN Switch Interconnect router side that is being provisioned.""" + + node: RouterBlockProvisioning + ae_iface: str | None = None + ae_members: LAGMemberList[LanSwitchInterconnectInterfaceBlockProvisioning] + + +class LanSwitchInterconnectRouterSideBlock( + LanSwitchInterconnectRouterSideBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE] +): + """An active LAN Switch Interconnect router side.""" + + node: RouterBlock + ae_iface: str + ae_members: LAGMemberList[LanSwitchInterconnectInterfaceBlock] + + +class LanSwitchInterconnectSwitchSideBlockInactive( + ProductBlockModel, + lifecycle=[SubscriptionLifecycle.INITIAL], + product_block_name="LanSwitchInterconnectSwitchSideBlock", +): + """An inactive LAN Switch Interconnect switch side.""" + + node: SwitchBlockInactive + ae_iface: str | None = None + ae_members: LAGMemberList[LanSwitchInterconnectInterfaceBlockInactive] + + +class LanSwitchInterconnectSwitchSideBlockProvisioning( + LanSwitchInterconnectSwitchSideBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING] +): + """An LAN Switch Interconnect switch side that is being provisioned.""" + + node: SwitchBlockProvisioning + ae_iface: str | None = None + ae_members: LAGMemberList[LanSwitchInterconnectInterfaceBlockProvisioning] + + +class LanSwitchInterconnectSwitchSideBlock( + LanSwitchInterconnectSwitchSideBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE] +): + """An active LAN Switch Interconnect switch side.""" + + node: SwitchBlock + ae_iface: str + ae_members: LAGMemberList[LanSwitchInterconnectInterfaceBlock] + + +class LanSwitchInterconnectBlockInactive( + ProductBlockModel, + lifecycle=[SubscriptionLifecycle.INITIAL], + product_block_name="LanSwitchInterconnectBlock", +): + """A LAN Switch Interconnect that's currently inactive, see :class:`LanSwitchInterconnectBlock`.""" + + lan_switch_interconnect_description: str | None = None + address_space: LanSwitchInterconnectAddressSpace | None = None + minimum_links: int | None = None + router_side: LanSwitchInterconnectRouterSideBlockInactive + switch_side: LanSwitchInterconnectSwitchSideBlockInactive + + +class LanSwitchInterconnectBlockProvisioning( + LanSwitchInterconnectBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING] +): + """A LAN Switch Interconnect that's currently being provisioned, see :class:`LanSwitchInterconnectBlock`.""" + + lan_switch_interconnect_description: str | None = None + address_space: LanSwitchInterconnectAddressSpace | None = None + minimum_links: int | None = None + router_side: LanSwitchInterconnectRouterSideBlockProvisioning + switch_side: LanSwitchInterconnectSwitchSideBlockProvisioning + + +class LanSwitchInterconnectBlock(LanSwitchInterconnectBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]): + """A LAN Switch Interconnect that's currently deployed in the network.""" + + #: A human-readable description of this LAN Switch Interconnect. + lan_switch_interconnect_description: str + #: The address space of the VLAN Switch Interconnect. It can be private or public. + address_space: LanSwitchInterconnectAddressSpace + #: The minimum amount of links the LAN Switch Interconnect should consist of. + minimum_links: int + #: The router side of the LAN Switch Interconnect. + router_side: LanSwitchInterconnectRouterSideBlock + #: The switch side of the LAN Switch Interconnect. + switch_side: LanSwitchInterconnectSwitchSideBlock diff --git a/gso/products/product_blocks/pop_vlan.py b/gso/products/product_blocks/pop_vlan.py new file mode 100644 index 0000000000000000000000000000000000000000..4935c2f69966874c686489c7e06e3061d5509365 --- /dev/null +++ b/gso/products/product_blocks/pop_vlan.py @@ -0,0 +1,101 @@ +"""Pop VLAN product block that has all parameters of a subscription throughout its lifecycle.""" + +from ipaddress import IPv4Network, IPv6Network +from typing import TypeVar + +from orchestrator.domain.base import ProductBlockModel +from orchestrator.forms.validators import UniqueConstrainedList +from orchestrator.types import SubscriptionLifecycle +from pydantic_forms.types import strEnum + +from gso.products.product_blocks.lan_switch_interconnect import ( + LanSwitchInterconnectBlock, + LanSwitchInterconnectBlockInactive, + LanSwitchInterconnectBlockProvisioning, +) + +T_co = TypeVar("T_co", covariant=True) + + +class LayerPreference(strEnum): + """Enumerator for the different types of layer preferences.""" + + L2 = "L2" + L3 = "L3" + + +class PortList(UniqueConstrainedList[T_co]): # type: ignore[type-var] + """A list of ports.""" + + +class PopVlanPortBlockInactive( + ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="PopVlanPortBlock" +): + """An inactive Pop VLAN port.""" + + port_name: str | None = None + port_description: str | None = None + tagged: bool | None = None + + +class PopVlanPortBlockProvisioning(PopVlanPortBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]): + """A Pop VLAN port that is being provisioned.""" + + port_name: str | None = None + port_description: str | None = None + tagged: bool | None = None + + +class PopVlanPortBlock(PopVlanPortBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]): + """An active Pop VLAN port.""" + + port_name: str + port_description: str + tagged: bool + + +class PopVlanBlockInactive( + ProductBlockModel, + lifecycle=[SubscriptionLifecycle.INITIAL], + product_block_name="PopVlanBlock", +): + """A Pop VLAN that's currently inactive, see :class:`PopVlanBlock`.""" + + vlan_id: int + pop_vlan_description: str | None + lan_switch_interconnect: LanSwitchInterconnectBlockInactive + ports: PortList[PopVlanPortBlockProvisioning] + layer_preference: LayerPreference + ipv4_network: IPv4Network | None = None + ipv6_network: IPv6Network | None = None + + +class PopVlanBlockProvisioning(PopVlanBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]): + """A Pop VLAN that's currently being provisioned, see :class:`PopVlanBlock`.""" + + vlan_id: int + pop_vlan_description: str | None + lan_switch_interconnect: LanSwitchInterconnectBlockProvisioning + ports: PortList[PopVlanPortBlockProvisioning] + layer_preference: LayerPreference + ipv4_network: IPv4Network | None = None + ipv6_network: IPv6Network | None = None + + +class PopVlanBlock(PopVlanBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]): + """A Pop VLAN that's currently deployed in the network.""" + + #: The VLAN ID of the Pop VLAN. + vlan_id: int + #: The description of the Pop VLAN. + pop_vlan_description: str + #: The LAN Switch Interconnect that this Pop VLAN is connected to. + lan_switch_interconnect: LanSwitchInterconnectBlock + #: The ports of the Pop VLAN. + ports: PortList[PopVlanPortBlock] + #: The level of the layer preference for the Pop VLAN (L2 or L3). + layer_preference: LayerPreference + #: IPv4 network for the Pop VLAN if layer preference is L3. + ipv4_network: IPv4Network | None = None + #: IPv6 network for the Pop VLAN if layer preference is L3. + ipv6_network: IPv6Network | None = None diff --git a/gso/products/product_blocks/switch.py b/gso/products/product_blocks/switch.py new file mode 100644 index 0000000000000000000000000000000000000000..660cfd81320fc0739713a649a780b20265871d11 --- /dev/null +++ b/gso/products/product_blocks/switch.py @@ -0,0 +1,57 @@ +"""Product block for :class:`Switch` products.""" + +from orchestrator.domain.base import ProductBlockModel +from orchestrator.types import SubscriptionLifecycle +from pydantic_forms.types import strEnum + +from gso.products.product_blocks.site import ( + SiteBlock, + SiteBlockInactive, + SiteBlockProvisioning, +) +from gso.utils.shared_enums import PortNumber, Vendor + + +class SwitchModel(strEnum): + """Enumerator for the different types of switches.""" + + EX3400 = "EX3400" + + +class SwitchBlockInactive( + ProductBlockModel, + lifecycle=[SubscriptionLifecycle.INITIAL], + product_block_name="SwitchBlock", +): + """A switch that's being currently inactive. See :class:`SwitchBlock`.""" + + switch_hostname: str | None = None + switch_ts_port: PortNumber | None = None + switch_site: SiteBlockInactive | None + switch_vendor: Vendor | None + switch_model: SwitchModel | None = None + + +class SwitchBlockProvisioning(SwitchBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]): + """A switch that's being provisioned. See :class:`SwitchBlock`.""" + + switch_hostname: str + switch_ts_port: PortNumber + switch_site: SiteBlockProvisioning + switch_vendor: Vendor + switch_model: SwitchModel | None = None + + +class SwitchBlock(SwitchBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]): + """A switch that's currently deployed in the network.""" + + #: The hostname of the switch. + switch_hostname: str + #: The port of the terminal server that this switch is connected to. Used to offer out of band access. + switch_ts_port: PortNumber + #: The :class:`Site` that this switch resides in. Both physically and computationally. + switch_site: SiteBlock + #: The vendor of the switch. + switch_vendor: Vendor + #: The model of the switch. + switch_model: SwitchModel | None = None diff --git a/gso/products/product_types/lan_switch_interconnect.py b/gso/products/product_types/lan_switch_interconnect.py new file mode 100644 index 0000000000000000000000000000000000000000..32433c4694b0f097bcdc80df742ca9695a9dee53 --- /dev/null +++ b/gso/products/product_types/lan_switch_interconnect.py @@ -0,0 +1,28 @@ +"""The product type for LAN Switch Interconnect.""" + +from orchestrator.domain.base import SubscriptionModel +from orchestrator.types import SubscriptionLifecycle + +from gso.products.product_blocks.lan_switch_interconnect import ( + LanSwitchInterconnectBlock, + LanSwitchInterconnectBlockInactive, + LanSwitchInterconnectBlockProvisioning, +) + + +class LanSwitchInterconnectInactive(SubscriptionModel, is_base=True): + """An LAN Switch Interconnect that is inactive.""" + + lan_switch_interconnect: LanSwitchInterconnectBlockInactive + + +class LanSwitchInterconnectProvisioning(LanSwitchInterconnectInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]): + """An LAN Switch Interconnect that is being provisioned.""" + + lan_switch_interconnect: LanSwitchInterconnectBlockProvisioning + + +class LanSwitchInterconnect(LanSwitchInterconnectProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]): + """An LAN Switch Interconnect that is active.""" + + lan_switch_interconnect: LanSwitchInterconnectBlock diff --git a/gso/products/product_types/pop_vlan.py b/gso/products/product_types/pop_vlan.py new file mode 100644 index 0000000000000000000000000000000000000000..7567f4ea8014bbd49fa66b6204839d2596cccb30 --- /dev/null +++ b/gso/products/product_types/pop_vlan.py @@ -0,0 +1,24 @@ +"""The product type for Pop VLAN.""" + +from orchestrator.domain.base import SubscriptionModel +from orchestrator.types import SubscriptionLifecycle + +from gso.products.product_blocks.pop_vlan import PopVlanBlock, PopVlanBlockInactive, PopVlanBlockProvisioning + + +class PopVlanInactive(SubscriptionModel, is_base=True): + """A Pop VLAN that is inactive.""" + + pop_vlan: PopVlanBlockInactive + + +class PopVlanProvisioning(PopVlanInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]): + """A Pop VLAN that is being provisioned.""" + + pop_vlan: PopVlanBlockProvisioning + + +class PopVlan(PopVlanProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]): + """A Pop VLAN that is active.""" + + pop_vlan: PopVlanBlock diff --git a/gso/products/product_types/switch.py b/gso/products/product_types/switch.py new file mode 100644 index 0000000000000000000000000000000000000000..ef2eb05eac333628ba4ae5bef2424283adec3758 --- /dev/null +++ b/gso/products/product_types/switch.py @@ -0,0 +1,24 @@ +"""A switch product type.""" + +from orchestrator.domain.base import SubscriptionModel +from orchestrator.types import SubscriptionLifecycle + +from gso.products.product_blocks.switch import SwitchBlock, SwitchBlockInactive, SwitchBlockProvisioning + + +class SwitchInactive(SubscriptionModel, is_base=True): + """An inactive switch.""" + + switch: SwitchBlockInactive + + +class SwitchProvisioning(SwitchInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]): + """A switch that is being provisioned.""" + + switch: SwitchBlockProvisioning + + +class Switch(SwitchProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]): + """A switch that is currently active.""" + + switch: SwitchBlock diff --git a/gso/services/infoblox.py b/gso/services/infoblox.py index ca01bcabb8727a92aa0a2912bcf142a2a550c3cd..06ee5719a48010c2cb175200abc3b8276312303d 100644 --- a/gso/services/infoblox.py +++ b/gso/services/infoblox.py @@ -19,10 +19,6 @@ class AllocationError(Exception): """Raised when Infoblox failed to allocate a resource.""" -class DeletionError(Exception): - """Raised when Infoblox failed to delete a resource.""" - - def _setup_connection() -> tuple[connector.Connector, IPAMParams]: """Set up a new connection with an Infoblox instance. @@ -158,7 +154,7 @@ def delete_network(ip_network: ipaddress.IPv4Network | ipaddress.IPv6Network) -> network.delete() else: msg = f"Could not find network {ip_network}, nothing has been deleted." - raise DeletionError(msg) + logger.warning(msg) def allocate_host( @@ -253,8 +249,9 @@ def create_host_by_ip( :term:`GSO`. """ if not hostname_available(hostname): - msg = f"Cannot allocate new host, FQDN {hostname} already taken." - raise AllocationError(msg) + msg = f"FQDN {hostname} already taken, nothing to be done." + logger.warning(msg) + return conn, oss = _setup_connection() ipv6_object = objects.IP.create(ip=str(ipv6_address), mac=NULL_MAC, configure_for_dhcp=False) @@ -331,7 +328,7 @@ def delete_host_by_ip(ip_addr: ipaddress.IPv4Address | ipaddress.IPv6Address) -> host.delete() else: msg = f"Could not find host at {ip_addr}, nothing has been deleted." - raise DeletionError(msg) + logger.warning(msg) def delete_host_by_fqdn(fqdn: str) -> None: @@ -348,4 +345,4 @@ def delete_host_by_fqdn(fqdn: str) -> None: host.delete() else: msg = f"Could not find host at {fqdn}, nothing has been deleted." - raise DeletionError(msg) + logger.warning(msg) diff --git a/gso/translations/en-GB.json b/gso/translations/en-GB.json index fe687f342d00b95c0a44c1045a3e9718f8c1df47..63b31e76b5ea23947a59fff034c4367f50e1f83d 100644 --- a/gso/translations/en-GB.json +++ b/gso/translations/en-GB.json @@ -38,7 +38,6 @@ "workflow": { "activate_iptrunk": "Activate IP Trunk", "activate_router": "Activate router", - "cancel_subscription": "Cancel subscription", "confirm_info": "Please verify this form looks correct.", "deploy_twamp": "Deploy TWAMP", "migrate_iptrunk": "Migrate IP Trunk", diff --git a/gso/utils/helpers.py b/gso/utils/helpers.py index 6c30324ed0b81064bdc4c84e862f1a0ff671b9da..0241e836ef8c9db3bcc750b357e03c65d291914d 100644 --- a/gso/utils/helpers.py +++ b/gso/utils/helpers.py @@ -25,7 +25,7 @@ class LAGMember(BaseModel): """A :term:`LAG` member interface that consists of a name and description.""" interface_name: str - interface_description: str + interface_description: str | None def __hash__(self) -> int: """Calculate the hash based on the interface name and description, so that uniqueness can be determined.""" diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py index 05848a322c6b893231a8c754954c43ecf38da943..1e89ec894bf9b3029484db458b6ea19e592c6c55 100644 --- a/gso/workflows/__init__.py +++ b/gso/workflows/__init__.py @@ -1,17 +1,26 @@ """Initialisation class that imports all workflows into :term:`GSO`.""" from orchestrator.services.subscriptions import WF_USABLE_MAP +from orchestrator.types import SubscriptionLifecycle from orchestrator.workflows import LazyWorkflowInstance +ALL_ALIVE_STATES: list[str] = [ + SubscriptionLifecycle.INITIAL, + SubscriptionLifecycle.PROVISIONING, + SubscriptionLifecycle.ACTIVE, +] + WF_USABLE_MAP.update( { - "cancel_subscription": ["initial"], - "redeploy_base_config": ["provisioning", "active"], - "update_ibgp_mesh": ["provisioning", "active"], - "activate_router": ["provisioning"], - "deploy_twamp": ["provisioning", "active"], - "modify_trunk_interface": ["provisioning", "active"], - "activate_iptrunk": ["provisioning"], + "redeploy_base_config": [SubscriptionLifecycle.PROVISIONING, SubscriptionLifecycle.ACTIVE], + "update_ibgp_mesh": [SubscriptionLifecycle.PROVISIONING, SubscriptionLifecycle.ACTIVE], + "activate_router": [SubscriptionLifecycle.PROVISIONING], + "deploy_twamp": [SubscriptionLifecycle.PROVISIONING, SubscriptionLifecycle.ACTIVE], + "modify_trunk_interface": [SubscriptionLifecycle.PROVISIONING, SubscriptionLifecycle.ACTIVE], + "activate_iptrunk": [SubscriptionLifecycle.PROVISIONING], + "terminate_site": ALL_ALIVE_STATES, + "terminate_router": ALL_ALIVE_STATES, + "terminate_iptrunk": ALL_ALIVE_STATES, } ) @@ -28,7 +37,6 @@ LazyWorkflowInstance("gso.workflows.router.redeploy_base_config", "redeploy_base LazyWorkflowInstance("gso.workflows.router.terminate_router", "terminate_router") LazyWorkflowInstance("gso.workflows.router.update_ibgp_mesh", "update_ibgp_mesh") LazyWorkflowInstance("gso.workflows.router.modify_connection_strategy", "modify_connection_strategy") -LazyWorkflowInstance("gso.workflows.shared.cancel_subscription", "cancel_subscription") LazyWorkflowInstance("gso.workflows.site.create_site", "create_site") LazyWorkflowInstance("gso.workflows.site.modify_site", "modify_site") LazyWorkflowInstance("gso.workflows.site.terminate_site", "terminate_site") diff --git a/gso/workflows/iptrunk/create_iptrunk.py b/gso/workflows/iptrunk/create_iptrunk.py index b4dbed48a5cb6e9ef389147b71ac985e750987fa..fed3ab25b20b27a205481ca9fc685528670284d3 100644 --- a/gso/workflows/iptrunk/create_iptrunk.py +++ b/gso/workflows/iptrunk/create_iptrunk.py @@ -57,11 +57,11 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: tt_number: str partner: str = ReadOnlyField("GEANT") - geant_s_sid: str + geant_s_sid: str | None iptrunk_description: str iptrunk_type: IptrunkType iptrunk_speed: PhysicalPortCapacity - iptrunk_minimum_links: int + iptrunk_number_of_members: int @validator("tt_number", allow_reuse=True) def validate_tt_number(cls, tt_number: str) -> str: @@ -69,6 +69,13 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: initial_user_input = yield CreateIptrunkForm + class VerifyMinimumLinksForm(FormPage): + info_label: Label = ( + f"This is the calculated minimum-links for this LAG: " f"{initial_user_input.iptrunk_number_of_members - 1}" # type: ignore[assignment] + ) + info_label2: Label = "Please confirm or modify." # type: ignore[assignment] + + yield VerifyMinimumLinksForm router_enum_a = Choice("Select a router", zip(routers.keys(), routers.items(), strict=True)) # type: ignore[arg-type] class SelectRouterSideA(FormPage): @@ -86,7 +93,8 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: router_a_fqdn = Router.from_subscription(router_a).router.router_fqdn class JuniperAeMembers(UniqueConstrainedList[LAGMember]): - min_items = initial_user_input.iptrunk_minimum_links + min_items = initial_user_input.iptrunk_number_of_members + max_items = initial_user_input.iptrunk_number_of_members if get_router_vendor(router_a) == Vendor.NOKIA: @@ -97,7 +105,8 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: ) class NokiaAeMembersA(UniqueConstrainedList[NokiaLAGMemberA]): - min_items = initial_user_input.iptrunk_minimum_links + min_items = initial_user_input.iptrunk_number_of_members + max_items = initial_user_input.iptrunk_number_of_members ae_members_side_a = NokiaAeMembersA else: @@ -108,7 +117,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: title = f"Provide subscription details for side A of the trunk.({router_a_fqdn})" side_a_ae_iface: available_lags_choices(router_a) or str # type: ignore[valid-type] - side_a_ae_geant_a_sid: str + side_a_ae_geant_a_sid: str | None side_a_ae_members: ae_members_side_a # type: ignore[valid-type] @validator("side_a_ae_members", allow_reuse=True) @@ -159,7 +168,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: title = f"Provide subscription details for side B of the trunk.({router_b_fqdn})" side_b_ae_iface: available_lags_choices(router_b) or str # type: ignore[valid-type] - side_b_ae_geant_a_sid: str + side_b_ae_geant_a_sid: str | None side_b_ae_members: ae_members_side_b # type: ignore[valid-type] @validator("side_b_ae_members", allow_reuse=True) @@ -209,18 +218,18 @@ def get_info_from_ipam(subscription: IptrunkInactive) -> State: @step("Initialize subscription") def initialize_subscription( subscription: IptrunkInactive, - geant_s_sid: str, + geant_s_sid: str | None, iptrunk_type: IptrunkType, iptrunk_description: str, iptrunk_speed: PhysicalPortCapacity, - iptrunk_minimum_links: int, + iptrunk_number_of_members: int, side_a_node_id: str, side_a_ae_iface: str, - side_a_ae_geant_a_sid: str, + side_a_ae_geant_a_sid: str | None, side_a_ae_members: list[dict], side_b_node_id: str, side_b_ae_iface: str, - side_b_ae_geant_a_sid: str, + side_b_ae_geant_a_sid: str | None, side_b_ae_members: list[dict], ) -> State: """Take all input from the user, and store it in the database.""" @@ -232,7 +241,7 @@ def initialize_subscription( subscription.iptrunk.iptrunk_type = iptrunk_type subscription.iptrunk.iptrunk_speed = iptrunk_speed subscription.iptrunk.iptrunk_isis_metric = oss_params.GENERAL.isis_high_metric - subscription.iptrunk.iptrunk_minimum_links = iptrunk_minimum_links + subscription.iptrunk.iptrunk_minimum_links = iptrunk_number_of_members - 1 subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node = side_a subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_iface = side_a_ae_iface diff --git a/gso/workflows/iptrunk/deploy_twamp.py b/gso/workflows/iptrunk/deploy_twamp.py index b9003078a016313bab3012cfee8eaf3f11f278ab..64483e60b8cae8717ad553c3f03bb03651fa6299 100644 --- a/gso/workflows/iptrunk/deploy_twamp.py +++ b/gso/workflows/iptrunk/deploy_twamp.py @@ -1,9 +1,12 @@ """Workflow for adding TWAMP to an existing IP trunk.""" +import json + from orchestrator.forms import FormPage 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, done, init, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form @@ -38,9 +41,10 @@ def _initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: def deploy_twamp_dry(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State: """Perform a dry run of deploying the TWAMP session.""" extra_vars = { - "subscription": subscription, + "subscription": json.loads(json_dumps(subscription)), "process_id": process_id, "dry_run": True, + "verb": "deploy", "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Deploy TWAMP", } @@ -58,9 +62,10 @@ def deploy_twamp_dry(subscription: Iptrunk, process_id: UUIDstr, callback_route: def deploy_twamp_real(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State: """Deploy the TWAMP session.""" extra_vars = { - "subscription": subscription, + "subscription": json.loads(json_dumps(subscription)), "process_id": process_id, "dry_run": False, + "verb": "deploy", "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Deploy TWAMP", } @@ -74,6 +79,24 @@ def deploy_twamp_real(subscription: Iptrunk, process_id: UUIDstr, callback_route return {"subscription": subscription} +@step("Check TWAMP status on both sides") +def check_twamp_status(subscription: Iptrunk, callback_route: str) -> State: + """Check TWAMP session.""" + extra_vars = { + "subscription": json.loads(json_dumps(subscription)), + "verb": "check_twamp", + } + + inventory = ( + f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}" + f"\n{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}" + ) + + execute_playbook("deploy_twamp.yaml", callback_route, inventory, extra_vars) + + return {"subscription": subscription} + + @workflow( "Deploy TWAMP", initial_input_form=wrap_modify_initial_input_form(_initial_input_form_generator), @@ -90,6 +113,7 @@ def deploy_twamp() -> StepList: >> unsync >> lso_interaction(deploy_twamp_dry) >> lso_interaction(deploy_twamp_real) + >> lso_interaction(check_twamp_status) >> resync >> done ) diff --git a/gso/workflows/iptrunk/migrate_iptrunk.py b/gso/workflows/iptrunk/migrate_iptrunk.py index 540505d061f834b13bfae8855b592f0c140decc7..96ee4abb3ea30356ae701c90c22f2db27669c949 100644 --- a/gso/workflows/iptrunk/migrate_iptrunk.py +++ b/gso/workflows/iptrunk/migrate_iptrunk.py @@ -15,7 +15,6 @@ from orchestrator.forms import FormPage from orchestrator.forms.validators import Choice, Label, UniqueConstrainedList 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, conditional, done, init, inputstep from orchestrator.workflows.steps import resync, store_process_subscription, unsync @@ -28,7 +27,6 @@ from gso.products.product_blocks.iptrunk import IptrunkInterfaceBlock from gso.products.product_types.iptrunk import Iptrunk from gso.products.product_types.router import Router from gso.services import infoblox -from gso.services.infoblox import DeletionError from gso.services.lso_client import execute_playbook, lso_interaction from gso.services.netbox_client import NetboxClient from gso.services.subscriptions import get_active_router_subscriptions @@ -609,12 +607,7 @@ def update_ipam(subscription: Iptrunk, replace_index: int, new_node: Router, new v6_addr = subscription.iptrunk.iptrunk_ipv6_network[replace_index + 1] # Out with the old - try: - infoblox.delete_host_by_ip(subscription.iptrunk.iptrunk_ipv4_network[replace_index]) - except DeletionError as e: - msg = "Failed to delete record from Infoblox." - raise ProcessFailureError(msg) from e - + infoblox.delete_host_by_ip(subscription.iptrunk.iptrunk_ipv4_network[replace_index]) # And in with the new new_fqdn = f"{new_lag_interface}-0.{new_node.router.router_fqdn}" comment = str(subscription.subscription_id) diff --git a/gso/workflows/iptrunk/modify_trunk_interface.py b/gso/workflows/iptrunk/modify_trunk_interface.py index d3b5e60ee539660e7aa2fbd22088d20c8235e6cb..27111c62cbba9a1a92c173a035a3df163ac29d3f 100644 --- a/gso/workflows/iptrunk/modify_trunk_interface.py +++ b/gso/workflows/iptrunk/modify_trunk_interface.py @@ -40,7 +40,7 @@ def initialize_ae_members(subscription: Iptrunk, initial_user_input: dict, side_ """Initialize the list of AE members.""" router = subscription.iptrunk.iptrunk_sides[side_index].iptrunk_side_node router_vendor = get_router_vendor(router.owner_subscription_id) - iptrunk_minimum_link = initial_user_input["iptrunk_minimum_links"] + iptrunk_number_of_members = initial_user_input["iptrunk_number_of_members"] if router_vendor == Vendor.NOKIA: iptrunk_speed = initial_user_input["iptrunk_speed"] @@ -61,13 +61,15 @@ def initialize_ae_members(subscription: Iptrunk, initial_user_input: dict, side_ ) class NokiaAeMembers(UniqueConstrainedList[NokiaLAGMember]): - min_items = iptrunk_minimum_link + min_items = iptrunk_number_of_members + max_items = iptrunk_number_of_members ae_members = NokiaAeMembers else: class JuniperAeMembers(UniqueConstrainedList[LAGMember]): - min_items = iptrunk_minimum_link + min_items = iptrunk_number_of_members + max_items = iptrunk_number_of_members ae_members = JuniperAeMembers # type: ignore[assignment] return ae_members # type: ignore[return-value] @@ -79,7 +81,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: class ModifyIptrunkForm(FormPage): tt_number: str - geant_s_sid: str = subscription.iptrunk.geant_s_sid + geant_s_sid: str | None = subscription.iptrunk.geant_s_sid iptrunk_description: str = subscription.iptrunk.iptrunk_description iptrunk_type: IptrunkType = subscription.iptrunk.iptrunk_type warning_label: Label = ( @@ -87,7 +89,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: "You will need to add the new AE members in the next steps." # type: ignore[assignment] ) iptrunk_speed: PhysicalPortCapacity = subscription.iptrunk.iptrunk_speed - iptrunk_minimum_links: int = subscription.iptrunk.iptrunk_minimum_links + iptrunk_number_of_members: int = subscription.iptrunk.iptrunk_minimum_links + 1 iptrunk_isis_metric: int = ReadOnlyField(subscription.iptrunk.iptrunk_isis_metric) iptrunk_ipv4_network: ipaddress.IPv4Network = ReadOnlyField(subscription.iptrunk.iptrunk_ipv4_network) iptrunk_ipv6_network: ipaddress.IPv6Network = ReadOnlyField(subscription.iptrunk.iptrunk_ipv6_network) @@ -97,6 +99,14 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: return validate_tt_number(tt_number) initial_user_input = yield ModifyIptrunkForm + + class VerifyMinimumLinksForm(FormPage): + info_label: Label = ( + f"This is the calculated minimum-links for this LAG: " f"{initial_user_input.iptrunk_number_of_members - 1}" # type: ignore[assignment] + ) + info_label2: Label = "Please confirm or modify." # type: ignore[assignment] + + yield VerifyMinimumLinksForm ae_members_side_a = initialize_ae_members(subscription, initial_user_input.dict(), 0) class ModifyIptrunkSideAForm(FormPage): @@ -105,7 +115,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: side_a_node: str = ReadOnlyField(subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn) side_a_ae_iface: str = ReadOnlyField(subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_iface) - side_a_ae_geant_a_sid: str = subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_geant_a_sid + side_a_ae_geant_a_sid: str | None = subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_geant_a_sid side_a_ae_members: ae_members_side_a = ( # type: ignore[valid-type] subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members if initial_user_input.iptrunk_speed == subscription.iptrunk.iptrunk_speed @@ -130,7 +140,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: side_b_node: str = ReadOnlyField(subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn) side_b_ae_iface: str = ReadOnlyField(subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_iface) - side_b_ae_geant_a_sid: str = subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_geant_a_sid + side_b_ae_geant_a_sid: str | None = subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_geant_a_sid side_b_ae_members: ae_members_side_b = ( # type: ignore[valid-type] subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members if initial_user_input.iptrunk_speed == subscription.iptrunk.iptrunk_speed @@ -154,14 +164,14 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: @step("Update subscription") def modify_iptrunk_subscription( subscription: Iptrunk, - geant_s_sid: str, + geant_s_sid: str | None, iptrunk_type: IptrunkType, iptrunk_description: str, iptrunk_speed: PhysicalPortCapacity, - iptrunk_minimum_links: int, - side_a_ae_geant_a_sid: str, + iptrunk_number_of_members: int, + side_a_ae_geant_a_sid: str | None, side_a_ae_members: list[dict], - side_b_ae_geant_a_sid: str, + side_b_ae_geant_a_sid: str | None, side_b_ae_members: list[dict], ) -> State: """Modify the subscription in the service database, reflecting the changes to the newly selected interfaces.""" @@ -187,7 +197,7 @@ def modify_iptrunk_subscription( subscription.iptrunk.iptrunk_description = iptrunk_description subscription.iptrunk.iptrunk_type = iptrunk_type subscription.iptrunk.iptrunk_speed = iptrunk_speed - subscription.iptrunk.iptrunk_minimum_links = iptrunk_minimum_links + subscription.iptrunk.iptrunk_minimum_links = iptrunk_number_of_members - 1 subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_geant_a_sid = side_a_ae_geant_a_sid # Flush the old list of member interfaces diff --git a/gso/workflows/iptrunk/terminate_iptrunk.py b/gso/workflows/iptrunk/terminate_iptrunk.py index 7a2afe6c22f0e2a1f381c92734f0efd5a8dbbb25..a2cb6727215a9f6184859c548dba7c3162127559 100644 --- a/gso/workflows/iptrunk/terminate_iptrunk.py +++ b/gso/workflows/iptrunk/terminate_iptrunk.py @@ -28,18 +28,23 @@ from gso.utils.shared_enums import Vendor from gso.utils.workflow_steps import set_isis_to_max -def initial_input_form_generator() -> FormGenerator: +def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: """Ask the operator to confirm whether router configuration and :term:`IPAM` resources should be deleted.""" + iptrunk = Iptrunk.from_subscription(subscription_id) class TerminateForm(FormPage): + if iptrunk.status == SubscriptionLifecycle.INITIAL: + info_label_2: Label = ( + "This will immediately mark the subscription as terminated, preventing any other workflows from " # type:ignore[assignment] + "interacting with this product subscription." + ) + info_label_3: Label = "ONLY EXECUTE THIS WORKFLOW WHEN YOU ARE ABSOLUTELY SURE WHAT YOU ARE DOING." # type:ignore[assignment] + + tt_number: str termination_label: Label = ( - "Please confirm whether configuration should get removed from the A and B sides of the trunk, and whether " - "IPAM and Netbox resources should be released." # type: ignore[assignment] + "Please confirm whether configuration should get removed from the A and B sides of the trunk." # type: ignore[assignment] ) - tt_number: str remove_configuration: bool = True - clean_up_ipam: bool = True - clean_up_netbox: bool = True @validator("tt_number", allow_reuse=True) def validate_tt_number(cls, tt_number: str) -> str: @@ -148,22 +153,19 @@ def terminate_iptrunk() -> StepList: * Let the operator decide whether to remove configuration from the routers, if so: * Set the :term:`ISIS` metric of the IP trunk to an arbitrarily high value * Disable and remove configuration from the routers, first as a dry run - * Mark the IP trunk interfaces as free in Netbox, if selected by the operator - * Clear :term:`IPAM` resources, if selected by the operator + * Mark the IP trunk interfaces as free in Netbox + * Clear :term:`IPAM` resources * Terminate the subscription in the service database """ run_config_steps = conditional(lambda state: state["remove_configuration"]) - run_ipam_steps = conditional(lambda state: state["clean_up_ipam"]) side_a_is_nokia = conditional( - lambda state: state["clean_up_netbox"] - and get_router_vendor( + lambda state: get_router_vendor( state["subscription"]["iptrunk"]["iptrunk_sides"][0]["iptrunk_side_node"]["owner_subscription_id"] ) == Vendor.NOKIA ) side_b_is_nokia = conditional( - lambda state: state["clean_up_netbox"] - and get_router_vendor( + lambda state: get_router_vendor( state["subscription"]["iptrunk"]["iptrunk_sides"][1]["iptrunk_side_node"]["owner_subscription_id"] ) == Vendor.NOKIA @@ -175,7 +177,6 @@ def terminate_iptrunk() -> StepList: >> lso_interaction(deprovision_ip_trunk_dry) >> lso_interaction(deprovision_ip_trunk_real) ) - ipam_steps = init >> deprovision_ip_trunk_ipv4 >> deprovision_ip_trunk_ipv6 return ( init @@ -184,7 +185,8 @@ def terminate_iptrunk() -> StepList: >> run_config_steps(config_steps) >> side_a_is_nokia(netbox_clean_up_side_a) >> side_b_is_nokia(netbox_clean_up_side_b) - >> run_ipam_steps(ipam_steps) + >> deprovision_ip_trunk_ipv4 + >> deprovision_ip_trunk_ipv6 >> set_status(SubscriptionLifecycle.TERMINATED) >> resync >> done diff --git a/gso/workflows/router/terminate_router.py b/gso/workflows/router/terminate_router.py index 8abc8877367f5c9333852de0e19720f6f01d25d8..0d46f9abbd7a32e2f141894a09751659d24d52fa 100644 --- a/gso/workflows/router/terminate_router.py +++ b/gso/workflows/router/terminate_router.py @@ -32,13 +32,16 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: router = Router.from_subscription(subscription_id) class TerminateForm(FormPage): - termination_label: Label = ( - "Please confirm whether configuration should get removed from the router, and whether IPAM resources should" - " be released." # type: ignore[assignment] - ) + if router.status == SubscriptionLifecycle.INITIAL: + info_label_2: Label = ( + "This will immediately mark the subscription as terminated, preventing any other workflows from " # type:ignore[assignment] + "interacting with this product subscription." + ) + info_label_3: Label = "ONLY EXECUTE THIS WORKFLOW WHEN YOU ARE ABSOLUTELY SURE WHAT YOU ARE DOING." # type:ignore[assignment] + tt_number: str + termination_label: Label = "Please confirm whether configuration should get removed from the router." # type: ignore[assignment] remove_configuration: bool = True - clean_up_ipam: bool = True user_input = yield TerminateForm return user_input.dict() | {"router_is_nokia": router.router.vendor == Vendor.NOKIA} @@ -114,7 +117,6 @@ def terminate_router() -> StepList: * Disable and delete configuration on the router, if selected by the operator * Mark the subscription as terminated in the service database """ - run_ipam_steps = conditional(lambda state: state["clean_up_ipam"]) run_config_steps = conditional(lambda state: state["remove_configuration"]) router_is_nokia = conditional(lambda state: state["router_is_nokia"]) @@ -122,7 +124,7 @@ def terminate_router() -> StepList: init >> store_process_subscription(Target.TERMINATE) >> unsync - >> run_ipam_steps(deprovision_loopback_ips) + >> deprovision_loopback_ips >> run_config_steps(lso_interaction(remove_config_from_router_dry)) >> run_config_steps(lso_interaction(remove_config_from_router_real)) >> router_is_nokia(remove_device_from_netbox) diff --git a/gso/workflows/shared/__init__.py b/gso/workflows/shared/__init__.py deleted file mode 100644 index daa25805d2c02ae05d7292e4a74a649d93a07e6b..0000000000000000000000000000000000000000 --- a/gso/workflows/shared/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Workflows that are shared across multiple products.""" diff --git a/gso/workflows/shared/cancel_subscription.py b/gso/workflows/shared/cancel_subscription.py deleted file mode 100644 index 79bb05905f6940ce3eda6543d960acf41818fb4f..0000000000000000000000000000000000000000 --- a/gso/workflows/shared/cancel_subscription.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Cancel a subscription that is in the initial lifecycle state.""" - -from orchestrator.forms import FormPage -from orchestrator.forms.validators import Label -from orchestrator.targets import Target -from orchestrator.types import FormGenerator, SubscriptionLifecycle, UUIDstr -from orchestrator.workflow import StepList, done, init, workflow -from orchestrator.workflows.steps import resync, set_status, store_process_subscription, unsync -from orchestrator.workflows.utils import wrap_modify_initial_input_form - - -def _initial_input_form(subscription_id: UUIDstr) -> FormGenerator: - class CancelSubscriptionForm(FormPage): - info_label: Label = f"Canceling subscription with ID {subscription_id}" # type:ignore[assignment] - info_label_2: Label = ( - "This will immediately mark the subscription as terminated, preventing any other workflows from interacting" # type:ignore[assignment] - " with this product subscription." - ) - info_label_3: Label = "ONLY EXECUTE THIS WORKFLOW WHEN YOU ARE ABSOLUTELY SURE WHAT YOU ARE DOING." # type:ignore[assignment] - info_label_4: Label = "THIS WORKFLOW IS IRREVERSIBLE AND MAY HAVE UNFORESEEN CONSEQUENCES." # type:ignore[assignment] - - yield CancelSubscriptionForm - - return {"subscription_id": subscription_id} - - -@workflow( - "Cancel an initial subscription", - initial_input_form=wrap_modify_initial_input_form(_initial_input_form), - target=Target.TERMINATE, -) -def cancel_subscription() -> StepList: - """Cancel an initial subscription, taking it from the ``INITIAL`` state straight to ``TERMINATED``. - - This workflow can be used when a creation workflow has failed, and the process needs to be restarted. This workflow - will prevent a stray subscription, forever stuck in the initial state, to stick around. - - * Update the subscription lifecycle state to ``TERMINATED``. - """ - return ( - init - >> store_process_subscription(Target.TERMINATE) - >> unsync - >> set_status(SubscriptionLifecycle.TERMINATED) - >> resync - >> done - ) diff --git a/gso/workflows/site/terminate_site.py b/gso/workflows/site/terminate_site.py index 91be194f181f13422cf349d7ca0f65826b3a6a2a..96e807b47ed9c72b101ad3c6303e9b1fbd1405bc 100644 --- a/gso/workflows/site/terminate_site.py +++ b/gso/workflows/site/terminate_site.py @@ -18,9 +18,16 @@ from gso.products.product_types.site import Site def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: """Ask the user for confirmation whether to terminate the selected site.""" - Site.from_subscription(subscription_id) + site = Site.from_subscription(subscription_id) class TerminateForm(FormPage): + if site.status == SubscriptionLifecycle.INITIAL: + info_label_2: Label = ( + "This will immediately mark the subscription as terminated, preventing any other workflows from " # type:ignore[assignment] + "interacting with this product subscription." + ) + info_label_3: Label = "ONLY EXECUTE THIS WORKFLOW WHEN YOU ARE ABSOLUTELY SURE WHAT YOU ARE DOING." # type:ignore[assignment] + termination_label: Label = "Are you sure you want to delete this site?" # type: ignore[assignment] user_input = yield TerminateForm diff --git a/gso/workflows/tasks/import_iptrunk.py b/gso/workflows/tasks/import_iptrunk.py index 648d954f94ae57f9471826bba04402684508de81..9c6687b35943f7316a4cf4758009598270f80b77 100644 --- a/gso/workflows/tasks/import_iptrunk.py +++ b/gso/workflows/tasks/import_iptrunk.py @@ -39,7 +39,7 @@ def initial_input_form_generator() -> FormGenerator: title = "Import Iptrunk" partner: str - geant_s_sid: str + geant_s_sid: str | None iptrunk_description: str iptrunk_type: IptrunkType iptrunk_speed: PhysicalPortCapacity @@ -48,12 +48,12 @@ def initial_input_form_generator() -> FormGenerator: side_a_node_id: router_enum # type: ignore[valid-type] side_a_ae_iface: str - side_a_ae_geant_a_sid: str + side_a_ae_geant_a_sid: str | None side_a_ae_members: UniqueConstrainedList[LAGMember] side_b_node_id: router_enum # type: ignore[valid-type] side_b_ae_iface: str - side_b_ae_geant_a_sid: str + side_b_ae_geant_a_sid: str | None side_b_ae_members: UniqueConstrainedList[LAGMember] iptrunk_ipv4_network: ipaddress.IPv4Network @@ -80,7 +80,7 @@ def create_subscription(partner: str) -> State: @step("Initialize subscription") def initialize_subscription( subscription: IptrunkInactive, - geant_s_sid: str, + geant_s_sid: str | None, iptrunk_type: IptrunkType, iptrunk_description: str, iptrunk_speed: PhysicalPortCapacity, @@ -88,11 +88,11 @@ def initialize_subscription( iptrunk_isis_metric: int, side_a_node_id: str, side_a_ae_iface: str, - side_a_ae_geant_a_sid: str, + side_a_ae_geant_a_sid: str | None, side_a_ae_members: list[dict], side_b_node_id: str, side_b_ae_iface: str, - side_b_ae_geant_a_sid: str, + side_b_ae_geant_a_sid: str | None, side_b_ae_members: list[dict], ) -> State: """Take all input from the user, and store it in the database.""" diff --git a/setup.py b/setup.py index 82661968f448d6d87fdf7583360a6be254d6f41c..2fcf9799777b7fc3a7bbd56c99307d3818fbfe7e 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import find_packages, setup setup( name="geant-service-orchestrator", - version="1.4", + version="1.5", author="GÉANT Orchestration and Automation Team", author_email="goat@geant.org", description="GÉANT Service Orchestrator", diff --git a/test/services/test_infoblox.py b/test/services/test_infoblox.py index bebcdae602f852f4fb92fab4bfbeab85e7f12d26..3a7332606ed6314c104fd281a1c8c03bfcd18a91 100644 --- a/test/services/test_infoblox.py +++ b/test/services/test_infoblox.py @@ -1,4 +1,5 @@ import ipaddress +import logging import re from os import PathLike @@ -7,7 +8,7 @@ import responses from requests import codes from gso.services import infoblox -from gso.services.infoblox import AllocationError, DeletionError +from gso.services.infoblox import AllocationError def _set_up_network_responses(): @@ -216,17 +217,16 @@ def test_delete_good_network(data_config_filename: PathLike): @responses.activate -def test_delete_non_existent_network(data_config_filename: PathLike): +def test_delete_non_existent_network(data_config_filename: PathLike, caplog): responses.add( method=responses.GET, url="https://10.0.0.1/wapi/v2.12/network?network=10.255.255.0%2F26&_return_fields=comment%2Cextattrs%2Cnetwork%" "2Cnetwork_view", json=[], ) - - with pytest.raises(DeletionError) as e: - infoblox.delete_network(ipaddress.IPv4Network("10.255.255.0/26")) - assert e.value.args[0] == "Could not find network 10.255.255.0/26, nothing has been deleted." + caplog.set_level(logging.WARNING) + infoblox.delete_network(ipaddress.IPv4Network("10.255.255.0/26")) + assert "Could not find network 10.255.255.0/26, nothing has been deleted." in caplog.text @responses.activate @@ -277,21 +277,19 @@ def test_delete_good_host(data_config_filename: PathLike): @responses.activate -def test_delete_bad_host(data_config_filename: PathLike): +def test_delete_bad_host(data_config_filename: PathLike, caplog): responses.add( method=responses.GET, url=re.compile(r".+"), json=[], ) + caplog.set_level(logging.WARNING) - with pytest.raises(DeletionError) as e: - infoblox.delete_host_by_ip(ipaddress.IPv4Address("10.255.255.1")) - assert e.value.args[0] == "Could not find host at 10.255.255.1, nothing has been deleted." + infoblox.delete_host_by_ip(ipaddress.IPv4Address("10.255.255.1")) + assert "Could not find host at 10.255.255.1, nothing has been deleted." in caplog.text - with pytest.raises(DeletionError) as e: - infoblox.delete_host_by_ip(ipaddress.IPv6Address("dead:beef::1")) - assert e.value.args[0] == "Could not find host at dead:beef::1, nothing has been deleted." + infoblox.delete_host_by_ip(ipaddress.IPv6Address("dead:beef::1")) + assert "Could not find host at dead:beef::1, nothing has been deleted." in caplog.text - with pytest.raises(DeletionError) as e: - infoblox.delete_host_by_fqdn("fake.host.net") - assert e.value.args[0] == "Could not find host at fake.host.net, nothing has been deleted." + infoblox.delete_host_by_fqdn("fake.host.net") + assert "Could not find host at fake.host.net, nothing has been deleted." in caplog.text diff --git a/test/workflows/iptrunk/test_create_iptrunk.py b/test/workflows/iptrunk/test_create_iptrunk.py index 34a79604ef532c41cf141214c88b5790b810aeef..4163f6835166d368cbda5a007d24f7043bd8e7f4 100644 --- a/test/workflows/iptrunk/test_create_iptrunk.py +++ b/test/workflows/iptrunk/test_create_iptrunk.py @@ -52,12 +52,12 @@ def input_form_wizard_data(request, juniper_router_subscription_factory, nokia_r # Set side b router to Juniper if vendor == Vendor.JUNIPER: router_side_b = juniper_router_subscription_factory() - side_b_members = faker.link_members_juniper() + side_b_members = faker.link_members_juniper()[0:2] else: router_side_b = nokia_router_subscription_factory() side_b_members = [ LAGMember(interface_name=f"Interface{interface}", interface_description=faker.sentence()) - for interface in range(5) + for interface in range(2) ] create_ip_trunk_step = { @@ -66,18 +66,19 @@ def input_form_wizard_data(request, juniper_router_subscription_factory, nokia_r "iptrunk_type": IptrunkType.DARK_FIBER, "iptrunk_description": faker.sentence(), "iptrunk_speed": PhysicalPortCapacity.HUNDRED_GIGABIT_PER_SECOND, - "iptrunk_minimum_links": 2, + "iptrunk_number_of_members": 2, } + create_ip_trunk_confirm_step = {} create_ip_trunk_side_a_router_name = {"side_a_node_id": router_side_a} create_ip_trunk_side_a_step = { "side_a_ae_iface": "lag-1", - "side_a_ae_geant_a_sid": faker.geant_sid(), + "side_a_ae_geant_a_sid": None, "side_a_ae_members": [ LAGMember( interface_name=f"Interface{interface}", interface_description=faker.sentence(), ) - for interface in range(5) + for interface in range(2) ], } create_ip_trunk_side_b_router_name = {"side_b_node_id": router_side_b} @@ -89,6 +90,7 @@ def input_form_wizard_data(request, juniper_router_subscription_factory, nokia_r return [ create_ip_trunk_step, + create_ip_trunk_confirm_step, create_ip_trunk_side_a_router_name, create_ip_trunk_side_a_step, create_ip_trunk_side_b_router_name, diff --git a/test/workflows/iptrunk/test_deploy_twamp.py b/test/workflows/iptrunk/test_deploy_twamp.py index c5592738f530a1769c5fd8dd56e02e426950c6ad..8584cd99cae310bccf70ac22a6cd377b5672cefa 100644 --- a/test/workflows/iptrunk/test_deploy_twamp.py +++ b/test/workflows/iptrunk/test_deploy_twamp.py @@ -25,7 +25,7 @@ def test_iptrunk_deploy_twamp_success( initial_input_data = [{"subscription_id": product_id}, {"tt_number": faker.tt_number()}] result, process_stat, step_log = run_workflow("deploy_twamp", initial_input_data) - for _ in range(2): + for _ in range(3): result, step_log = assert_lso_interaction_success(result, process_stat, step_log) assert_complete(result) @@ -35,4 +35,4 @@ def test_iptrunk_deploy_twamp_success( subscription = Iptrunk.from_subscription(subscription_id) assert subscription.status == "active" - assert mock_execute_playbook.call_count == 2 + assert mock_execute_playbook.call_count == 3 diff --git a/test/workflows/iptrunk/test_modify_trunk_interface.py b/test/workflows/iptrunk/test_modify_trunk_interface.py index 4dca28e963ef862f182749a9ce7c1b25c418c4b7..18524cfd70ba8a61a12db319ff4cd1af921c64fe 100644 --- a/test/workflows/iptrunk/test_modify_trunk_interface.py +++ b/test/workflows/iptrunk/test_modify_trunk_interface.py @@ -29,26 +29,26 @@ def input_form_iptrunk_data( side_node = juniper_router_subscription_factory() side_a_node = iptrunk_side_subscription_factory(iptrunk_side_node=side_node) side_b_node = iptrunk_side_subscription_factory() - new_side_a_ae_members = faker.link_members_juniper() - new_side_b_ae_members = faker.link_members_nokia() + new_side_a_ae_members = faker.link_members_juniper()[0:2] + new_side_b_ae_members = faker.link_members_nokia()[0:2] elif use_juniper == UseJuniperSide.SIDE_B: side_node = juniper_router_subscription_factory() side_a_node = iptrunk_side_subscription_factory() side_b_node = iptrunk_side_subscription_factory(iptrunk_side_node=side_node) - new_side_a_ae_members = faker.link_members_nokia() - new_side_b_ae_members = faker.link_members_juniper() + new_side_a_ae_members = faker.link_members_nokia()[0:2] + new_side_b_ae_members = faker.link_members_juniper()[0:2] elif use_juniper == UseJuniperSide.SIDE_BOTH: side_node_1 = juniper_router_subscription_factory() side_node_2 = juniper_router_subscription_factory() side_a_node = iptrunk_side_subscription_factory(iptrunk_side_node=side_node_1) side_b_node = iptrunk_side_subscription_factory(iptrunk_side_node=side_node_2) - new_side_a_ae_members = faker.link_members_juniper() - new_side_b_ae_members = faker.link_members_juniper() + new_side_a_ae_members = faker.link_members_juniper()[0:2] + new_side_b_ae_members = faker.link_members_juniper()[0:2] else: side_a_node = iptrunk_side_subscription_factory() side_b_node = iptrunk_side_subscription_factory() - new_side_a_ae_members = faker.link_members_nokia() - new_side_b_ae_members = faker.link_members_nokia() + new_side_a_ae_members = faker.link_members_nokia()[0:2] + new_side_b_ae_members = faker.link_members_nokia()[0:2] product_id = iptrunk_subscription_factory(iptrunk_sides=[side_a_node, side_b_node]) @@ -70,8 +70,9 @@ def input_form_iptrunk_data( "iptrunk_description": new_description, "iptrunk_type": new_type, "iptrunk_speed": new_speed, - "iptrunk_minimum_links": new_link_count, + "iptrunk_number_of_members": new_link_count, }, + {}, { "side_a_ae_geant_a_sid": new_side_a_sid, "side_a_ae_members": new_side_a_ae_members, @@ -133,10 +134,10 @@ def test_iptrunk_modify_trunk_interface_success( assert mock_provision_ip_trunk.call_count == 2 # Assert all Netbox calls have been made new_sid = input_form_iptrunk_data[1]["geant_s_sid"] - new_side_a_sid = input_form_iptrunk_data[2]["side_a_ae_geant_a_sid"] - new_side_a_ae_members = input_form_iptrunk_data[2]["side_a_ae_members"] - new_side_b_sid = input_form_iptrunk_data[3]["side_b_ae_geant_a_sid"] - new_side_b_ae_members = input_form_iptrunk_data[3]["side_b_ae_members"] + new_side_a_sid = input_form_iptrunk_data[3]["side_a_ae_geant_a_sid"] + new_side_a_ae_members = input_form_iptrunk_data[3]["side_a_ae_members"] + new_side_b_sid = input_form_iptrunk_data[4]["side_b_ae_geant_a_sid"] + new_side_b_ae_members = input_form_iptrunk_data[4]["side_b_ae_members"] # Only Nokia interfaces are checked vendor_side_a = subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.vendor @@ -162,7 +163,7 @@ def test_iptrunk_modify_trunk_interface_success( assert subscription.iptrunk.iptrunk_description == input_form_iptrunk_data[1]["iptrunk_description"] assert subscription.iptrunk.iptrunk_type == input_form_iptrunk_data[1]["iptrunk_type"] assert subscription.iptrunk.iptrunk_speed == input_form_iptrunk_data[1]["iptrunk_speed"] - assert subscription.iptrunk.iptrunk_minimum_links == input_form_iptrunk_data[1]["iptrunk_minimum_links"] + assert subscription.iptrunk.iptrunk_minimum_links == input_form_iptrunk_data[1]["iptrunk_number_of_members"] - 1 assert subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_geant_a_sid == new_side_a_sid def _find_interface_by_name(interfaces: list[dict[str, str]], name: str): diff --git a/test/workflows/iptrunk/test_terminate_iptrunk.py b/test/workflows/iptrunk/test_terminate_iptrunk.py index 05a94456ab00aa1145f2e1dae1953eae437568ee..c026e84bccc292fe236e1855e3f86a49d47a8ab1 100644 --- a/test/workflows/iptrunk/test_terminate_iptrunk.py +++ b/test/workflows/iptrunk/test_terminate_iptrunk.py @@ -42,7 +42,6 @@ def test_successful_iptrunk_termination( { "tt_number": faker.tt_number(), "remove_configuration": True, - "clean_up_ipam": True, }, ] result, process_stat, step_log = run_workflow("terminate_iptrunk", initial_iptrunk_data) diff --git a/test/workflows/router/test_terminate_router.py b/test/workflows/router/test_terminate_router.py index bc978824b1f03f03fa7b06a8f5325f1d26959a27..e52826820084981798541f1189a134eda5d1d9c0 100644 --- a/test/workflows/router/test_terminate_router.py +++ b/test/workflows/router/test_terminate_router.py @@ -7,15 +7,7 @@ from test.workflows import assert_complete, assert_lso_interaction_success, extr @pytest.mark.workflow() -@pytest.mark.parametrize( - ("remove_configuration", "clean_up_ipam"), - [ - (True, True), - (True, False), - (False, True), - (False, False), - ], -) +@pytest.mark.parametrize("remove_configuration", [True, False]) @patch("gso.services.lso_client._send_request") @patch("gso.workflows.router.terminate_router.NetboxClient.delete_device") @patch("gso.workflows.router.terminate_router.infoblox.delete_host_by_ip") @@ -24,7 +16,6 @@ def test_terminate_router_full_success( mock_delete_device, mock_execute_playbook, remove_configuration, - clean_up_ipam, nokia_router_subscription_factory, faker, data_config_filename, @@ -34,7 +25,6 @@ def test_terminate_router_full_success( router_termination_input_form_data = { "tt_number": faker.tt_number(), "remove_configuration": remove_configuration, - "clean_up_ipam": clean_up_ipam, } lso_interaction_count = 2 if remove_configuration else 0 @@ -53,5 +43,5 @@ def test_terminate_router_full_success( assert subscription.status == "terminated" assert mock_delete_device.call_count == 1 - assert mock_delete_host_by_ip.call_count == (1 if clean_up_ipam else 0) + assert mock_delete_host_by_ip.call_count == 1 assert mock_execute_playbook.call_count == lso_interaction_count diff --git a/test/workflows/shared/__init__.py b/test/workflows/shared/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/test/workflows/shared/cancel_subscription.py b/test/workflows/shared/cancel_subscription.py deleted file mode 100644 index 9963b8f21066cfd02408a819b8ce2ae5facf8cba..0000000000000000000000000000000000000000 --- a/test/workflows/shared/cancel_subscription.py +++ /dev/null @@ -1,30 +0,0 @@ -import pytest -from orchestrator.domain.base import SubscriptionModel -from orchestrator.types import SubscriptionLifecycle - -from test.workflows import assert_complete, extract_state, run_workflow - - -@pytest.mark.parametrize( - "subscription_factory", - [ - "site_subscription_factory", - "juniper_router_subscription_factory", - "nokia_router_subscription_factory", - "iptrunk_subscription_factory", - ], -) -@pytest.mark.workflow() -def test_cancel_workflow_success(subscription_factory, geant_partner, request): - subscription_id = request.getfixturevalue(subscription_factory)( - status=SubscriptionLifecycle.INITIAL, - partner=geant_partner, - ) - initial_site_data = [{"subscription_id": subscription_id}, {}] - result, _, _ = run_workflow("cancel_subscription", initial_site_data) - assert_complete(result) - - state = extract_state(result) - subscription_id = state["subscription_id"] - subscription = SubscriptionModel.from_subscription(subscription_id) - assert subscription.status == "terminated"