Skip to content

Commit 3aad99b

Browse files
committed
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
1 parent 6e87796 commit 3aad99b

File tree

5 files changed

+166
-0
lines changed

5 files changed

+166
-0
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
---
2+
apiVersion: argoproj.io/v1alpha1
3+
kind: Sensor
4+
metadata:
5+
name: nova-oslo-event
6+
annotations:
7+
workflows.argoproj.io/title: Process oslo_events for nova compute
8+
workflows.argoproj.io/description: |+
9+
Triggers on the following Nova Events:
10+
11+
- compute.instance.delete.end which happens when a server is deleted
12+
13+
Resulting code should be very similar to:
14+
15+
```
16+
argo -n argo-events submit --from workflowtemplate/openstack-oslo-event \
17+
-p event-json "JSON-payload"
18+
```
19+
20+
Defined in `components/site-workflows/sensors/sensor-nova-oslo-event.yaml`
21+
spec:
22+
dependencies:
23+
- eventName: notifications
24+
eventSourceName: openstack-nova
25+
name: nova-dep
26+
transform:
27+
# the event is a string-ified JSON so we need to decode it
28+
# replace the whole event body
29+
jq: |
30+
.body = (.body["oslo.message"] | fromjson)
31+
filters:
32+
# applies each of the items in data with 'and' but there's only one
33+
dataLogicalOperator: "and"
34+
data:
35+
- path: "body.event_type"
36+
type: "string"
37+
value:
38+
- "compute.instance.delete.end"
39+
template:
40+
serviceAccountName: sensor-submit-workflow
41+
triggers:
42+
- template:
43+
name: nova-instance-delete
44+
k8s:
45+
operation: create
46+
parameters:
47+
# first parameter is the parsed oslo.message
48+
- dest: spec.arguments.parameters.0.value
49+
src:
50+
dataKey: body
51+
dependencyName: nova-dep
52+
- dest: spec.arguments.parameters.1.value
53+
src:
54+
dataKey: body.payload.instance_id
55+
dependencyName: nova-dep
56+
- dest: spec.arguments.parameters.2.value
57+
src:
58+
dataKey: body.payload.tenant_id
59+
dependencyName: nova-dep
60+
source:
61+
# create a workflow in argo-events prefixed with nova-delete-
62+
resource:
63+
apiVersion: argoproj.io/v1alpha1
64+
kind: Workflow
65+
metadata:
66+
generateName: nova-delete-
67+
namespace: argo-events
68+
spec:
69+
serviceAccountName: workflow
70+
entrypoint: main
71+
# defines the parameters being replaced above
72+
arguments:
73+
parameters:
74+
- name: event-json
75+
- name: instance_id
76+
- name: project_id
77+
templates:
78+
- name: main
79+
steps:
80+
- - name: oslo-events
81+
templateRef:
82+
name: openstack-oslo-event
83+
template: main
84+
arguments:
85+
parameters:
86+
- name: event-json
87+
value: "{{workflow.parameters.event-json}}"
88+
- name: convert-project-id
89+
inline:
90+
script:
91+
image: python:alpine
92+
command: [python]
93+
source: |
94+
import uuid
95+
project_id_without_dashes = "{{workflow.parameters.project_id}}"
96+
print(str(uuid.UUID(project_id_without_dashes)))
97+
98+
- - name: ansible-delete-server-storage
99+
when: "{{steps.oslo-events.outputs.parameters.server_storage_deleted}} == True"
100+
templateRef:
101+
name: ansible-workflow-template
102+
template: ansible-run
103+
arguments:
104+
parameters:
105+
- name: playbook
106+
value: storage_on_server_delete.yml
107+
- name: extra_vars
108+
value: device_id={{steps.oslo-events.outputs.parameters.node_uuid}} instance_id={{workflow.parameters.instance_id}} project_id={{steps.convert-project-id.outputs.result}}
109+
- name: check_mode
110+
value: "true"

python/understack-workflows/understack_workflows/main/openstack_oslo_event.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class NoEventHandlerError(Exception):
7272
"baremetal.portgroup.update.end": ironic_portgroup.handle_portgroup_create_update,
7373
"baremetal.portgroup.delete.end": ironic_portgroup.handle_portgroup_delete,
7474
"baremetal.node.provision_set.end": ironic_node.handle_provision_end,
75+
"compute.instance.delete.end": ironic_node.handle_instance_delete,
7576
"identity.project.created": keystone_project.handle_project_created,
7677
"identity.project.updated": keystone_project.handle_project_updated,
7778
"identity.project.deleted": keystone_project.handle_project_deleted,

python/understack-workflows/understack_workflows/oslo_event/ironic_node.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,51 @@ def create_volume_connector(conn: Connection, event: IronicProvisionSetEvent):
9292

9393
def instance_nqn(instance_id: UUID):
9494
return f"nqn.2014-08.org.nvmexpress:uuid:{instance_id}"
95+
96+
97+
def handle_instance_delete(conn: Connection, _: Nautobot, event_data: dict) -> int:
98+
"""Operates on a Nova instance delete event to clean up storage networking."""
99+
payload = event_data.get("payload", {})
100+
instance_uuid = payload.get("instance_id")
101+
102+
if not instance_uuid:
103+
logger.error("No instance_id found in delete event payload")
104+
return 1
105+
106+
logger.info("Processing instance delete for %s", instance_uuid)
107+
108+
# Get the server to find the node_uuid
109+
try:
110+
server = conn.get_server_by_id(instance_uuid)
111+
if not server:
112+
logger.warning("Server %s not found, may already be deleted", instance_uuid)
113+
save_output("server_storage_deleted", "True")
114+
save_output("node_uuid", "unknown")
115+
save_output("instance_uuid", str(instance_uuid))
116+
return 0
117+
118+
# Check if this server had storage enabled
119+
if server.metadata.get("storage") != "wanted":
120+
logger.info("Server %s did not have storage enabled, skipping cleanup", instance_uuid)
121+
save_output("server_storage_deleted", "False")
122+
return 0
123+
124+
# Get node_uuid from the server's hypervisor_hostname or other field
125+
# The node_uuid might be in server properties
126+
node_uuid = getattr(server, 'hypervisor_hostname', None) or getattr(server, 'OS-EXT-SRV-ATTR:hypervisor_hostname', None)
127+
128+
logger.info("Marking server storage for deletion: instance=%s, node=%s", instance_uuid, node_uuid)
129+
save_output("server_storage_deleted", "True")
130+
save_output("node_uuid", str(node_uuid) if node_uuid else "unknown")
131+
save_output("instance_uuid", str(instance_uuid))
132+
133+
# Get project/lessee info
134+
project_id = server.project_id
135+
if project_id:
136+
save_output("project_id", project_id)
137+
138+
return 0
139+
140+
except Exception as e:
141+
logger.exception("Error processing instance delete: %s", e)
142+
return 1

workflows/argo-events/workflowtemplates/ansible-run.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ spec:
1919
default: "var=default"
2020
- name: inventory_file
2121
default: inventory/in-cluster/01-nautobot.yaml
22+
- name: check_mode
23+
default: "false"
2224
container:
2325
image: ghcr.io/rss-engineering/undercloud-nautobot/ansible:latest
2426
command: [ansible-playbook]
@@ -29,6 +31,7 @@ spec:
2931
- "-i"
3032
- "{{ inputs.parameters.inventory_file }}"
3133
- "-vvv"
34+
- "{{- if eq inputs.parameters.check_mode \"true\" }}--check{{- end }}"
3235
env:
3336
- name: NAUTOBOT_TOKEN
3437
valueFrom:

workflows/argo-events/workflowtemplates/openstack-oslo-event.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ spec:
8080
valueFrom:
8181
path: /var/run/argo/output.svm_created
8282
default: "False"
83+
- name: server_storage_deleted
84+
valueFrom:
85+
path: /var/run/argo/output.server_storage_deleted
86+
default: "False"
8387
- name: project_tags
8488
valueFrom:
8589
path: /var/run/argo/output.project_tags

0 commit comments

Comments
 (0)