2323class Command (BaseCommand ):
2424 help = "Validates quotas and users in resource allocations."
2525
26+ OPENSTACK_RESOURCE_NAMES = ["OpenStack" , "ESI" ]
27+ OPENSHIFT_RESOURCE_NAMES = ["OpenShift" , "Openshift Virtualization" ]
28+
2629 def add_arguments (self , parser ):
2730 parser .add_argument (
2831 "--apply" ,
@@ -91,7 +94,7 @@ def set_default_quota_on_allocation(allocation, allocator, coldfront_attr):
9194 return value
9295
9396 @staticmethod
94- def parse_quota_value (quota_str : str | None , attr : str ) -> int | None :
97+ def parse_openshift_quota_value (quota_str : str | None , attr : str ) -> int | None :
9598 PATTERN = r"([0-9]+)(m|k|Ki|Mi|Gi|Ti|Pi|Ei|K|M|G|T|P|E)?"
9699
97100 suffix = {
@@ -140,6 +143,97 @@ def parse_quota_value(quota_str: str | None, attr: str) -> int | None:
140143
141144 return quota_str
142145
146+ def validate_project_exists (self , allocator , project_id , resource_name ):
147+ if resource_name in self .OPENSHIFT_RESOURCE_NAMES :
148+ allocator ._get_project (project_id )
149+ elif resource_name in self .OPENSTACK_RESOURCE_NAMES :
150+ allocator .identity .projects .get (project_id )
151+
152+ def validate_quotas (
153+ self ,
154+ allocator ,
155+ project_id ,
156+ allocation ,
157+ allocation_str ,
158+ resource_name ,
159+ apply : bool ,
160+ ):
161+ quota = allocator .get_quota (project_id )
162+ for attr in tasks .get_expected_attributes (allocator ):
163+ # quota_key = Command.get_quota_key(attr, resource_name)
164+ # Get quota key
165+ if resource_name in self .OPENSHIFT_RESOURCE_NAMES :
166+ key_with_lambda = allocator .QUOTA_KEY_MAPPING .get (attr , None )
167+ # This gives me just the plain key str
168+ quota_key = list (key_with_lambda (1 ).keys ())[0 ]
169+ elif resource_name in self .OPENSTACK_RESOURCE_NAMES :
170+ quota_key = allocator .QUOTA_KEY_MAPPING_ALL_KEYS .get (attr , None )
171+ if not quota_key :
172+ # Note(knikolla): Some attributes are only maintained
173+ # for bookkeeping purposes and do not have a
174+ # corresponding quota set on the service.
175+ continue
176+
177+ expected_value = allocation .get_attribute (attr )
178+ current_value = quota .get (quota_key , None )
179+ # expected_value, current_value = Command.parse_quota_values(expected_value, current_value, attr, resource_name)
180+
181+ # parse quota values
182+ if resource_name in self .OPENSHIFT_RESOURCE_NAMES :
183+ current_value = Command .parse_openshift_quota_value (current_value , attr )
184+ elif resource_name in self .OPENSTACK_RESOURCE_NAMES :
185+ obj_key = openstack .OpenStackResourceAllocator .QUOTA_KEY_MAPPING [
186+ "object"
187+ ]["keys" ][attributes .QUOTA_OBJECT_GB ]
188+ if quota_key == obj_key and expected_value <= 0 :
189+ expected_value = 1
190+ current_value = int (
191+ allocator .object (project_id ).head_account ().get (obj_key )
192+ )
193+
194+ if current_value is None and expected_value is None :
195+ msg = (
196+ f"Value for quota for { attr } is not set anywhere"
197+ f" on allocation { allocation_str } "
198+ )
199+
200+ if apply :
201+ expected_value = Command .set_default_quota_on_allocation (
202+ allocation , allocator , attr
203+ )
204+ msg = f"Added default quota for { attr } to allocation { allocation_str } to { expected_value } "
205+ logger .warning (msg )
206+ elif current_value is not None and expected_value is None :
207+ msg = (
208+ f'Attribute "{ attr } " expected on allocation { allocation_str } but not set.'
209+ f" Current quota is { current_value } ."
210+ )
211+
212+ if apply :
213+ utils .set_attribute_on_allocation (allocation , attr , current_value )
214+ expected_value = (
215+ current_value # To pass `current_value != expected_value` check
216+ )
217+ msg = f"{ msg } Attribute set to match current quota."
218+ logger .warning (msg )
219+
220+ if current_value != expected_value :
221+ msg = (
222+ f"Value for quota for { attr } = { current_value } does not match expected"
223+ f" value of { expected_value } on allocation { allocation_str } "
224+ )
225+ logger .warning (msg )
226+
227+ if apply :
228+ try :
229+ allocator .set_quota (project_id )
230+ logger .warning (
231+ f"Quota for allocation { project_id } was out of date. Reapplied!"
232+ )
233+ except Exception as e :
234+ logger .error (f"setting openshift quota failed: { e } " )
235+ continue
236+
143237 def check_institution_specific_code (self , allocation , apply ):
144238 attr = attributes .ALLOCATION_INSTITUTION_SPECIFIC_CODE
145239 isc = allocation .get_attribute (attr )
@@ -152,202 +246,55 @@ def check_institution_specific_code(self, allocation, apply):
152246 logger .warn (f'Attribute "{ attr } " added to allocation { alloc_str } ' )
153247
154248 def handle (self , * args , ** options ):
155- # Deal with Openstack and ESI resources
156- openstack_resources = Resource .objects .filter (
157- resource_type__name__in = ["OpenStack" , "ESI" ]
158- )
159- openstack_allocations = Allocation .objects .filter (
160- resources__in = openstack_resources ,
161- status = AllocationStatusChoice .objects .get (name = "Active" ),
162- )
163- for allocation in openstack_allocations :
164- self .check_institution_specific_code (allocation , options ["apply" ])
165- allocation_str = f'{ allocation .pk } of project "{ allocation .project .title } "'
166- msg = f"Starting resource validation for allocation { allocation_str } ."
167- logger .debug (msg )
168-
169- failed_validation = False
170-
171- allocator = tasks .find_allocator (allocation )
172-
173- project_id = allocation .get_attribute (attributes .ALLOCATION_PROJECT_ID )
174- if not project_id :
175- logger .error (f"{ allocation_str } is active but has no Project ID set." )
176- continue
177-
178- try :
179- allocator .identity .projects .get (project_id )
180- except http .NotFound :
181- logger .error (
182- f"{ allocation_str } has Project ID { project_id } . But"
183- f" no project found in OpenStack."
184- )
185- continue
186-
187- quota = allocator .get_quota (project_id )
188-
189- failed_validation = Command .sync_users (
190- project_id , allocation , allocator , options ["apply" ]
249+ for resource_name in (
250+ self .OPENSTACK_RESOURCE_NAMES + self .OPENSHIFT_RESOURCE_NAMES
251+ ):
252+ resource = Resource .objects .filter (resource_type__name = resource_name )
253+ allocations = Allocation .objects .filter (
254+ resources__in = resource ,
255+ status = AllocationStatusChoice .objects .get (name = "Active" ),
191256 )
192257
193- obj_key = openstack .OpenStackResourceAllocator .QUOTA_KEY_MAPPING ["object" ][
194- "keys"
195- ][attributes .QUOTA_OBJECT_GB ]
258+ for allocation in allocations :
259+ allocation_str = (
260+ f'{ allocation .pk } of project "{ allocation .project .title } "'
261+ )
262+ logger .debug (
263+ f"Starting resource validation for allocation { allocation_str } ."
264+ )
196265
197- for attr in tasks .get_expected_attributes (allocator ):
198- key = allocator .QUOTA_KEY_MAPPING_ALL_KEYS .get (attr , None )
199- if not key :
200- # Note(knikolla): Some attributes are only maintained
201- # for bookkeeping purposes and do not have a
202- # corresponding quota set on the service.
203- continue
266+ allocator = tasks .find_allocator (allocation )
267+ project_id = allocation .get_attribute (attributes .ALLOCATION_PROJECT_ID )
204268
205- expected_value = allocation .get_attribute (attr )
206- current_value = quota .get (key , None )
207- if key == obj_key and expected_value <= 0 :
208- expected_obj_value = 1
209- current_value = int (
210- allocator .object (project_id ).head_account ().get (obj_key )
211- )
212- if current_value != expected_obj_value :
213- failed_validation = True
214- msg = (
215- f"Value for quota for { attr } = { current_value } does not match expected"
216- f" value of { expected_obj_value } on allocation { allocation_str } "
217- )
218- logger .warning (msg )
219- elif expected_value is None and current_value :
220- msg = (
221- f'Attribute "{ attr } " expected on allocation { allocation_str } but not set.'
222- f" Current quota is { current_value } ."
223- )
224- if options ["apply" ]:
225- utils .set_attribute_on_allocation (
226- allocation , attr , current_value
227- )
228- msg = f"{ msg } Attribute set to match current quota."
229- logger .warning (msg )
230- elif not current_value == expected_value :
231- failed_validation = True
232- msg = (
233- f"Value for quota for { attr } = { current_value } does not match expected"
234- f" value of { expected_value } on allocation { allocation_str } "
269+ # Check project ID is set
270+ if not project_id :
271+ logger .error (
272+ f"{ allocation_str } is active but has no Project ID set."
235273 )
236- logger . warning ( msg )
274+ continue
237275
238- if failed_validation and options [ "apply" ]:
276+ # Check project exists in remote cluster
239277 try :
240- allocator .set_quota (
241- allocation .get_attribute (attributes .ALLOCATION_PROJECT_ID )
242- )
243- except Exception as e :
278+ self .validate_project_exists (allocator , project_id , resource_name )
279+ except http .NotFound :
244280 logger .error (
245- f"setting { allocation .resources .first ()} quota failed: { e } "
281+ f"{ allocation_str } has Project ID { project_id } . But"
282+ f" no project found in { resource_name } ."
246283 )
247284 continue
248- logger .warning (
249- f"Quota for allocation { allocation_str } was out of date. Reapplied!"
250- )
251-
252- # Deal with OpenShift and Openshift VM
253-
254- openshift_resources = Resource .objects .filter (
255- resource_type__name__in = ["OpenShift" , "Openshift Virtualization" ]
256- )
257- openshift_allocations = Allocation .objects .filter (
258- resources__in = openshift_resources ,
259- status = AllocationStatusChoice .objects .get (name = "Active" ),
260- )
261-
262- for allocation in openshift_allocations :
263- self .check_institution_specific_code (allocation , options ["apply" ])
264- allocation_str = f'{ allocation .pk } of project "{ allocation .project .title } "'
265- logger .debug (
266- f"Starting resource validation for allocation { allocation_str } ."
267- )
268285
269- allocator = tasks .find_allocator (allocation )
270-
271- project_id = allocation .get_attribute (attributes .ALLOCATION_PROJECT_ID )
272-
273- if not project_id :
274- logger .error (f"{ allocation_str } is active but has no Project ID set." )
275- continue
276-
277- try :
278- allocator ._get_project (project_id )
279- except http .NotFound :
280- logger .error (
281- f"{ allocation_str } has Project ID { project_id } . But"
282- f" no project found in OpenShift."
283- )
284- continue
285-
286- quota = allocator .get_quota (project_id )
287-
288- failed_validation = Command .sync_users (
289- project_id , allocation , allocator , options ["apply" ]
290- )
291- Command .sync_openshift_project_labels (
292- project_id , allocator , options ["apply" ]
293- )
294-
295- for attr in tasks .get_expected_attributes (allocator ):
296- key_with_lambda = allocator .QUOTA_KEY_MAPPING .get (attr , None )
297-
298- # This gives me just the plain key
299- key = list (key_with_lambda (1 ).keys ())[0 ]
300-
301- expected_value = allocation .get_attribute (attr )
302- current_value = quota .get (key , None )
303- current_value = self .parse_quota_value (current_value , attr )
304-
305- if expected_value is None and current_value is not None :
306- msg = (
307- f'Attribute "{ attr } " expected on allocation { allocation_str } but not set.'
308- f" Current quota is { current_value } ."
286+ # Check institution code, users, labels, and quotas
287+ self .check_institution_specific_code (allocation , options ["apply" ])
288+ Command .sync_users (project_id , allocation , allocator , options ["apply" ])
289+ if resource_name in self .OPENSHIFT_RESOURCE_NAMES :
290+ Command .sync_openshift_project_labels (
291+ project_id , allocator , options ["apply" ]
309292 )
310- if options ["apply" ]:
311- utils .set_attribute_on_allocation (
312- allocation , attr , current_value
313- )
314- msg = f"{ msg } Attribute set to match current quota."
315- logger .warning (msg )
316- else :
317- # We just checked the case where the quota value is set in the cluster
318- # but not in coldfront. This is the only case the cluster value is the
319- # "source of truth" for the quota value
320- # If the coldfront value is set, it is always the source of truth.
321- # But first, we need to check if the quota value is set anywhere at all.
322- # TODO (Quan): Refactor these if statements so that we can remove this comment block
323- if current_value is None and expected_value is None :
324- msg = (
325- f"Value for quota for { attr } is not set anywhere"
326- f" on allocation { allocation_str } "
327- )
328- logger .warning (msg )
329-
330- if options ["apply" ]:
331- expected_value = self .set_default_quota_on_allocation (
332- allocation , allocator , attr
333- )
334- logger .warning (
335- f"Added default quota for { attr } to allocation { allocation_str } to { expected_value } "
336- )
337-
338- if not (current_value == expected_value ):
339- msg = (
340- f"Value for quota for { attr } = { current_value } does not match expected"
341- f" value of { expected_value } on allocation { allocation_str } "
342- )
343- logger .warning (msg )
344-
345- if options ["apply" ]:
346- try :
347- allocator .set_quota (project_id )
348- logger .warning (
349- f"Quota for allocation { project_id } was out of date. Reapplied!"
350- )
351- except Exception as e :
352- logger .error (f"setting openshift quota failed: { e } " )
353- continue
293+ self .validate_quotas (
294+ allocator ,
295+ project_id ,
296+ allocation ,
297+ allocation_str ,
298+ resource_name ,
299+ options ["apply" ],
300+ )
0 commit comments