|
| 1 | +""" |
| 2 | +This is a development script to write the future function to implement on the api |
| 3 | +
|
| 4 | +Objective final for api with all function below: |
| 5 | +- Sync the database of the api with the openstack keystone service |
| 6 | +- Enable user to add user to project it owns |
| 7 | +- Enable user to have dynamic quota for each project |
| 8 | +- Enable user to share quota with other project |
| 9 | +
|
| 10 | +Currently this script is used to sync the database of the api with the openstack keystone service on following aspects: user, project, project user membership, user quota, project quota |
| 11 | +""" |
| 12 | + |
| 13 | +import urllib3 |
| 14 | + |
| 15 | +urllib3.disable_warnings() |
| 16 | + |
| 17 | +from rich import print |
| 18 | +from rich.traceback import install |
| 19 | +install(show_locals=True) |
| 20 | +from sqlmodel import select, Session |
| 21 | +from fob_api import openstack, engine |
| 22 | +from fob_api.models.database import QuotaType, UserQuota, UserQuotaShare, Project, ProjectUserMembership, User |
| 23 | + |
| 24 | +keystone_client = openstack.get_keystone_client() |
| 25 | + |
| 26 | +import random |
| 27 | +import string |
| 28 | +random_password = lambda: "".join(random.choices(string.ascii_letters + string.digits, k=16)) |
| 29 | + |
| 30 | +BANNED_NAMES = ["admin", "serivce"] |
| 31 | +OPENSTACK_ROLE_MEMBER = keystone_client.roles.find(name="member") |
| 32 | + |
| 33 | +with Session(engine) as session: |
| 34 | + |
| 35 | + # TODO |
| 36 | + # Creation |
| 37 | + # 1 create user |
| 38 | + db_user: list[User] = session.exec(select(User)).all() |
| 39 | + openstack_users = keystone_client.users.list() |
| 40 | + |
| 41 | + db_user_names = [user.username for user in db_user if user.username not in BANNED_NAMES] |
| 42 | + openstack_user_names = [user.name for user in openstack_users if user.name not in BANNED_NAMES] |
| 43 | + |
| 44 | + user_to_create = [user for user in db_user if user.username not in openstack_user_names and user.username not in BANNED_NAMES] |
| 45 | + user_to_delete = [user for user in openstack_users if user.name not in db_user_names and user.name not in BANNED_NAMES] |
| 46 | + |
| 47 | + print("Creating missing users with random password:") |
| 48 | + for user in user_to_create: |
| 49 | + print(f"Creating user: {user.username}") |
| 50 | + keystone_client.users.create(name=user.username, password=random_password(), domain="default", enabled=True) |
| 51 | + print(f"Done") |
| 52 | + |
| 53 | + # 2 create project |
| 54 | + |
| 55 | + db_project: list[Project] = session.exec(select(Project)).all() |
| 56 | + openstack_projects = keystone_client.projects.list() |
| 57 | + |
| 58 | + db_project_names = [project.name for project in db_project] |
| 59 | + openstack_project_names = [project.name for project in openstack_projects] |
| 60 | + |
| 61 | + project_to_create = [project for project in db_project if project.name not in openstack_project_names and project.name not in BANNED_NAMES] |
| 62 | + project_to_delete = [project for project in openstack_projects if project.name not in db_project_names and project.name not in BANNED_NAMES] |
| 63 | + |
| 64 | + print("Creating missing projects:") |
| 65 | + for project in project_to_create: |
| 66 | + user = session.exec(select(User).where(User.id == project.owner_id)).first() |
| 67 | + print(f"Creating project: {project.name} with owner: {user.username}") |
| 68 | + keystone_client.projects.create(name=project.name, domain="default", enabled=True) |
| 69 | + print("Done") |
| 70 | + |
| 71 | + # 3 assign user to project |
| 72 | + db_assignments: list[ProjectUserMembership] = session.exec(select(ProjectUserMembership)).all() |
| 73 | + db_project: list[Project] = session.exec(select(Project)).all() |
| 74 | + |
| 75 | + # get required assignments from db |
| 76 | + merged_db_assignments = {} |
| 77 | + for project in db_project: |
| 78 | + if project.name not in merged_db_assignments: |
| 79 | + merged_db_assignments[project.name] = set() |
| 80 | + user = session.exec(select(User).where(User.id == project.owner_id)).first() |
| 81 | + merged_db_assignments[project.name].add(user.username) |
| 82 | + |
| 83 | + for assignment in db_assignments: |
| 84 | + user = session.exec(select(User).where(User.id == assignment.user_id)).first() |
| 85 | + project = session.exec(select(Project).where(Project.id == assignment.project_id)).first() |
| 86 | + merged_db_assignments[project.name].add(user.username) |
| 87 | + |
| 88 | + # get assignment from openstack |
| 89 | + merged_openstack_assignments = {} |
| 90 | + for assignment in keystone_client.role_assignments.list(): |
| 91 | + if "project" not in assignment.scope: |
| 92 | + continue |
| 93 | + |
| 94 | + project = keystone_client.projects.get(assignment.scope["project"]["id"]).name |
| 95 | + username = keystone_client.users.get(assignment.user["id"]).name |
| 96 | + |
| 97 | + if project in BANNED_NAMES or username in BANNED_NAMES: |
| 98 | + continue |
| 99 | + |
| 100 | + if project not in merged_openstack_assignments: |
| 101 | + merged_openstack_assignments[project] = set() |
| 102 | + merged_openstack_assignments[project].add(username) |
| 103 | + |
| 104 | + # give permission based on required assignments on db |
| 105 | + print("Assigning missing users to projects:") |
| 106 | + for project_name, users in merged_db_assignments.items(): |
| 107 | + for user in users: |
| 108 | + if project_name in merged_openstack_assignments and user in merged_openstack_assignments[project_name]: |
| 109 | + continue |
| 110 | + print(f"\t user: {user} to project: {project_name}") |
| 111 | + openstack_user = keystone_client.users.find(name=user) |
| 112 | + openstack_project = keystone_client.projects.find(name=project_name) |
| 113 | + keystone_client.roles.grant(role=OPENSTACK_ROLE_MEMBER, user=openstack_user, project=openstack_project) |
| 114 | + print("Done") |
| 115 | + |
| 116 | + # 4 assign quota to project |
| 117 | + users = session.exec(select(User).where(User.username != "admin")).all() |
| 118 | + for user in users: |
| 119 | + ## Calculate all quota for user with shared quota |
| 120 | + # extract max quota for user |
| 121 | + user_max_quota_dict = {k: 0 for k in QuotaType} |
| 122 | + for q in session.exec(select(UserQuota).where(UserQuota.user_id == user.id)).all(): |
| 123 | + user_max_quota_dict[QuotaType.from_str(q.type)] += q.quantity |
| 124 | + print(f"Max quota for user: {user.username}") |
| 125 | + print(user_max_quota_dict) |
| 126 | + |
| 127 | + # extract quota who user share with project |
| 128 | + user_shared_quota_dict = {k: 0 for k in QuotaType} |
| 129 | + for q in session.exec(select(UserQuotaShare).where(UserQuotaShare.user_id == user.id)).all(): |
| 130 | + user_shared_quota_dict[QuotaType.from_str(q.type)] += q.quantity |
| 131 | + |
| 132 | + print(f"Shared quota for user: {user.username}") |
| 133 | + print(user_shared_quota_dict) |
| 134 | + |
| 135 | + # calculate quota left for user |
| 136 | + user_left_quota_dict = {k: 0 for k in QuotaType} |
| 137 | + for k in user_max_quota_dict: |
| 138 | + user_left_quota_dict[k] = user_max_quota_dict[k] - user_shared_quota_dict[k] |
| 139 | + print(f"Left quota for user: {user.username}") |
| 140 | + print(user_left_quota_dict) |
| 141 | + |
| 142 | + # make inverstigation on limits system and create a nova endpoint to test quota assignement |
| 143 | + #print(keystone_client.limits.list()) |
| 144 | + # assign quota to project |
| 145 | + #for project in session.exec(select(Project)).all(): |
| 146 | + # project_quota_to_apply_dict = {k: 0 for k in QuotaType} |
| 147 | + # for q in session.exec(select(UserQuotaShare).where(UserQuotaShare.project_id == project.id)).all(): |
| 148 | + # project_quota_to_apply_dict[QuotaType.from_str(q.type)] += q.quantity |
| 149 | + # print(f"Quota to apply for project: {project.name}") |
| 150 | + # print(project_quota_to_apply_dict) |
| 151 | + # openstack_project = keystone_client.projects.find(name=project.name) |
| 152 | + # for k, v in project_quota_to_apply_dict.items(): |
| 153 | + # #keystone_client.projects.update_quota(openstack_project, k.value, v) |
| 154 | + |
| 155 | + |
| 156 | + # Deletion |
| 157 | + # try remove quota from project WARNING: check if quota is not used Or how can react nova if set under current usage |
| 158 | + |
| 159 | + # try remove user from project cant remove if user is owner |
| 160 | + print("Removing users from projects:") |
| 161 | + for project_name, users in merged_openstack_assignments.items(): |
| 162 | + for user in users: |
| 163 | + if project_name not in merged_db_assignments or user not in merged_db_assignments[project_name]: |
| 164 | + print(f"\t user: {user} from project: {project_name}") |
| 165 | + openstack_user = keystone_client.users.find(name=user) |
| 166 | + openstack_project = keystone_client.projects.find(name=project_name) |
| 167 | + keystone_client.roles.revoke(role=OPENSTACK_ROLE_MEMBER, user=openstack_user, project=openstack_project) |
| 168 | + |
| 169 | + # try remove project |
| 170 | + print("Removing projects:") |
| 171 | + for project in project_to_delete: |
| 172 | + if project.name in BANNED_NAMES: |
| 173 | + continue |
| 174 | + print(f"Removing project: {project.name}") |
| 175 | + openstack_project = keystone_client.projects.find(name=project.name) |
| 176 | + keystone_client.projects.delete(openstack_project) |
| 177 | + |
| 178 | + |
| 179 | + # try remove user |
| 180 | + print("Removing users:") |
| 181 | + for user in user_to_delete: |
| 182 | + if user.name in BANNED_NAMES: |
| 183 | + continue |
| 184 | + print(f"Removing user: {user.name}") |
| 185 | + openstack_user = keystone_client.users.find(name=user.name) |
| 186 | + keystone_client.users.delete(openstack_user) |
0 commit comments