From 597a0f76d436cf5ea52b9c27d0609b310b61726e Mon Sep 17 00:00:00 2001 From: Steve Keay Date: Tue, 25 Feb 2025 18:51:19 +0000 Subject: [PATCH 1/3] Create an OUTSIDE Network in Neutron for each new project (tenant) This can only be done by admin, so we take care of it here. --- .../main/sync_keystone.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/python/understack-workflows/understack_workflows/main/sync_keystone.py b/python/understack-workflows/understack_workflows/main/sync_keystone.py index 3a326b751..205670dcf 100644 --- a/python/understack-workflows/understack_workflows/main/sync_keystone.py +++ b/python/understack-workflows/understack_workflows/main/sync_keystone.py @@ -19,6 +19,8 @@ _EXIT_API_ERROR = 1 _EXIT_EVENT_UNKNOWN = 2 +OUTSIDE_NETWORK_NAME = "OUTSIDE" + class Event(StrEnum): ProjectCreate = "identity.project.created" @@ -70,6 +72,41 @@ def is_valid_domain( return ret +def _create_outside_network(conn: Connection, project_id: uuid.UUID): + payload = _outside_network_payload(project_id) + network = conn.network.find_network(**payload) + if network: + logger.info( + "%s Network %s already exists for this tenant", + OUTSIDE_NETWORK_NAME, + network.id, + ) + else: + payload.update(name=payload.pop("name_or_id")) + network = conn.network.create_network(**payload) + logger.info( + "Created %s Network %s for tenant", OUTSIDE_NETWORK_NAME, network.id + ) + + +def _delete_outside_network(conn: Connection, project_id: uuid.UUID): + payload = _outside_network_payload(project_id) + network = conn.network.find_network(**payload) + if network: + conn.delete_network(network.id) + logger.info( + "Deleted %s Network %s for this tenant", OUTSIDE_NETWORK_NAME, network.id + ) + + +def _outside_network_payload(project_id: uuid.UUID) -> dict: + return { + "project_id": project_id, + "name_or_id": OUTSIDE_NETWORK_NAME, + "router:external": True, + } + + def handle_project_create( conn: Connection, nautobot: Nautobot, project_id: uuid.UUID ) -> int: @@ -80,6 +117,7 @@ def handle_project_create( ten = ten_api.create( id=str(project_id), name=project.name, description=project.description ) + _create_outside_network(conn, project_id) except Exception: logger.exception( "Unable to create project %s / %s", str(project_id), project.name @@ -113,6 +151,8 @@ def handle_project_update( project_id, existing_tenant.last_updated, # type: ignore ) + + _create_outside_network(conn, project_id) except Exception: logger.exception( "Unable to update project %s / %s", str(project_id), project.name @@ -129,6 +169,8 @@ def handle_project_delete( if not ten: logger.warning("tenant %s does not exist, nothing to delete", project_id) return _EXIT_SUCCESS + + _delete_outside_network(conn, project_id) ten.delete() # type: ignore logger.info("deleted tenant %s", project_id) return _EXIT_SUCCESS From ca7b6d362629c800bdb8afeb396ef4a1e2806596 Mon Sep 17 00:00:00 2001 From: Steve Keay Date: Tue, 25 Feb 2025 19:58:40 +0000 Subject: [PATCH 2/3] Disable type checking around missing openstack type annotations --- .../understack_workflows/main/sync_keystone.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/understack-workflows/understack_workflows/main/sync_keystone.py b/python/understack-workflows/understack_workflows/main/sync_keystone.py index 205670dcf..3777d43f1 100644 --- a/python/understack-workflows/understack_workflows/main/sync_keystone.py +++ b/python/understack-workflows/understack_workflows/main/sync_keystone.py @@ -74,7 +74,7 @@ def is_valid_domain( def _create_outside_network(conn: Connection, project_id: uuid.UUID): payload = _outside_network_payload(project_id) - network = conn.network.find_network(**payload) + network = conn.network.find_network(**payload) # type: ignore if network: logger.info( "%s Network %s already exists for this tenant", @@ -83,7 +83,7 @@ def _create_outside_network(conn: Connection, project_id: uuid.UUID): ) else: payload.update(name=payload.pop("name_or_id")) - network = conn.network.create_network(**payload) + network = conn.network.create_network(**payload) # type: ignore logger.info( "Created %s Network %s for tenant", OUTSIDE_NETWORK_NAME, network.id ) @@ -91,7 +91,7 @@ def _create_outside_network(conn: Connection, project_id: uuid.UUID): def _delete_outside_network(conn: Connection, project_id: uuid.UUID): payload = _outside_network_payload(project_id) - network = conn.network.find_network(**payload) + network = conn.network.find_network(**payload) # type: ignore if network: conn.delete_network(network.id) logger.info( From 4b2c477bf894597075ba97a8708bbff476034992 Mon Sep 17 00:00:00 2001 From: Steve Keay Date: Wed, 26 Feb 2025 08:29:24 +0000 Subject: [PATCH 3/3] Create OUTSIDE Network with rbac policy instead of router:external flag Adding the rbac policy automagically updates the router type, setting the router:external flag to True, so we also change our search criterion to match existing Networks regardless of the router:external value. --- .../main/sync_keystone.py | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/python/understack-workflows/understack_workflows/main/sync_keystone.py b/python/understack-workflows/understack_workflows/main/sync_keystone.py index 3777d43f1..56dea9e1f 100644 --- a/python/understack-workflows/understack_workflows/main/sync_keystone.py +++ b/python/understack-workflows/understack_workflows/main/sync_keystone.py @@ -73,8 +73,7 @@ def is_valid_domain( def _create_outside_network(conn: Connection, project_id: uuid.UUID): - payload = _outside_network_payload(project_id) - network = conn.network.find_network(**payload) # type: ignore + network = _find_outside_network(conn, project_id) if network: logger.info( "%s Network %s already exists for this tenant", @@ -82,16 +81,25 @@ def _create_outside_network(conn: Connection, project_id: uuid.UUID): network.id, ) else: - payload.update(name=payload.pop("name_or_id")) + payload = { + "project_id": project_id, + "name": OUTSIDE_NETWORK_NAME, + "router:external": False, + } network = conn.network.create_network(**payload) # type: ignore logger.info( "Created %s Network %s for tenant", OUTSIDE_NETWORK_NAME, network.id ) + conn.network.create_rbac_policy( # type: ignore + object_type="network", + object_id=network.id, + action="access_as_external", + target_project_id=project_id, + ) def _delete_outside_network(conn: Connection, project_id: uuid.UUID): - payload = _outside_network_payload(project_id) - network = conn.network.find_network(**payload) # type: ignore + network = _find_outside_network(conn, project_id) if network: conn.delete_network(network.id) logger.info( @@ -99,12 +107,11 @@ def _delete_outside_network(conn: Connection, project_id: uuid.UUID): ) -def _outside_network_payload(project_id: uuid.UUID) -> dict: - return { - "project_id": project_id, - "name_or_id": OUTSIDE_NETWORK_NAME, - "router:external": True, - } +def _find_outside_network(conn, project_id): + return conn.network.find_network( # type: ignore + project_id=project_id, + name_or_id=OUTSIDE_NETWORK_NAME, + ) def handle_project_create(