22
33from fastapi import APIRouter , Depends , HTTPException
44from sqlmodel import Session , select
5+ from novaclient import exceptions as nova_exceptions
6+ from cinderclient import exceptions as cinder_exceptions
57
68from fob_api import auth , openstack , random_end_uid , OPENSTACK_DOMAIN_ID , OPENSTACK_ROLE_MEMBER_ID , random_password , get_session
7- from fob_api .models .database import User , Project , ProjectUserMembership
8- from fob_api .models .api import OpenStackProject as OpenStackProjectAPI
9- from fob_api .models .api import OpenStackUserPassword as OpenStackUserPasswordAPI
9+ from fob_api .models .database import User , Project , ProjectUserMembership # deprecated import for models
10+ from fob_api .models import database as db_models
11+ from fob_api .models import api as api_models
12+ from fob_api .models .api import OpenStackProject as OpenStackProjectAPI # deprecated import for models
13+ from fob_api .models .api import OpenStackUserPassword as OpenStackUserPasswordAPI # deprecated import for models
1014from fob_api .tasks .openstack import get_or_create_user as openstack_get_or_create_user
1115from fob_api .tasks .openstack import set_user_password as openstack_set_user_password
12-
16+ from fob_api . tasks import openstack as openstack_tasks
1317
1418router = APIRouter (prefix = "/openstack" )
1519
20+ MAX_PROJECTS_USER_OWN = 3
21+
1622@router .get ("/projects/{username}" , tags = ["openstack" ])
1723def list_openstack_project_for_user (
1824 username : str ,
@@ -25,18 +31,33 @@ def list_openstack_project_for_user(
2531 auth .is_admin_or_self (user , username )
2632 user_find = session .exec (select (User ).where (User .username == username )).first ()
2733 projects_owner = session .exec (select (Project ).where (Project .owner_id == user_find .id )).all ()
34+ # get all owner projects
35+ data = []
36+ for project in projects_owner :
37+ db_members = session .exec (select (ProjectUserMembership ).where (ProjectUserMembership .project_id == project .id )).all ()
38+ data .append (OpenStackProjectAPI (
39+ id = project .id ,
40+ name = project .name ,
41+ owner = username ,
42+ members = [
43+ session .exec (select (User ).where (User .id == member .user_id )).first ().username
44+ for member in db_members
45+ ]
46+ ))
47+ # get all member projects and add to data
2848 project_memberships = session .exec (select (ProjectUserMembership ).where (ProjectUserMembership .user_id == user_find .id )).all ()
29- data = [OpenStackProjectAPI (
30- id = project .id ,
31- name = project .name ,
32- type = "owner"
33- ) for project in projects_owner ]
3449 for project_membership in project_memberships :
3550 local_project = session .exec (select (Project ).where (Project .id == project_membership .project_id )).first ()
51+ db_members = session .exec (select (ProjectUserMembership ).where (ProjectUserMembership .project_id == local_project .id )).all ()
52+ owner = session .exec (select (User ).where (User .id == local_project .owner_id )).first ()
3653 data .append (OpenStackProjectAPI (
3754 id = local_project .id ,
3855 name = local_project .name ,
39- type = "member"
56+ owner = owner .username ,
57+ members = [
58+ session .exec (select (User ).where (User .id == member .user_id )).first ().username
59+ for member in db_members
60+ ]
4061 ))
4162 return data
4263
@@ -45,15 +66,15 @@ def create_openstack_project(
4566 project_name : str ,
4667 user : Annotated [User , Depends (auth .get_current_user )],
4768 session : Session = Depends (get_session )
48- ) -> OpenStackProjectAPI | None :
69+ ) -> api_models . OpenStackProject | None :
4970 """
5071 Create a new OpenStack project
5172 """
5273 openstack_client = openstack .get_keystone_client ()
5374
5475 projects = session .exec (select (Project ).where (Project .owner_id == user .id )).all ()
55- if len (projects ) >= 3 :
56- raise HTTPException (status_code = 400 , detail = "You cannot create more than 3 projects" )
76+ if len (projects ) >= MAX_PROJECTS_USER_OWN :
77+ raise HTTPException (status_code = 400 , detail = f "You cannot create more than { str ( MAX_PROJECTS_USER_OWN ) } projects" )
5778 project_name = project_name + "-" + random_end_uid ()
5879 new_project = Project (name = project_name , owner_id = user .id )
5980 session .add (new_project )
@@ -85,6 +106,20 @@ def delete_openstack_project(
85106 raise HTTPException (status_code = 404 , detail = "Project not found" )
86107 if project .owner_id != user .id and not user .is_admin :
87108 raise HTTPException (status_code = 403 , detail = "Not allowed to delete this project" )
109+
110+ # check if quota is given to project
111+ # here we are checking if project has any quotas assigned to it so nothing is left behind not assigned to any project
112+ project_quotas = session .exec (select (db_models .UserQuotaShare ).where (db_models .UserQuotaShare .project_id == project .id )).all ()
113+ for project_quota in project_quotas :
114+ if project_quota .quantity > 0 :
115+ print (project_quota .quantity )
116+ raise HTTPException (status_code = 400 , detail = "Cannot delete project with quotas assigned to it please remove all quotas assigned to project" )
117+
118+ # check if project has members
119+ project_memberships = session .exec (select (db_models .ProjectUserMembership ).where (db_models .ProjectUserMembership .project_id == project .id )).all ()
120+ if project_memberships :
121+ raise HTTPException (status_code = 400 , detail = "Cannot delete project with members please remove all members from project" )
122+
88123 openstack_project = openstack_client .projects .find (name = project_name )
89124 openstack_client .projects .delete (openstack_project .id )
90125 session .delete (project )
@@ -115,19 +150,21 @@ def add_user_to_project(
115150 session : Session = Depends (get_session )
116151 ) -> None :
117152 """
118- Add user to project if user is owner of the project
153+ Add user to project
119154 """
120155 db_project = session .exec (select (Project ).where (Project .name == project_name )).first ()
156+ # check if owner of the project or is admin
121157 if db_project .owner_id != user .id and not user .is_admin :
122158 raise HTTPException (status_code = 403 , detail = "Not allowed to add user to this project" )
123-
159+
160+ # reject if user is owner of the project
161+ if user .username == username and db_project .owner_id == user .id :
162+ raise HTTPException (status_code = 400 , detail = "Cannot add owner to project (owner is already in project)" )
163+
164+ # get user to add
124165 user_to_add = session .exec (select (User ).where (User .username == username )).first ()
125166 if not user_to_add or not db_project :
126- raise HTTPException (status_code = 404 , detail = "User or project not found" )
127-
128- # if user is owner of the project
129- if user_to_add .id == db_project .owner_id :
130- raise HTTPException (status_code = 400 , detail = "User is owner of the project do not need to be added" )
167+ raise HTTPException (status_code = 404 , detail = "User to add not found" )
131168
132169 # check if user is already in project
133170 assignment = session .exec (select (ProjectUserMembership ).where (ProjectUserMembership .project_id == db_project .id , ProjectUserMembership .user_id == user_to_add .id )).first ()
@@ -153,31 +190,73 @@ def remove_user_from_project(
153190 session : Session = Depends (get_session )
154191 ) -> None :
155192 """
156- Remove user from project if user is owner of the project
193+ Remove user from project
157194 """
195+ # check if owner of the project or is admin
158196 db_project = session .exec (select (Project ).where (Project .name == project_name )).first ()
159- if db_project .owner_id != user .id and not user .is_admin :
197+ db_project_members_name = [
198+ session .exec (select (User ).where (User .id == member .user_id )).first ().username
199+ for member in session .exec (select (ProjectUserMembership ).where (ProjectUserMembership .project_id == db_project .id )).all ()
200+ ]
201+
202+ # action allowed if anyone of the following is true
203+ # 1. user is admin
204+ # 2. user is owner of the project and wants to remove other user
205+ # 3. user is not owner of the project and wants to remove himself from project
206+
207+ if db_project .owner_id != user .id and not user .is_admin and user .username not in db_project_members_name :
160208 raise HTTPException (status_code = 403 , detail = "Not allowed to remove user from this project" )
161209
210+ if user .username in db_project_members_name :
211+ # this avoids the case where user is not owner of the project and wants to remove other user
212+ username = user .username
213+
214+ # reject if user is owner of the project
215+ if user .username == username and db_project .owner_id == user .id :
216+ raise HTTPException (status_code = 400 , detail = "Cannot remove yourself from project (delete project instead)" )
217+
218+ # get user to remove
162219 user_to_remove = session .exec (select (User ).where (User .username == username )).first ()
163220 if not user_to_remove or not db_project :
164- raise HTTPException (status_code = 404 , detail = "User or project not found" )
165-
166- # check if user is owner of the project
167- if db_project .owner_id == user_to_remove .id :
168- raise HTTPException (status_code = 400 , detail = "Cannot remove owner from project" )
221+ raise HTTPException (status_code = 404 , detail = "User not found" )
169222
170223 # check if user is already in project
171224 assignment = session .exec (select (ProjectUserMembership ).where (ProjectUserMembership .project_id == db_project .id , ProjectUserMembership .user_id == user_to_remove .id )).first ()
172225 if not assignment :
173226 raise HTTPException (status_code = 400 , detail = "User not in project" )
174227
175- session . delete ( assignment )
228+ # get project from openstack
176229 openstack_client = openstack .get_keystone_client ()
177230 try :
178231 os_project = openstack_client .projects .find (name = project_name )
179232 except Exception :
180233 raise HTTPException (status_code = 500 , detail = "OpenStack error cant get project" )
234+
235+ os_project = openstack .get_keystone_client ().projects .find (name = project_name )
236+ # check if user has any quotas assigned to project
237+ project_quotas = session .exec (select (db_models .UserQuotaShare ).where (db_models .UserQuotaShare .project_id == db_project .id , db_models .UserQuotaShare .user_id == user_to_remove .id )).all ()
238+ # try to set all quotas to 0
239+ old_quotas_map = {k : 0 for k in db_models .QuotaType }
240+ for project_quota in project_quotas :
241+ old_quotas_map [project_quota .type ] = project_quota .quantity
242+ project_quota .quantity = 0
243+ session .add (project_quota )
244+ session .commit ()
245+
246+ # try to sync quotas with openstack without user quotas if it fails rollback quotas
247+ try :
248+ openstack_tasks .sync_project_quota (os_project )
249+ except (nova_exceptions .ClientException , cinder_exceptions .ClientException ) as e :
250+ # rollback quotas when quota sync fails (when quota is lower than current usage)
251+ for project_quota in project_quotas :
252+ session .refresh (project_quota )
253+ project_quota .quantity = old_quotas_map [project_quota .type ]
254+ session .add (project_quota )
255+ session .commit ()
256+ raise HTTPException (status_code = 400 , detail = "Cannot remove user from project, user share used quotas with project" )
257+
258+ # remove user from project
181259 os_user = openstack_get_or_create_user (username )
182260 openstack_client .roles .revoke (role = OPENSTACK_ROLE_MEMBER_ID , user = os_user .id , project = os_project .id )
261+ session .delete (assignment )
183262 session .commit ()
0 commit comments