@@ -36,12 +36,12 @@ def clean_openshift_metadata(obj):
3636 return obj
3737
3838QUOTA_KEY_MAPPING = {
39- attributes .QUOTA_LIMITS_CPU : lambda x : {": limits.cpu" : f"{ x * 1000 } m" },
40- attributes .QUOTA_LIMITS_MEMORY : lambda x : {": limits.memory" : f"{ x } Mi" },
41- attributes .QUOTA_LIMITS_EPHEMERAL_STORAGE_GB : lambda x : {": limits.ephemeral-storage" : f"{ x } Gi" },
42- attributes .QUOTA_REQUESTS_STORAGE : lambda x : {": requests.storage" : f"{ x } Gi" },
43- attributes .QUOTA_REQUESTS_GPU : lambda x : {": requests.nvidia.com/gpu" : f"{ x } " },
44- attributes .QUOTA_PVC : lambda x : {": persistentvolumeclaims" : f"{ x } " },
39+ attributes .QUOTA_LIMITS_CPU : lambda x : {"limits.cpu" : f"{ x * 1000 } m" },
40+ attributes .QUOTA_LIMITS_MEMORY : lambda x : {"limits.memory" : f"{ x } Mi" },
41+ attributes .QUOTA_LIMITS_EPHEMERAL_STORAGE_GB : lambda x : {"limits.ephemeral-storage" : f"{ x } Gi" },
42+ attributes .QUOTA_REQUESTS_STORAGE : lambda x : {"requests.storage" : f"{ x } Gi" },
43+ attributes .QUOTA_REQUESTS_GPU : lambda x : {"requests.nvidia.com/gpu" : f"{ x } " },
44+ attributes .QUOTA_PVC : lambda x : {"persistentvolumeclaims" : f"{ x } " },
4545}
4646
4747
@@ -146,20 +146,64 @@ def create_project(self, suggested_project_name):
146146 project_name = project_id
147147 self ._create_project (project_name , project_id )
148148 return self .Project (project_name , project_id )
149+
150+ def delete_moc_quotas (self , project_id ):
151+ """deletes all resourcequotas from an openshift project"""
152+ resourcequotas = self ._openshift_get_resourcequotas (project_id )
153+ for resourcequota in resourcequotas :
154+ self ._openshift_delete_resourcequota (project_id , resourcequota ["metadata" ]["name" ])
155+
156+ logger .info (f"All quotas for { project_id } successfully deleted" )
149157
150158 def set_quota (self , project_id ):
151- url = f"{ self .auth_url } /projects/{ project_id } /quota"
152- payload = dict ()
159+ """Sets the quota for a project, creating a minimal resourcequota
160+ object in the project namespace with no extra scopes"""
161+
162+ quota_spec = {}
153163 for key , func in QUOTA_KEY_MAPPING .items ():
154164 if (x := self .allocation .get_attribute (key )) is not None :
155- payload .update (func (x ))
156- r = self .session .put (url , data = json .dumps ({'Quota' : payload }))
157- self .check_response (r )
165+ quota_spec .update (func (x ))
166+
167+ quota_def = {
168+ "metadata" : {"name" : f"{ project_id } -project" },
169+ "spec" : {"hard" : quota_spec },
170+ }
171+
172+ self .delete_moc_quotas (project_id )
173+ self ._openshift_create_resourcequota (project_id , quota_def )
174+
175+ logger .info (f"Quota for { project_id } successfully created" )
176+
177+ def _get_moc_quota_from_resourcequotas (self , project_id ):
178+ """This returns a dictionary suitable for merging in with the
179+ specification from Adjutant/ColdFront"""
180+ resourcequotas = self ._openshift_get_resourcequotas (project_id )
181+ moc_quota = {}
182+ for rq in resourcequotas :
183+ name , spec = rq ["metadata" ]["name" ], rq ["spec" ]
184+ logger .info (f"processing resourcequota: { project_id } :{ name } " )
185+ scope_list = spec .get ("scopes" , ["" ])
186+ for quota_name , quota_value in spec .get ("hard" , {}).items ():
187+ for scope_item in scope_list :
188+ moc_quota_name = f"{ scope_item } :{ quota_name } "
189+ moc_quota .setdefault (moc_quota_name , quota_value )
190+ return moc_quota
158191
159192 def get_quota (self , project_id ):
160- url = f"{ self .auth_url } /projects/{ project_id } /quota"
161- r = self .session .get (url )
162- return self .check_response (r )
193+ quota_from_project = self ._get_moc_quota_from_resourcequotas (project_id )
194+
195+ quota = {}
196+ for quota_name , quota_value in quota_from_project .items ():
197+ if quota_value :
198+ quota [quota_name ] = quota_value
199+
200+ quota_object = {
201+ "Version" : "0.9" ,
202+ "Kind" : "MocQuota" ,
203+ "ProjectName" : project_id ,
204+ "Quota" : quota ,
205+ }
206+ return quota_object
163207
164208 def create_project_defaults (self , project_id ):
165209 pass
@@ -297,3 +341,48 @@ def _openshift_useridentitymapping_exists(self, user_name, id_user):
297341 for identity in user .get ("identities" , [])
298342 )
299343
344+ def _openshift_get_project (self , project_name ):
345+ api = self .get_resource_api (API_PROJECT , "Project" )
346+ return clean_openshift_metadata (api .get (name = project_name ).to_dict ())
347+
348+ def _openshift_get_resourcequotas (self , project_id ):
349+ """Returns a list of all of the resourcequota objects"""
350+ # Raise a NotFound error if the project doesn't exist
351+ self ._openshift_get_project (project_id )
352+ api = self .get_resource_api (API_CORE , "ResourceQuota" )
353+ res = clean_openshift_metadata (api .get (namespace = project_id ).to_dict ())
354+
355+ return res ["items" ]
356+
357+ def _wait_for_quota_to_settle (self , project_id , resource_quota ):
358+ """Wait for quota on resourcequotas to settle.
359+
360+ When creating a new resourcequota that sets a quota on resourcequota objects, we need to
361+ wait for OpenShift to calculate the quota usage before we attempt to create any new
362+ resourcequota objects.
363+ """
364+
365+ if "resourcequotas" in resource_quota ["spec" ]["hard" ]:
366+ logger .info ("waiting for resourcequota quota" )
367+
368+ api = self .get_resource_api (API_CORE , "ResourceQuota" )
369+ while True :
370+ resp = clean_openshift_metadata (
371+ api .get (
372+ namespace = project_id , name = resource_quota ["metadata" ]["name" ]
373+ ).to_dict ()
374+ )
375+ if "resourcequotas" in resp ["status" ].get ("used" , {}):
376+ break
377+ time .sleep (0.1 )
378+
379+ def _openshift_create_resourcequota (self , project_id , quota_def ):
380+ api = self .get_resource_api (API_CORE , "ResourceQuota" )
381+ res = api .create (namespace = project_id , body = quota_def ).to_dict ()
382+ self ._wait_for_quota_to_settle (project_id , res )
383+
384+ def _openshift_delete_resourcequota (self , project_id , resourcequota_name ):
385+ """In an openshift namespace {project_id) delete a specified resourcequota"""
386+ api = self .get_resource_api (API_CORE , "ResourceQuota" )
387+ return api .delete (namespace = project_id , name = resourcequota_name ).to_dict ()
388+
0 commit comments