From 3aad99b4465e2398fdb6913ffd1be90371059cd2 Mon Sep 17 00:00:00 2001 From: Amir Chatur Date: Fri, 21 Nov 2025 14:03:27 -0600 Subject: [PATCH] feat: add storage deletion workflow with conditional execution Add storage cleanup steps to keystone oslo event sensor with proper conditional execution based on event type. Centralize deletion logic in openstack-oslo-event workflow template output parameters. - Add ansible-delete-server step triggered by server_storage_delete - Pass node_uuid as device_id to server deletion playbook - Added oslo sensor for nova for delete - Added handler and registered it --- .../sensors/sensor-nova-oslo-event.yaml | 110 ++++++++++++++++++ .../main/openstack_oslo_event.py | 1 + .../oslo_event/ironic_node.py | 48 ++++++++ .../workflowtemplates/ansible-run.yaml | 3 + .../openstack-oslo-event.yaml | 4 + 5 files changed, 166 insertions(+) create mode 100644 components/site-workflows/sensors/sensor-nova-oslo-event.yaml diff --git a/components/site-workflows/sensors/sensor-nova-oslo-event.yaml b/components/site-workflows/sensors/sensor-nova-oslo-event.yaml new file mode 100644 index 000000000..30b455dd1 --- /dev/null +++ b/components/site-workflows/sensors/sensor-nova-oslo-event.yaml @@ -0,0 +1,110 @@ +--- +apiVersion: argoproj.io/v1alpha1 +kind: Sensor +metadata: + name: nova-oslo-event + annotations: + workflows.argoproj.io/title: Process oslo_events for nova compute + workflows.argoproj.io/description: |+ + Triggers on the following Nova Events: + + - compute.instance.delete.end which happens when a server is deleted + + Resulting code should be very similar to: + + ``` + argo -n argo-events submit --from workflowtemplate/openstack-oslo-event \ + -p event-json "JSON-payload" + ``` + + Defined in `components/site-workflows/sensors/sensor-nova-oslo-event.yaml` +spec: + dependencies: + - eventName: notifications + eventSourceName: openstack-nova + name: nova-dep + transform: + # the event is a string-ified JSON so we need to decode it + # replace the whole event body + jq: | + .body = (.body["oslo.message"] | fromjson) + filters: + # applies each of the items in data with 'and' but there's only one + dataLogicalOperator: "and" + data: + - path: "body.event_type" + type: "string" + value: + - "compute.instance.delete.end" + template: + serviceAccountName: sensor-submit-workflow + triggers: + - template: + name: nova-instance-delete + k8s: + operation: create + parameters: + # first parameter is the parsed oslo.message + - dest: spec.arguments.parameters.0.value + src: + dataKey: body + dependencyName: nova-dep + - dest: spec.arguments.parameters.1.value + src: + dataKey: body.payload.instance_id + dependencyName: nova-dep + - dest: spec.arguments.parameters.2.value + src: + dataKey: body.payload.tenant_id + dependencyName: nova-dep + source: + # create a workflow in argo-events prefixed with nova-delete- + resource: + apiVersion: argoproj.io/v1alpha1 + kind: Workflow + metadata: + generateName: nova-delete- + namespace: argo-events + spec: + serviceAccountName: workflow + entrypoint: main + # defines the parameters being replaced above + arguments: + parameters: + - name: event-json + - name: instance_id + - name: project_id + templates: + - name: main + steps: + - - name: oslo-events + templateRef: + name: openstack-oslo-event + template: main + arguments: + parameters: + - name: event-json + value: "{{workflow.parameters.event-json}}" + - name: convert-project-id + inline: + script: + image: python:alpine + command: [python] + source: | + import uuid + project_id_without_dashes = "{{workflow.parameters.project_id}}" + print(str(uuid.UUID(project_id_without_dashes))) + + - - name: ansible-delete-server-storage + when: "{{steps.oslo-events.outputs.parameters.server_storage_deleted}} == True" + templateRef: + name: ansible-workflow-template + template: ansible-run + arguments: + parameters: + - name: playbook + value: storage_on_server_delete.yml + - name: extra_vars + value: device_id={{steps.oslo-events.outputs.parameters.node_uuid}} instance_id={{workflow.parameters.instance_id}} project_id={{steps.convert-project-id.outputs.result}} + - name: check_mode + value: "true" diff --git a/python/understack-workflows/understack_workflows/main/openstack_oslo_event.py b/python/understack-workflows/understack_workflows/main/openstack_oslo_event.py index 16e0e5893..eda931a64 100644 --- a/python/understack-workflows/understack_workflows/main/openstack_oslo_event.py +++ b/python/understack-workflows/understack_workflows/main/openstack_oslo_event.py @@ -72,6 +72,7 @@ class NoEventHandlerError(Exception): "baremetal.portgroup.update.end": ironic_portgroup.handle_portgroup_create_update, "baremetal.portgroup.delete.end": ironic_portgroup.handle_portgroup_delete, "baremetal.node.provision_set.end": ironic_node.handle_provision_end, + "compute.instance.delete.end": ironic_node.handle_instance_delete, "identity.project.created": keystone_project.handle_project_created, "identity.project.updated": keystone_project.handle_project_updated, "identity.project.deleted": keystone_project.handle_project_deleted, diff --git a/python/understack-workflows/understack_workflows/oslo_event/ironic_node.py b/python/understack-workflows/understack_workflows/oslo_event/ironic_node.py index 525dd7290..9df5b59ca 100644 --- a/python/understack-workflows/understack_workflows/oslo_event/ironic_node.py +++ b/python/understack-workflows/understack_workflows/oslo_event/ironic_node.py @@ -92,3 +92,51 @@ def create_volume_connector(conn: Connection, event: IronicProvisionSetEvent): def instance_nqn(instance_id: UUID): return f"nqn.2014-08.org.nvmexpress:uuid:{instance_id}" + + +def handle_instance_delete(conn: Connection, _: Nautobot, event_data: dict) -> int: + """Operates on a Nova instance delete event to clean up storage networking.""" + payload = event_data.get("payload", {}) + instance_uuid = payload.get("instance_id") + + if not instance_uuid: + logger.error("No instance_id found in delete event payload") + return 1 + + logger.info("Processing instance delete for %s", instance_uuid) + + # Get the server to find the node_uuid + try: + server = conn.get_server_by_id(instance_uuid) + if not server: + logger.warning("Server %s not found, may already be deleted", instance_uuid) + save_output("server_storage_deleted", "True") + save_output("node_uuid", "unknown") + save_output("instance_uuid", str(instance_uuid)) + return 0 + + # Check if this server had storage enabled + if server.metadata.get("storage") != "wanted": + logger.info("Server %s did not have storage enabled, skipping cleanup", instance_uuid) + save_output("server_storage_deleted", "False") + return 0 + + # Get node_uuid from the server's hypervisor_hostname or other field + # The node_uuid might be in server properties + node_uuid = getattr(server, 'hypervisor_hostname', None) or getattr(server, 'OS-EXT-SRV-ATTR:hypervisor_hostname', None) + + logger.info("Marking server storage for deletion: instance=%s, node=%s", instance_uuid, node_uuid) + save_output("server_storage_deleted", "True") + save_output("node_uuid", str(node_uuid) if node_uuid else "unknown") + save_output("instance_uuid", str(instance_uuid)) + + # Get project/lessee info + project_id = server.project_id + if project_id: + save_output("project_id", project_id) + + return 0 + + except Exception as e: + logger.exception("Error processing instance delete: %s", e) + return 1 diff --git a/workflows/argo-events/workflowtemplates/ansible-run.yaml b/workflows/argo-events/workflowtemplates/ansible-run.yaml index 53ff28f08..5746078c8 100644 --- a/workflows/argo-events/workflowtemplates/ansible-run.yaml +++ b/workflows/argo-events/workflowtemplates/ansible-run.yaml @@ -19,6 +19,8 @@ spec: default: "var=default" - name: inventory_file default: inventory/in-cluster/01-nautobot.yaml + - name: check_mode + default: "false" container: image: ghcr.io/rss-engineering/undercloud-nautobot/ansible:latest command: [ansible-playbook] @@ -29,6 +31,7 @@ spec: - "-i" - "{{ inputs.parameters.inventory_file }}" - "-vvv" + - "{{- if eq inputs.parameters.check_mode \"true\" }}--check{{- end }}" env: - name: NAUTOBOT_TOKEN valueFrom: diff --git a/workflows/argo-events/workflowtemplates/openstack-oslo-event.yaml b/workflows/argo-events/workflowtemplates/openstack-oslo-event.yaml index 7b9094b18..34b9ba236 100644 --- a/workflows/argo-events/workflowtemplates/openstack-oslo-event.yaml +++ b/workflows/argo-events/workflowtemplates/openstack-oslo-event.yaml @@ -80,6 +80,10 @@ spec: valueFrom: path: /var/run/argo/output.svm_created default: "False" + - name: server_storage_deleted + valueFrom: + path: /var/run/argo/output.server_storage_deleted + default: "False" - name: project_tags valueFrom: path: /var/run/argo/output.project_tags