@@ -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
@@ -77,7 +77,7 @@ def __init__(self, resource, allocation):
7777 def k8_client (self ):
7878 # Load Endpoint URL and Auth token for new k8 client
7979 openshift_token = os .getenv (f"OPENSHIFT_{ self .safe_resource_name } _TOKEN" )
80- openshift_url = self .resource .get_attribute (attributes .RESOURCE_AUTH_URL )
80+ openshift_url = self .resource .get_attribute (attributes .RESOURCE_API_URL )
8181
8282 k8_config = kubernetes .client .Configuration ()
8383 k8_config .api_key ["authorization" ] = openshift_token
@@ -146,20 +146,41 @@ 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" )
158176
159177 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 )
178+ cloud_quotas = self ._openshift_get_resourcequotas (project_id )
179+ combined_quota = {}
180+ for cloud_quota in cloud_quotas :
181+ combined_quota .update (cloud_quota ["spec" ]["hard" ])
182+
183+ return combined_quota
163184
164185 def create_project_defaults (self , project_id ):
165186 pass
@@ -181,7 +202,7 @@ def reactivate_project(self, project_id):
181202 def get_federated_user (self , username ):
182203 if (
183204 self ._openshift_user_exists (username )
184- and self ._openshift_get_identity (username )
205+ and self ._openshift_identity_exists (username )
185206 and self ._openshift_useridentitymapping_exists (username , username )
186207 ):
187208 return {'username' : username }
@@ -264,25 +285,84 @@ def _openshift_get_identity(self, id_user):
264285 def _openshift_user_exists (self , user_name ):
265286 try :
266287 self ._openshift_get_user (user_name )
267- except kexc .NotFoundError :
268- return False
288+ except kexc .NotFoundError as e :
289+ # Ensures error raise because resource not found,
290+ # not because of other reasons, like incorrect url
291+ e_info = json .loads (e .body )
292+ if (
293+ e_info ["reason" ] == "NotFound"
294+ and e_info ["details" ]["name" ] == user_name
295+ ):
296+ return False
297+ raise e
269298 return True
270299
271300 def _openshift_identity_exists (self , id_user ):
272301 try :
273302 self ._openshift_get_identity (id_user )
274- except kexc .NotFoundError :
275- return False
303+ except kexc .NotFoundError as e :
304+ e_info = json .loads (e .body )
305+ if e_info .get ("reason" ) == "NotFound" :
306+ return False
307+ raise e
276308 return True
277309
278310 def _openshift_useridentitymapping_exists (self , user_name , id_user ):
279311 try :
280312 user = self ._openshift_get_user (user_name )
281- except kexc .NotFoundError :
282- return False
313+ except kexc .NotFoundError as e :
314+ e_info = json .loads (e .body )
315+ if e_info .get ("reason" ) == "NotFound" :
316+ return False
317+ raise e
283318
284319 return any (
285320 identity == self .qualified_id_user (id_user )
286321 for identity in user .get ("identities" , [])
287322 )
288323
324+ def _openshift_get_project (self , project_name ):
325+ api = self .get_resource_api (API_PROJECT , "Project" )
326+ return clean_openshift_metadata (api .get (name = project_name ).to_dict ())
327+
328+ def _openshift_get_resourcequotas (self , project_id ):
329+ """Returns a list of resourcequota objects in namespace with name `project_id`"""
330+ # Raise a NotFound error if the project doesn't exist
331+ self ._openshift_get_project (project_id )
332+ api = self .get_resource_api (API_CORE , "ResourceQuota" )
333+ res = clean_openshift_metadata (api .get (namespace = project_id ).to_dict ())
334+
335+ return res ["items" ]
336+
337+ def _wait_for_quota_to_settle (self , project_id , resource_quota ):
338+ """Wait for quota on resourcequotas to settle.
339+
340+ When creating a new resourcequota that sets a quota on resourcequota objects, we need to
341+ wait for OpenShift to calculate the quota usage before we attempt to create any new
342+ resourcequota objects.
343+ """
344+
345+ if "resourcequotas" in resource_quota ["spec" ]["hard" ]:
346+ logger .info ("waiting for resourcequota quota" )
347+
348+ api = self .get_resource_api (API_CORE , "ResourceQuota" )
349+ while True :
350+ resp = clean_openshift_metadata (
351+ api .get (
352+ namespace = project_id , name = resource_quota ["metadata" ]["name" ]
353+ ).to_dict ()
354+ )
355+ if "resourcequotas" in resp ["status" ].get ("used" , {}):
356+ break
357+ time .sleep (0.1 )
358+
359+ def _openshift_create_resourcequota (self , project_id , quota_def ):
360+ api = self .get_resource_api (API_CORE , "ResourceQuota" )
361+ res = api .create (namespace = project_id , body = quota_def ).to_dict ()
362+ self ._wait_for_quota_to_settle (project_id , res )
363+
364+ def _openshift_delete_resourcequota (self , project_id , resourcequota_name ):
365+ """In an openshift namespace {project_id) delete a specified resourcequota"""
366+ api = self .get_resource_api (API_CORE , "ResourceQuota" )
367+ return api .delete (namespace = project_id , name = resourcequota_name ).to_dict ()
368+
0 commit comments