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 ef7dd90e6..d8e5791b0 100644 --- a/python/understack-workflows/understack_workflows/main/openstack_oslo_event.py +++ b/python/understack-workflows/understack_workflows/main/openstack_oslo_event.py @@ -79,6 +79,7 @@ class NoEventHandlerError(Exception): ironic_node.handle_provision_end, nautobot_device_sync.handle_node_event, ], + "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 b436134f3..588dc18ce 100644 --- a/python/understack-workflows/understack_workflows/oslo_event/ironic_node.py +++ b/python/understack-workflows/understack_workflows/oslo_event/ironic_node.py @@ -119,3 +119,41 @@ def create_volume_connector(conn: Connection, event: IronicProvisionSetEvent): def instance_nqn(instance_id: str | None) -> str: 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") + project_id = payload.get("tenant_id") + + if not instance_uuid or not project_id: + logger.error("No instance_id found in delete event payload") + return 1 + + logger.info("Processing instance delete for {}, Tenant {}".format(instance_uuid, project_id)) + + # Get the server to find the node_uuid + try: + + # Check if this server had storage enabled + if payload.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 = payload.get("node") + + 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)) + 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