waldur-site-agent-mup 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- waldur_site_agent_mup-0.7.0/.gitignore +6 -0
- waldur_site_agent_mup-0.7.0/.python-version +1 -0
- waldur_site_agent_mup-0.7.0/PKG-INFO +10 -0
- waldur_site_agent_mup-0.7.0/README.md +1 -0
- waldur_site_agent_mup-0.7.0/pyproject.toml +21 -0
- waldur_site_agent_mup-0.7.0/tests/test_mup_backend.py +549 -0
- waldur_site_agent_mup-0.7.0/tests/test_mup_client.py +580 -0
- waldur_site_agent_mup-0.7.0/tests/test_mup_fixtures.py +279 -0
- waldur_site_agent_mup-0.7.0/tests/test_mup_order_processing.py +617 -0
- waldur_site_agent_mup-0.7.0/waldur_site_agent_mup/__init__.py +1 -0
- waldur_site_agent_mup-0.7.0/waldur_site_agent_mup/backend.py +744 -0
- waldur_site_agent_mup-0.7.0/waldur_site_agent_mup/client.py +416 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.9
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: waldur-site-agent-mup
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: MUP plugin for Waldur Site Agent
|
|
5
|
+
Author-email: OpenNode Team <info@opennodecloud.com>
|
|
6
|
+
Requires-Python: <4,>=3.9
|
|
7
|
+
Requires-Dist: waldur-site-agent==0.7.0
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# MUP plugin for Waldur Site Agent
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# MUP plugin for Waldur Site Agent
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "waldur-site-agent-mup"
|
|
3
|
+
version = "0.7.0"
|
|
4
|
+
description = "MUP plugin for Waldur Site Agent"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [{ name = "OpenNode Team", email = "info@opennodecloud.com" }]
|
|
7
|
+
requires-python = ">=3.9, <4"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"waldur-site-agent==0.7.0"
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[build-system]
|
|
13
|
+
requires = ["hatchling"]
|
|
14
|
+
build-backend = "hatchling.build"
|
|
15
|
+
|
|
16
|
+
[tool.uv.sources]
|
|
17
|
+
waldur-site-agent = { workspace = true }
|
|
18
|
+
|
|
19
|
+
# Entry points for exporting backends
|
|
20
|
+
[project.entry-points."waldur_site_agent.backends"]
|
|
21
|
+
mup = "waldur_site_agent_mup.backend:MUPBackend"
|
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import uuid
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from waldur_api_client.models.offering_user import OfferingUser
|
|
7
|
+
from waldur_api_client.models.project_user import ProjectUser
|
|
8
|
+
from waldur_api_client.models.resource import Resource as WaldurResource
|
|
9
|
+
from waldur_api_client.models.resource_limits import ResourceLimits
|
|
10
|
+
from waldur_api_client.types import Unset
|
|
11
|
+
|
|
12
|
+
from waldur_site_agent.backend.exceptions import BackendError
|
|
13
|
+
from waldur_site_agent_mup.backend import MUPBackend
|
|
14
|
+
from waldur_site_agent_mup.client import MUPError
|
|
15
|
+
from waldur_site_agent.backend.structures import BackendResourceInfo
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MUPBackendTest(unittest.TestCase):
|
|
19
|
+
"""Test suite for MUP backend functionality."""
|
|
20
|
+
|
|
21
|
+
def setUp(self) -> None:
|
|
22
|
+
"""Set up test fixtures."""
|
|
23
|
+
self.mup_settings = {
|
|
24
|
+
"api_url": "https://mup-api.example.com/api",
|
|
25
|
+
"username": "test_user",
|
|
26
|
+
"password": "test_password",
|
|
27
|
+
"default_research_field": 1,
|
|
28
|
+
"default_agency": "FCT",
|
|
29
|
+
"project_prefix": "waldur_",
|
|
30
|
+
"allocation_prefix": "alloc_",
|
|
31
|
+
"default_allocation_type": "compute",
|
|
32
|
+
"default_storage_limit": 1000,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
self.mup_components = {
|
|
36
|
+
"cpu": {
|
|
37
|
+
"measured_unit": "core-hours",
|
|
38
|
+
"unit_factor": 1,
|
|
39
|
+
"accounting_type": "limit",
|
|
40
|
+
"label": "CPU Cores",
|
|
41
|
+
"mup_allocation_type": "Deucalion x86_64",
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
self.project_uuid = uuid.uuid4()
|
|
46
|
+
self.resource_uuid = uuid.uuid4()
|
|
47
|
+
self.customer_uuid = uuid.uuid4()
|
|
48
|
+
|
|
49
|
+
self.sample_waldur_resource = WaldurResource(
|
|
50
|
+
uuid=self.resource_uuid,
|
|
51
|
+
name="test-resource",
|
|
52
|
+
project_uuid=self.project_uuid,
|
|
53
|
+
project_name="Test Project",
|
|
54
|
+
offering_uuid=uuid.uuid4(),
|
|
55
|
+
offering_name="MUP Offering",
|
|
56
|
+
limits=ResourceLimits.from_dict({"cpu": 10}),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
self.sample_mup_project = {
|
|
60
|
+
"id": 1,
|
|
61
|
+
"title": "Test Project",
|
|
62
|
+
"description": "A test project",
|
|
63
|
+
"pi": "pi@example.com",
|
|
64
|
+
"grant_number": f"waldur_{self.resource_uuid.hex}",
|
|
65
|
+
"active": True,
|
|
66
|
+
"agency": "FCT",
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
self.sample_mup_allocation = {
|
|
70
|
+
"id": 1,
|
|
71
|
+
"type": "compute",
|
|
72
|
+
"identifier": f"alloc_{self.resource_uuid.hex}",
|
|
73
|
+
"size": 10,
|
|
74
|
+
"used": 0,
|
|
75
|
+
"active": True,
|
|
76
|
+
"project": 1,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# Sample user context for testing
|
|
80
|
+
self.sample_user_context = {
|
|
81
|
+
"team": [
|
|
82
|
+
ProjectUser(
|
|
83
|
+
uuid="user-uuid-1",
|
|
84
|
+
email="pi@example.com",
|
|
85
|
+
username="pi_user",
|
|
86
|
+
full_name="Principal Investigator",
|
|
87
|
+
role="admin",
|
|
88
|
+
url="https://waldur.example.com/api/users/user-uuid-1/",
|
|
89
|
+
expiration_time=None,
|
|
90
|
+
offering_user_username="pi_user",
|
|
91
|
+
)
|
|
92
|
+
],
|
|
93
|
+
"offering_users": [
|
|
94
|
+
OfferingUser(
|
|
95
|
+
username="pi_user",
|
|
96
|
+
user_uuid="user-uuid-1",
|
|
97
|
+
offering_uuid="offering-uuid-1",
|
|
98
|
+
created="2024-01-01T00:00:00Z",
|
|
99
|
+
modified="2024-01-01T00:00:00Z",
|
|
100
|
+
)
|
|
101
|
+
],
|
|
102
|
+
"user_mappings": {
|
|
103
|
+
"user-uuid-1": ProjectUser(
|
|
104
|
+
uuid="user-uuid-1",
|
|
105
|
+
email="pi@example.com",
|
|
106
|
+
username="pi_user",
|
|
107
|
+
full_name="Principal Investigator",
|
|
108
|
+
role="admin",
|
|
109
|
+
url="https://waldur.example.com/api/users/user-uuid-1/",
|
|
110
|
+
expiration_time=None,
|
|
111
|
+
offering_user_username="pi_user",
|
|
112
|
+
)
|
|
113
|
+
},
|
|
114
|
+
"offering_user_mappings": {
|
|
115
|
+
"user-uuid-1": OfferingUser(
|
|
116
|
+
username="pi_user",
|
|
117
|
+
user_uuid="user-uuid-1",
|
|
118
|
+
offering_uuid="offering-uuid-1",
|
|
119
|
+
created="2025-01-01T00:00:00Z",
|
|
120
|
+
modified="2025-01-01T00:00:00Z",
|
|
121
|
+
)
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
126
|
+
def test_init_with_valid_settings(self, mock_client_class) -> None:
|
|
127
|
+
"""Test backend initialization with valid settings."""
|
|
128
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
129
|
+
|
|
130
|
+
assert backend.backend_type == "mup"
|
|
131
|
+
assert backend.project_prefix == "waldur_"
|
|
132
|
+
assert backend.allocation_prefix == "alloc_"
|
|
133
|
+
assert backend.default_research_field == 1
|
|
134
|
+
assert backend.default_agency == "FCT"
|
|
135
|
+
|
|
136
|
+
mock_client_class.assert_called_once_with(
|
|
137
|
+
api_url="https://mup-api.example.com/api",
|
|
138
|
+
username="test_user",
|
|
139
|
+
password="test_password",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def test_init_missing_required_settings(self) -> None:
|
|
143
|
+
"""Test backend initialization fails with missing required settings."""
|
|
144
|
+
incomplete_settings = {"api_url": "https://example.com"}
|
|
145
|
+
|
|
146
|
+
with pytest.raises(ValueError) as context:
|
|
147
|
+
MUPBackend(incomplete_settings, self.mup_components)
|
|
148
|
+
|
|
149
|
+
assert "Missing required setting: username" in str(context.value)
|
|
150
|
+
|
|
151
|
+
def test_init_invalid_accounting_type(self) -> None:
|
|
152
|
+
"""Test backend initialization fails with invalid accounting_type."""
|
|
153
|
+
invalid_components = {
|
|
154
|
+
"cpu": {
|
|
155
|
+
"measured_unit": "core-hours",
|
|
156
|
+
"unit_factor": 1,
|
|
157
|
+
"accounting_type": "usage", # Invalid for MUP
|
|
158
|
+
"label": "CPU Cores",
|
|
159
|
+
"mup_allocation_type": "Deucalion x86_64",
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
with pytest.raises(ValueError) as context:
|
|
164
|
+
MUPBackend(self.mup_settings, invalid_components)
|
|
165
|
+
|
|
166
|
+
assert "MUP backend only supports components with accounting_type='limit'" in str(
|
|
167
|
+
context.value
|
|
168
|
+
)
|
|
169
|
+
assert "Component 'cpu' has accounting_type='usage'" in str(context.value)
|
|
170
|
+
|
|
171
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
172
|
+
def test_ping_success(self, mock_client_class) -> None:
|
|
173
|
+
"""Test successful ping to MUP backend."""
|
|
174
|
+
mock_client = mock_client_class.return_value
|
|
175
|
+
mock_client.get_research_fields.return_value = [{"id": 1, "name": "Computer Science"}]
|
|
176
|
+
|
|
177
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
178
|
+
result = backend.ping()
|
|
179
|
+
|
|
180
|
+
assert result
|
|
181
|
+
mock_client.get_research_fields.assert_called_once()
|
|
182
|
+
|
|
183
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
184
|
+
def test_ping_failure(self, mock_client_class) -> None:
|
|
185
|
+
"""Test ping failure with MUP backend."""
|
|
186
|
+
mock_client = mock_client_class.return_value
|
|
187
|
+
mock_client.get_research_fields.side_effect = MUPError("Connection failed")
|
|
188
|
+
|
|
189
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
190
|
+
result = backend.ping()
|
|
191
|
+
|
|
192
|
+
assert not result
|
|
193
|
+
|
|
194
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
195
|
+
def test_ping_failure_with_exception(self, mock_client_class) -> None:
|
|
196
|
+
"""Test ping failure raises exception when requested."""
|
|
197
|
+
mock_client = mock_client_class.return_value
|
|
198
|
+
mock_client.get_research_fields.side_effect = MUPError("Connection failed")
|
|
199
|
+
|
|
200
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
201
|
+
|
|
202
|
+
with pytest.raises(BackendError):
|
|
203
|
+
backend.ping(raise_exception=True)
|
|
204
|
+
|
|
205
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
206
|
+
def test_list_components(self, mock_client_class) -> None:
|
|
207
|
+
"""Test listing backend components."""
|
|
208
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
209
|
+
components = backend.list_components()
|
|
210
|
+
|
|
211
|
+
assert components == ["cpu"]
|
|
212
|
+
|
|
213
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
214
|
+
def test_get_research_fields_caching(self, mock_client_class) -> None:
|
|
215
|
+
"""Test research fields caching."""
|
|
216
|
+
mock_client = mock_client_class.return_value
|
|
217
|
+
research_fields = [{"id": 1, "name": "Computer Science"}]
|
|
218
|
+
mock_client.get_research_fields.return_value = research_fields
|
|
219
|
+
|
|
220
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
221
|
+
|
|
222
|
+
# First call
|
|
223
|
+
result1 = backend.get_research_fields()
|
|
224
|
+
# Second call should use cache
|
|
225
|
+
result2 = backend.get_research_fields()
|
|
226
|
+
|
|
227
|
+
assert result1 == research_fields
|
|
228
|
+
assert result2 == research_fields
|
|
229
|
+
mock_client.get_research_fields.assert_called_once()
|
|
230
|
+
|
|
231
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
232
|
+
def test_get_or_create_user_existing(self, mock_client_class) -> None:
|
|
233
|
+
"""Test getting existing user."""
|
|
234
|
+
mock_client = mock_client_class.return_value
|
|
235
|
+
existing_users = [{"id": 1, "email": "pi@example.com", "username": "pi_user"}]
|
|
236
|
+
mock_client.get_users.return_value = existing_users
|
|
237
|
+
|
|
238
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
239
|
+
waldur_user = {
|
|
240
|
+
"email": "pi@example.com",
|
|
241
|
+
"username": "pi_user",
|
|
242
|
+
"first_name": "Principal",
|
|
243
|
+
"last_name": "Investigator",
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
user_id = backend._get_or_create_user(waldur_user)
|
|
247
|
+
|
|
248
|
+
assert user_id == 1
|
|
249
|
+
mock_client.get_users.assert_called_once()
|
|
250
|
+
mock_client.create_user_request.assert_not_called()
|
|
251
|
+
|
|
252
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
253
|
+
def test_get_or_create_user_create_new(self, mock_client_class) -> None:
|
|
254
|
+
"""Test creating new user."""
|
|
255
|
+
mock_client = mock_client_class.return_value
|
|
256
|
+
mock_client.get_users.return_value = [] # No existing users
|
|
257
|
+
mock_client.create_user_request.return_value = {"id": 2}
|
|
258
|
+
|
|
259
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
260
|
+
waldur_user = {
|
|
261
|
+
"email": "new@example.com",
|
|
262
|
+
"username": "new_user",
|
|
263
|
+
"first_name": "New",
|
|
264
|
+
"last_name": "User",
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
user_id = backend._get_or_create_user(waldur_user)
|
|
268
|
+
|
|
269
|
+
assert user_id == 2
|
|
270
|
+
mock_client.get_users.assert_called_once()
|
|
271
|
+
mock_client.create_user_request.assert_called_once()
|
|
272
|
+
|
|
273
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
274
|
+
def test_get_or_create_user_no_email(self, mock_client_class) -> None:
|
|
275
|
+
"""Test handling user without email."""
|
|
276
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
277
|
+
waldur_user = {"username": "user_without_email"}
|
|
278
|
+
|
|
279
|
+
user_id = backend._get_or_create_user(waldur_user)
|
|
280
|
+
|
|
281
|
+
assert user_id is None
|
|
282
|
+
|
|
283
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
284
|
+
def test_get_project_by_waldur_id_found(self, mock_client_class) -> None:
|
|
285
|
+
"""Test finding project by Waldur ID."""
|
|
286
|
+
mock_client = mock_client_class.return_value
|
|
287
|
+
mock_client.get_projects.return_value = [self.sample_mup_project]
|
|
288
|
+
|
|
289
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
290
|
+
project = backend._get_project_by_waldur_id(self.resource_uuid.hex)
|
|
291
|
+
|
|
292
|
+
assert project == self.sample_mup_project
|
|
293
|
+
|
|
294
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
295
|
+
def test_get_project_by_waldur_id_not_found(self, mock_client_class) -> None:
|
|
296
|
+
"""Test project not found by Waldur ID."""
|
|
297
|
+
mock_client = mock_client_class.return_value
|
|
298
|
+
mock_client.get_projects.return_value = []
|
|
299
|
+
|
|
300
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
301
|
+
project = backend._get_project_by_waldur_id("nonexistent_uuid")
|
|
302
|
+
|
|
303
|
+
assert project is None
|
|
304
|
+
|
|
305
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
306
|
+
def test_create_mup_project_success(self, mock_client_class) -> None:
|
|
307
|
+
"""Test successful MUP project creation."""
|
|
308
|
+
mock_client = mock_client_class.return_value
|
|
309
|
+
mock_client.create_project.return_value = self.sample_mup_project
|
|
310
|
+
|
|
311
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
312
|
+
waldur_project = {
|
|
313
|
+
"uuid": self.project_uuid.hex,
|
|
314
|
+
"name": "Test Project",
|
|
315
|
+
"description": "A test project",
|
|
316
|
+
}
|
|
317
|
+
pi_email = "pi@example.com"
|
|
318
|
+
|
|
319
|
+
result = backend._create_mup_project(waldur_project, pi_email)
|
|
320
|
+
|
|
321
|
+
assert result == self.sample_mup_project
|
|
322
|
+
mock_client.create_project.assert_called_once()
|
|
323
|
+
|
|
324
|
+
# Check project data structure
|
|
325
|
+
call_args = mock_client.create_project.call_args[0][0]
|
|
326
|
+
assert call_args["title"] == "Test Project"
|
|
327
|
+
assert call_args["pi"] == "pi@example.com"
|
|
328
|
+
assert call_args["grant_number"] == f"waldur_{self.project_uuid.hex}"
|
|
329
|
+
|
|
330
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
331
|
+
def test_create_mup_project_failure(self, mock_client_class) -> None:
|
|
332
|
+
"""Test MUP project creation failure."""
|
|
333
|
+
mock_client = mock_client_class.return_value
|
|
334
|
+
mock_client.create_project.side_effect = MUPError("Project creation failed")
|
|
335
|
+
|
|
336
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
337
|
+
waldur_project = {"uuid": self.project_uuid.hex, "name": "Test Project"}
|
|
338
|
+
pi_email = "pi@example.com"
|
|
339
|
+
|
|
340
|
+
result = backend._create_mup_project(waldur_project, pi_email)
|
|
341
|
+
|
|
342
|
+
assert result is None
|
|
343
|
+
|
|
344
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
345
|
+
def test_create_resource_success(self, mock_client_class) -> None:
|
|
346
|
+
"""Test successful resource creation."""
|
|
347
|
+
mock_client = mock_client_class.return_value
|
|
348
|
+
# Mock for user creation
|
|
349
|
+
mock_client.get_users.return_value = [] # No existing users
|
|
350
|
+
mock_client.create_user_request.return_value = {"id": 1}
|
|
351
|
+
mock_client.get_projects.return_value = [self.sample_mup_project]
|
|
352
|
+
mock_client.create_allocation.return_value = self.sample_mup_allocation
|
|
353
|
+
mock_client.add_project_member.return_value = {"status": "success"}
|
|
354
|
+
|
|
355
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
356
|
+
result = backend.create_resource(self.sample_waldur_resource, self.sample_user_context)
|
|
357
|
+
|
|
358
|
+
assert isinstance(result, BackendResourceInfo)
|
|
359
|
+
assert result.backend_id == "1"
|
|
360
|
+
assert result.limits["cpu"] == 10
|
|
361
|
+
|
|
362
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
363
|
+
def test_create_resource_no_project_uuid(self, mock_client_class) -> None:
|
|
364
|
+
"""Test resource creation failure with no project UUID."""
|
|
365
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
366
|
+
|
|
367
|
+
waldur_resource = self.sample_waldur_resource
|
|
368
|
+
waldur_resource.project_uuid = Unset()
|
|
369
|
+
|
|
370
|
+
with pytest.raises(BackendError) as context:
|
|
371
|
+
backend.create_resource(waldur_resource)
|
|
372
|
+
|
|
373
|
+
assert "No project UUID found" in str(context.value)
|
|
374
|
+
|
|
375
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
376
|
+
def test_create_resource_project_activation(self, mock_client_class) -> None:
|
|
377
|
+
"""Test project activation during resource creation."""
|
|
378
|
+
mock_client = mock_client_class.return_value
|
|
379
|
+
# Mock for user creation
|
|
380
|
+
mock_client.get_users.return_value = [] # No existing users
|
|
381
|
+
mock_client.create_user_request.return_value = {"id": 1}
|
|
382
|
+
|
|
383
|
+
# Project exists but is inactive
|
|
384
|
+
inactive_project = self.sample_mup_project.copy()
|
|
385
|
+
inactive_project["active"] = False
|
|
386
|
+
mock_client.get_projects.return_value = [inactive_project]
|
|
387
|
+
mock_client.activate_project.return_value = {"status": "activated"}
|
|
388
|
+
mock_client.create_allocation.return_value = self.sample_mup_allocation
|
|
389
|
+
mock_client.add_project_member.return_value = {"status": "success"}
|
|
390
|
+
|
|
391
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
392
|
+
result = backend.create_resource(self.sample_waldur_resource, self.sample_user_context)
|
|
393
|
+
|
|
394
|
+
mock_client.activate_project.assert_called_once_with(1)
|
|
395
|
+
assert isinstance(result, BackendResourceInfo)
|
|
396
|
+
|
|
397
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
398
|
+
def test_create_resource_with_real_pi_from_context(self, mock_client_class) -> None:
|
|
399
|
+
"""Test resource creation uses real PI from user context."""
|
|
400
|
+
mock_client = mock_client_class.return_value
|
|
401
|
+
mock_client.get_users.return_value = [] # No existing users
|
|
402
|
+
mock_client.create_user_request.return_value = {"id": 1}
|
|
403
|
+
mock_client.get_projects.return_value = [] # No existing project
|
|
404
|
+
mock_client.create_project.return_value = self.sample_mup_project
|
|
405
|
+
mock_client.create_allocation.return_value = self.sample_mup_allocation
|
|
406
|
+
mock_client.add_project_member.return_value = {"status": "success"}
|
|
407
|
+
|
|
408
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
409
|
+
result = backend.create_resource(self.sample_waldur_resource, self.sample_user_context)
|
|
410
|
+
|
|
411
|
+
# Verify project was created with real PI email from user context
|
|
412
|
+
mock_client.create_project.assert_called_once()
|
|
413
|
+
project_data = mock_client.create_project.call_args[0][0]
|
|
414
|
+
assert project_data["pi"] == "pi@example.com" # Real email from user context
|
|
415
|
+
|
|
416
|
+
# Verify user was added to project during creation
|
|
417
|
+
mock_client.add_project_member.assert_called()
|
|
418
|
+
|
|
419
|
+
assert isinstance(result, BackendResourceInfo)
|
|
420
|
+
|
|
421
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
422
|
+
def test_create_resource_without_user_context(self, mock_client_class) -> None:
|
|
423
|
+
"""Test resource creation falls back to default PI when no user context."""
|
|
424
|
+
mock_client = mock_client_class.return_value
|
|
425
|
+
mock_client.get_users.return_value = []
|
|
426
|
+
mock_client.create_user_request.return_value = {"id": 1}
|
|
427
|
+
mock_client.get_projects.return_value = [] # No existing project
|
|
428
|
+
mock_client.create_project.return_value = self.sample_mup_project
|
|
429
|
+
mock_client.create_allocation.return_value = self.sample_mup_allocation
|
|
430
|
+
|
|
431
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
432
|
+
result = backend.create_resource(self.sample_waldur_resource) # No user context
|
|
433
|
+
|
|
434
|
+
# Verify project was created with fallback PI email
|
|
435
|
+
mock_client.create_project.assert_called_once()
|
|
436
|
+
project_data = mock_client.create_project.call_args[0][0]
|
|
437
|
+
assert project_data["pi"].endswith(".example.com") # Fallback email
|
|
438
|
+
|
|
439
|
+
# Verify no users were added during creation (since no context)
|
|
440
|
+
mock_client.add_project_member.assert_not_called()
|
|
441
|
+
|
|
442
|
+
assert isinstance(result, BackendResourceInfo)
|
|
443
|
+
|
|
444
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
445
|
+
def test_collect_limits(self, mock_client_class) -> None:
|
|
446
|
+
"""Test limits collection."""
|
|
447
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
448
|
+
|
|
449
|
+
waldur_resource = WaldurResource(limits=ResourceLimits.from_dict({"cpu": 10}))
|
|
450
|
+
|
|
451
|
+
allocation_limits, waldur_limits = backend._collect_resource_limits(waldur_resource)
|
|
452
|
+
|
|
453
|
+
assert allocation_limits["cpu"] == 10 # unit_factor = 1
|
|
454
|
+
assert waldur_limits["cpu"] == 10
|
|
455
|
+
|
|
456
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
457
|
+
def test_get_resource_metadata(self, mock_client_class) -> None:
|
|
458
|
+
"""Test getting resource metadata."""
|
|
459
|
+
mock_client = mock_client_class.return_value
|
|
460
|
+
mock_client.get_projects.return_value = [self.sample_mup_project]
|
|
461
|
+
mock_client.get_project_allocations.return_value = [self.sample_mup_allocation]
|
|
462
|
+
|
|
463
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
464
|
+
metadata = backend.get_resource_metadata("1")
|
|
465
|
+
|
|
466
|
+
expected_metadata = {
|
|
467
|
+
"mup_project_id": 1,
|
|
468
|
+
"mup_allocation_id": 1,
|
|
469
|
+
"allocation_type": "compute",
|
|
470
|
+
"allocation_size": 10,
|
|
471
|
+
"allocation_used": 0,
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
assert metadata == expected_metadata
|
|
475
|
+
|
|
476
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
477
|
+
def test_get_usage_report(self, mock_client_class) -> None:
|
|
478
|
+
"""Test usage report generation."""
|
|
479
|
+
mock_client = mock_client_class.return_value
|
|
480
|
+
mock_client.get_projects.return_value = [self.sample_mup_project]
|
|
481
|
+
mock_client.get_project_allocations.return_value = [
|
|
482
|
+
{"id": 1, "type": "compute", "used": 5, "size": 10}
|
|
483
|
+
]
|
|
484
|
+
mock_client.get_project_members.return_value = [
|
|
485
|
+
{"id": 1, "active": True, "member": {"username": "user1", "email": "user1@example.com"}}
|
|
486
|
+
]
|
|
487
|
+
|
|
488
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
489
|
+
accounts = ["1"]
|
|
490
|
+
report = backend._get_usage_report(accounts)
|
|
491
|
+
|
|
492
|
+
account_key = "1"
|
|
493
|
+
assert account_key in report
|
|
494
|
+
assert "TOTAL_ACCOUNT_USAGE" in report[account_key]
|
|
495
|
+
assert report[account_key]["TOTAL_ACCOUNT_USAGE"]["cpu"] == 5
|
|
496
|
+
assert "user1" in report[account_key]
|
|
497
|
+
|
|
498
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
499
|
+
def test_add_users_to_resource(self, mock_client_class) -> None:
|
|
500
|
+
"""Test adding users to resource."""
|
|
501
|
+
mock_client = mock_client_class.return_value
|
|
502
|
+
mock_client.get_projects.return_value = [self.sample_mup_project]
|
|
503
|
+
mock_client.get_project_allocations.return_value = [self.sample_mup_allocation]
|
|
504
|
+
mock_client.get_users.return_value = [] # User doesn't exist
|
|
505
|
+
mock_client.create_user_request.return_value = {"id": 2}
|
|
506
|
+
mock_client.add_project_member.return_value = {"status": "success"}
|
|
507
|
+
|
|
508
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
509
|
+
user_ids = {"newuser@example.com"}
|
|
510
|
+
resource_backend_id = "1"
|
|
511
|
+
|
|
512
|
+
added_users = backend.add_users_to_resource(resource_backend_id, user_ids)
|
|
513
|
+
|
|
514
|
+
assert added_users == user_ids
|
|
515
|
+
mock_client.add_project_member.assert_called_once()
|
|
516
|
+
|
|
517
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
518
|
+
def test_remove_users_from_account(self, mock_client_class) -> None:
|
|
519
|
+
"""Test removing users from account."""
|
|
520
|
+
mock_client = mock_client_class.return_value
|
|
521
|
+
mock_client.get_projects.return_value = [self.sample_mup_project]
|
|
522
|
+
mock_client.get_project_allocations.return_value = [self.sample_mup_allocation]
|
|
523
|
+
mock_client.get_project_members.return_value = [
|
|
524
|
+
{"id": 1, "active": True, "member": {"username": "user1", "email": "user1@example.com"}}
|
|
525
|
+
]
|
|
526
|
+
mock_client.toggle_member_status.return_value = {"status": "deactivated"}
|
|
527
|
+
|
|
528
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
529
|
+
usernames = {"user1"}
|
|
530
|
+
resource_backend_id = "1"
|
|
531
|
+
|
|
532
|
+
removed_users = backend.remove_users_from_resource(resource_backend_id, usernames)
|
|
533
|
+
|
|
534
|
+
assert removed_users == ["user1"]
|
|
535
|
+
mock_client.toggle_member_status.assert_called_once_with(1, 1, {"active": False})
|
|
536
|
+
|
|
537
|
+
@patch("waldur_site_agent_mup.backend.MUPClient")
|
|
538
|
+
def test_unsupported_operations_warning_only(self, mock_client_class) -> None:
|
|
539
|
+
"""Test that unsupported operations return False and log warnings."""
|
|
540
|
+
backend = MUPBackend(self.mup_settings, self.mup_components)
|
|
541
|
+
|
|
542
|
+
# These operations are not supported by MUP but should not raise exceptions
|
|
543
|
+
assert not backend.downscale_resource("test_account")
|
|
544
|
+
assert not backend.pause_resource("test_account")
|
|
545
|
+
assert not backend.restore_resource("test_account")
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
if __name__ == "__main__":
|
|
549
|
+
unittest.main()
|