Skip to content

Commit 8490db6

Browse files
authored
Merge pull request #1172 from rackerlabs/trigger-ansible-on-svm
feat: Trigger Ansible on SVM creation
2 parents 390b6d0 + fe0811e commit 8490db6

File tree

7 files changed

+263
-35
lines changed

7 files changed

+263
-35
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,4 @@ repos:
116116
# entry: bash -c 'trufflehog --no-update git file://. --since-commit HEAD --results=verified,unknown --fail'
117117
entry: bash -c 'docker run --rm -v "$(pwd):/workdir" -i --rm trufflesecurity/trufflehog:latest git file:///workdir --since-commit HEAD --results=verified,unknown --fail'
118118
language: system
119-
stages: ["pre-commit", "pre-push"]
119+
stages: ["pre-push"]

python/understack-workflows/pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ testpaths = [
7474
]
7575
filterwarnings = [
7676
# sushy
77-
"ignore:pkg_resources is deprecated as an API.:DeprecationWarning"
77+
"ignore:pkg_resources is deprecated as an API.:DeprecationWarning",
78+
# netapp
79+
"ignore:.*Number` field should not be instantiated.*"
7880
]
7981

8082
[tool.ruff]

python/understack-workflows/tests/test_keystone_project.py

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -123,22 +123,29 @@ def test_handle_project_created_wrong_event_type(self, mock_conn, mock_nautobot)
123123
assert result == 1
124124

125125
@patch("understack_workflows.oslo_event.keystone_project._keystone_project_tags")
126+
@patch("builtins.open")
126127
def test_handle_project_created_no_svm_tag(
127-
self, mock_tags, mock_conn, mock_nautobot, valid_event_data
128+
self, mock_open, mock_tags, mock_conn, mock_nautobot, valid_event_data
128129
):
129130
"""Test handling project creation without SVM tag."""
130131
mock_tags.return_value = ["tag1", "tag2"]
131132

132-
with pytest.raises(SystemExit) as exc_info:
133-
handle_project_created(mock_conn, mock_nautobot, valid_event_data)
134-
135-
assert exc_info.value.code == 0
133+
result = handle_project_created(mock_conn, mock_nautobot, valid_event_data)
134+
assert result == 0
136135
mock_tags.assert_called_once_with(mock_conn, "test-project-123")
136+
mock_open.assert_called_with("/var/run/argo/output.svm_enabled", "w")
137137

138138
@patch("understack_workflows.oslo_event.keystone_project.NetAppManager")
139139
@patch("understack_workflows.oslo_event.keystone_project._keystone_project_tags")
140+
@patch("builtins.open")
140141
def test_handle_project_created_with_svm_tag(
141-
self, mock_tags, mock_netapp_class, mock_conn, mock_nautobot, valid_event_data
142+
self,
143+
mock_open,
144+
mock_tags,
145+
mock_netapp_class,
146+
mock_conn,
147+
mock_nautobot,
148+
valid_event_data,
142149
):
143150
"""Test successful project creation handling with SVM tag."""
144151
mock_tags.return_value = ["tag1", SVM_PROJECT_TAG, "tag2"]
@@ -158,11 +165,19 @@ def test_handle_project_created_with_svm_tag(
158165
volume_size=VOLUME_SIZE,
159166
aggregate_name=AGGREGATE_NAME,
160167
)
168+
mock_open.assert_called()
161169

162170
@patch("understack_workflows.oslo_event.keystone_project.NetAppManager")
163171
@patch("understack_workflows.oslo_event.keystone_project._keystone_project_tags")
172+
@patch("builtins.open")
164173
def test_handle_project_created_netapp_manager_failure(
165-
self, mock_tags, mock_netapp_class, mock_conn, mock_nautobot, valid_event_data
174+
self,
175+
mock_open,
176+
mock_tags,
177+
mock_netapp_class,
178+
mock_conn,
179+
mock_nautobot,
180+
valid_event_data,
166181
):
167182
"""Test handling when NetAppManager creation fails."""
168183
mock_tags.return_value = [SVM_PROJECT_TAG]
@@ -173,11 +188,19 @@ def test_handle_project_created_netapp_manager_failure(
173188

174189
mock_tags.assert_called_once_with(mock_conn, "test-project-123")
175190
mock_netapp_class.assert_called_once()
191+
mock_open.assert_called()
176192

177193
@patch("understack_workflows.oslo_event.keystone_project.NetAppManager")
178194
@patch("understack_workflows.oslo_event.keystone_project._keystone_project_tags")
195+
@patch("builtins.open")
179196
def test_handle_project_created_svm_creation_failure(
180-
self, mock_tags, mock_netapp_class, mock_conn, mock_nautobot, valid_event_data
197+
self,
198+
mock_open,
199+
mock_tags,
200+
mock_netapp_class,
201+
mock_conn,
202+
mock_nautobot,
203+
valid_event_data,
181204
):
182205
"""Test handling when SVM creation fails."""
183206
mock_tags.return_value = [SVM_PROJECT_TAG]
@@ -196,8 +219,15 @@ def test_handle_project_created_svm_creation_failure(
196219

197220
@patch("understack_workflows.oslo_event.keystone_project.NetAppManager")
198221
@patch("understack_workflows.oslo_event.keystone_project._keystone_project_tags")
222+
@patch("builtins.open")
199223
def test_handle_project_created_volume_creation_failure(
200-
self, mock_tags, mock_netapp_class, mock_conn, mock_nautobot, valid_event_data
224+
self,
225+
mock_open,
226+
mock_tags,
227+
mock_netapp_class,
228+
mock_conn,
229+
mock_nautobot,
230+
valid_event_data,
201231
):
202232
"""Test handling when volume creation fails."""
203233
mock_tags.return_value = [SVM_PROJECT_TAG]
@@ -232,8 +262,9 @@ def test_handle_project_created_invalid_event_data(self, mock_conn, mock_nautobo
232262
handle_project_created(mock_conn, mock_nautobot, invalid_event_data)
233263

234264
@patch("understack_workflows.oslo_event.keystone_project._keystone_project_tags")
265+
@patch("builtins.open")
235266
def test_handle_project_created_constants_used(
236-
self, mock_tags, mock_conn, mock_nautobot, valid_event_data
267+
self, mock_open, mock_tags, mock_conn, mock_nautobot, valid_event_data
237268
):
238269
"""Test constants used for aggregate name and volume size."""
239270
mock_tags.return_value = [SVM_PROJECT_TAG]
@@ -253,6 +284,7 @@ def test_handle_project_created_constants_used(
253284
)
254285
mock_netapp_manager.create_volume.assert_called_once_with(
255286
project_id="test-project-123",
256-
volume_size="1GB", # VOLUME_SIZE constant
287+
volume_size="514GB", # VOLUME_SIZE constant
257288
aggregate_name="aggr02_n02_NVME", # AGGREGATE_NAME constant
258289
)
290+
mock_open.assert_called()

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

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111

1212
SVM_PROJECT_TAG = "UNDERSTACK_SVM"
1313
AGGREGATE_NAME = "aggr02_n02_NVME"
14-
VOLUME_SIZE = "1GB"
14+
VOLUME_SIZE = "514GB"
15+
OUTPUT_BASE_PATH = "/var/run/argo"
1516

1617

1718
@dataclass
@@ -47,23 +48,39 @@ def handle_project_created(
4748
conn: Connection, _nautobot: Nautobot, event_data: dict
4849
) -> int:
4950
if event_data.get("event_type") != "identity.project.created":
51+
logger.error("Received event that is not identity.project.created")
5052
return 1
5153

5254
event = KeystoneProjectEvent.from_event_dict(event_data)
5355
logger.info("Starting ONTAP SVM and Volume creation workflow.")
5456
tags = _keystone_project_tags(conn, event.project_id)
5557
logger.debug("Project %s has tags: %s", event.project_id, tags)
56-
if SVM_PROJECT_TAG not in tags:
58+
59+
project_is_svm_enabled = SVM_PROJECT_TAG in tags
60+
_save_output("svm_enabled", str(project_is_svm_enabled))
61+
62+
if not project_is_svm_enabled:
5763
logger.info("The %s is missing, not creating SVM.", SVM_PROJECT_TAG)
58-
exit(0)
59-
60-
netapp_manager = NetAppManager()
61-
netapp_manager.create_svm(
62-
project_id=event.project_id, aggregate_name=AGGREGATE_NAME
63-
)
64-
netapp_manager.create_volume(
65-
project_id=event.project_id,
66-
volume_size=VOLUME_SIZE,
67-
aggregate_name=AGGREGATE_NAME,
68-
)
64+
return 0
65+
66+
svm_name = None
67+
try:
68+
netapp_manager = NetAppManager()
69+
netapp_manager.create_svm(
70+
project_id=event.project_id, aggregate_name=AGGREGATE_NAME
71+
)
72+
svm_name = netapp_manager.create_volume(
73+
project_id=event.project_id,
74+
volume_size=VOLUME_SIZE,
75+
aggregate_name=AGGREGATE_NAME,
76+
)
77+
finally:
78+
if not svm_name:
79+
svm_name = "not_returned"
80+
_save_output("svm_name", svm_name)
6981
return 0
82+
83+
84+
def _save_output(name, value):
85+
with open(f"{OUTPUT_BASE_PATH}/output.{name}", "w") as f:
86+
return f.write(value)
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import os
5+
import requests
6+
import sys
7+
import urllib3
8+
from requests.auth import HTTPBasicAuth
9+
10+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
11+
12+
13+
def delete_volume(hostname, login, password, project_id):
14+
"""Delete volume named vol_$project_id"""
15+
volume_name = f"vol_{project_id}"
16+
url = f"https://{hostname}/api/storage/volumes"
17+
18+
# First, find the volume by name
19+
params = {"name": volume_name}
20+
response = requests.get(
21+
url, auth=HTTPBasicAuth(login, password), params=params, verify=False
22+
)
23+
24+
if response.status_code != 200:
25+
print(
26+
f"Error finding volume {volume_name}: {response.status_code} - {response.text}"
27+
)
28+
return False
29+
30+
volumes = response.json().get("records", [])
31+
if not volumes:
32+
print(f"Volume {volume_name} not found")
33+
return False
34+
35+
volume_uuid = volumes[0]["uuid"]
36+
37+
# Delete the volume
38+
delete_url = f"{url}/{volume_uuid}"
39+
response = requests.delete(
40+
delete_url, auth=HTTPBasicAuth(login, password), verify=False
41+
)
42+
43+
if response.status_code == 202:
44+
print(f"Volume {volume_name} deletion initiated successfully")
45+
return True
46+
else:
47+
print(
48+
f"Error deleting volume {volume_name}: {response.status_code} - {response.text}"
49+
)
50+
return False
51+
52+
53+
def delete_svm(hostname, login, password, project_id):
54+
"""Delete SVM named os-$project_id"""
55+
svm_name = f"os-{project_id}"
56+
url = f"https://{hostname}/api/svm/svms"
57+
58+
# First, find the SVM by name
59+
params = {"name": svm_name}
60+
response = requests.get(
61+
url, auth=HTTPBasicAuth(login, password), params=params, verify=False
62+
)
63+
64+
if response.status_code != 200:
65+
print(f"Error finding SVM {svm_name}: {response.status_code} - {response.text}")
66+
return False
67+
68+
svms = response.json().get("records", [])
69+
if not svms:
70+
print(f"SVM {svm_name} not found")
71+
return False
72+
73+
svm_uuid = svms[0]["uuid"]
74+
75+
# Delete the SVM
76+
delete_url = f"{url}/{svm_uuid}"
77+
response = requests.delete(
78+
delete_url, auth=HTTPBasicAuth(login, password), verify=False
79+
)
80+
81+
if response.status_code == 202:
82+
print(f"SVM {svm_name} deletion initiated successfully")
83+
return True
84+
else:
85+
print(
86+
f"Error deleting SVM {svm_name}: {response.status_code} - {response.text}"
87+
)
88+
return False
89+
90+
91+
def main():
92+
parser = argparse.ArgumentParser(
93+
description="Delete NetApp ONTAP volume and SVM for a project"
94+
)
95+
parser.add_argument("project_id", help="Project ID")
96+
97+
args = parser.parse_args()
98+
99+
# Get credentials from environment variables
100+
hostname = os.getenv("NETAPP_HOST")
101+
login = os.getenv("NETAPP_LOGIN")
102+
password = os.getenv("NETAPP_PASSWORD")
103+
104+
if not hostname or not login or not password:
105+
print(
106+
"Error: NETAPP_HOST, NETAPP_LOGIN, and NETAPP_PASSWORD environment variables must be set"
107+
)
108+
sys.exit(1)
109+
110+
print(f"Deleting resources for project: {args.project_id}")
111+
print(f"Connecting to ONTAP: {hostname}")
112+
113+
# Delete volume first
114+
volume_success = delete_volume(hostname, login, password, args.project_id)
115+
116+
# Delete SVM
117+
svm_success = delete_svm(hostname, login, password, args.project_id)
118+
119+
if volume_success and svm_success:
120+
print("All resources deleted successfully")
121+
sys.exit(0)
122+
else:
123+
print("Some resources failed to delete")
124+
sys.exit(1)
125+
126+
127+
if __name__ == "__main__":
128+
main()

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,11 @@ spec:
6262
- name: netapp-ini
6363
secret:
6464
secretName: netapp-config
65+
outputs:
66+
parameters:
67+
- name: exit_code
68+
valueFrom:
69+
path: /var/run/argo/output.exit_code
70+
- name: msg
71+
valueFrom:
72+
path: /var/run/argo/output.msg

0 commit comments

Comments
 (0)