Skip to content

Commit 93b46e4

Browse files
committed
bulk project users
1 parent e2d18c0 commit 93b46e4

7 files changed

Lines changed: 621 additions & 0 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<!--
2+
Licensed to the Apache Software Foundation (ASF) under one
3+
or more contributor license agreements. See the NOTICE file
4+
distributed with this work for additional information
5+
regarding copyright ownership. The ASF licenses this file
6+
to you under the Apache License, Version 2.0 (the
7+
"License"); you may not use this file except in compliance
8+
with the License. You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing,
13+
software distributed under the License is distributed on an
14+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
KIND, either express or implied. See the License for the
16+
specific language governing permissions and limitations
17+
under the License.
18+
-->
19+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
20+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
21+
<modelVersion>4.0.0</modelVersion>
22+
<artifactId>cloud-plugin-api-bulk-project-users</artifactId>
23+
<name>Apache CloudStack Plugin - Bulk Project Users API</name>
24+
<parent>
25+
<groupId>org.apache.cloudstack</groupId>
26+
<artifactId>cloudstack-plugins</artifactId>
27+
<version>4.23.0.0-SNAPSHOT</version>
28+
<relativePath>../../pom.xml</relativePath>
29+
</parent>
30+
</project>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.apache.cloudstack.api.bulk.project.users;
19+
20+
import java.util.List;
21+
22+
import com.cloud.projects.ProjectAccount.Role;
23+
24+
public interface BulkProjectUsersService {
25+
26+
boolean addBulkUsersToProject(Long projectId, List<String> usernames, Long projectRoleId, Role projectRole);
27+
28+
boolean deleteBulkUsersFromProject(long projectId, List<Long> userIds);
29+
}
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.apache.cloudstack.api.bulk.project.users;
19+
20+
import java.sql.PreparedStatement;
21+
import java.sql.SQLException;
22+
import java.util.ArrayList;
23+
import java.util.List;
24+
25+
import javax.inject.Inject;
26+
27+
import org.apache.cloudstack.acl.ProjectRole;
28+
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
29+
import org.apache.cloudstack.acl.dao.ProjectRoleDao;
30+
import org.apache.cloudstack.api.bulk.project.users.command.AddBulkUsersToProjectCmd;
31+
import org.apache.cloudstack.api.bulk.project.users.command.DeleteBulkUsersFromProjectCmd;
32+
import org.apache.cloudstack.context.CallContext;
33+
34+
import com.cloud.exception.InvalidParameterValueException;
35+
import com.cloud.projects.Project;
36+
import com.cloud.projects.Project.State;
37+
import com.cloud.projects.ProjectAccount;
38+
import com.cloud.projects.ProjectAccount.Role;
39+
import com.cloud.projects.ProjectAccountVO;
40+
import com.cloud.projects.dao.ProjectAccountDao;
41+
import com.cloud.projects.dao.ProjectDao;
42+
import com.cloud.user.Account;
43+
import com.cloud.user.AccountManager;
44+
import com.cloud.user.User;
45+
import com.cloud.user.dao.AccountDao;
46+
import com.cloud.user.dao.UserDao;
47+
import com.cloud.utils.component.ManagerBase;
48+
import com.cloud.utils.component.PluggableService;
49+
import com.cloud.utils.db.TransactionLegacy;
50+
import com.cloud.utils.exception.CloudRuntimeException;
51+
52+
public class BulkProjectUsersServiceImpl extends ManagerBase implements BulkProjectUsersService, PluggableService {
53+
54+
@Inject
55+
private ProjectDao projectDao;
56+
@Inject
57+
private ProjectAccountDao projectAccountDao;
58+
@Inject
59+
private UserDao userDao;
60+
@Inject
61+
private AccountDao accountDao;
62+
@Inject
63+
private AccountManager accountMgr;
64+
@Inject
65+
private ProjectRoleDao projectRoleDao;
66+
67+
@Override
68+
public List<Class<?>> getCommands() {
69+
List<Class<?>> cmdList = new ArrayList<>();
70+
cmdList.add(AddBulkUsersToProjectCmd.class);
71+
cmdList.add(DeleteBulkUsersFromProjectCmd.class);
72+
return cmdList;
73+
}
74+
75+
@Override
76+
public boolean addBulkUsersToProject(Long projectId, List<String> usernames, Long projectRoleId, Role projectRole) {
77+
Account caller = CallContext.current().getCallingAccount();
78+
79+
Project project = projectDao.findById(projectId);
80+
if (project == null) {
81+
InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find project with specified id");
82+
ex.addProxyObject(String.valueOf(projectId), "projectId");
83+
throw ex;
84+
}
85+
86+
if (project.getState() != State.Active) {
87+
InvalidParameterValueException ex =
88+
new InvalidParameterValueException("Can't add users to project in state=" + project.getState() + " as it isn't currently active");
89+
ex.addProxyObject(project.getUuid(), "projectId");
90+
throw ex;
91+
}
92+
93+
CallContext.current().setProject(project);
94+
accountMgr.checkAccess(caller, AccessType.ModifyProject, true, accountMgr.getAccount(project.getProjectAccountId()));
95+
96+
if (projectRoleId != null && projectRoleId < 1L) {
97+
throw new InvalidParameterValueException("Invalid project role id provided");
98+
}
99+
100+
ProjectRole role = null;
101+
if (projectRoleId != null) {
102+
role = projectRoleDao.findById(projectRoleId);
103+
if (role == null || !role.getProjectId().equals(projectId)) {
104+
throw new InvalidParameterValueException("Invalid project role ID for the given project");
105+
}
106+
}
107+
108+
List<ProjectAccountVO> projectAccountsToInsert = new ArrayList<>();
109+
for (String username : usernames) {
110+
User user = userDao.getUserByName(username, project.getDomainId());
111+
if (user == null) {
112+
throw new InvalidParameterValueException("Invalid username provided: " + username);
113+
}
114+
115+
// Skip if user is already in the project
116+
ProjectAccount existingProjectAccount = projectAccountDao.findByProjectIdUserId(projectId, user.getAccountId(), user.getId());
117+
if (existingProjectAccount != null) {
118+
logger.info("User: {} is already added to project: {}, skipping", username, project);
119+
continue;
120+
}
121+
122+
Role effectiveRole = projectRole != null ? projectRole : Role.Regular;
123+
Long effectiveProjectRoleId = role != null ? role.getId() : null;
124+
ProjectAccountVO projectAccountVO = new ProjectAccountVO(project, user.getAccountId(), effectiveRole, user.getId(), effectiveProjectRoleId);
125+
projectAccountsToInsert.add(projectAccountVO);
126+
}
127+
128+
if (projectAccountsToInsert.isEmpty()) {
129+
logger.info("All specified users are already in project: {}", project);
130+
return true;
131+
}
132+
133+
batchInsertProjectAccounts(projectAccountsToInsert);
134+
logger.info("Successfully added {} users to project: {}", projectAccountsToInsert.size(), project);
135+
return true;
136+
}
137+
138+
@Override
139+
public boolean deleteBulkUsersFromProject(long projectId, List<Long> userIds) {
140+
Account caller = CallContext.current().getCallingAccount();
141+
142+
Project project = projectDao.findById(projectId);
143+
if (project == null) {
144+
InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find project with specified id");
145+
ex.addProxyObject(String.valueOf(projectId), "projectId");
146+
throw ex;
147+
}
148+
149+
CallContext.current().setProject(project);
150+
accountMgr.checkAccess(caller, AccessType.ModifyProject, true, accountMgr.getAccount(project.getProjectAccountId()));
151+
152+
// Validate all user IDs exist
153+
for (Long userId : userIds) {
154+
User user = userDao.findById(userId);
155+
if (user == null) {
156+
throw new InvalidParameterValueException("Invalid user ID provided: " + userId);
157+
}
158+
}
159+
160+
int removed = batchDeleteProjectUsers(projectId, userIds);
161+
logger.info("Successfully removed {} users from project: {}", removed, project);
162+
return removed > 0;
163+
}
164+
165+
/**
166+
* Performs a single batch INSERT of multiple project_account rows.
167+
*/
168+
private void batchInsertProjectAccounts(List<ProjectAccountVO> projectAccounts) {
169+
TransactionLegacy txn = TransactionLegacy.currentTxn();
170+
StringBuilder sql = new StringBuilder(
171+
"INSERT INTO project_account (project_id, account_id, user_id, account_role, project_account_id, project_role_id, created) VALUES ");
172+
for (int i = 0; i < projectAccounts.size(); i++) {
173+
if (i > 0) {
174+
sql.append(", ");
175+
}
176+
sql.append("(?, ?, ?, ?, ?, ?, NOW())");
177+
}
178+
try {
179+
txn.start();
180+
PreparedStatement pstmt = txn.prepareAutoCloseStatement(sql.toString());
181+
int index = 1;
182+
for (ProjectAccountVO pa : projectAccounts) {
183+
pstmt.setLong(index++, pa.getProjectId());
184+
pstmt.setLong(index++, pa.getAccountId());
185+
if (pa.getUserId() != null) {
186+
pstmt.setLong(index++, pa.getUserId());
187+
} else {
188+
pstmt.setNull(index++, java.sql.Types.BIGINT);
189+
}
190+
pstmt.setString(index++, pa.getAccountRole().toString());
191+
pstmt.setLong(index++, pa.getProjectAccountId());
192+
if (pa.getProjectRoleId() != null) {
193+
pstmt.setLong(index++, pa.getProjectRoleId());
194+
} else {
195+
pstmt.setNull(index++, java.sql.Types.BIGINT);
196+
}
197+
}
198+
pstmt.executeUpdate();
199+
txn.commit();
200+
} catch (SQLException e) {
201+
txn.rollback();
202+
throw new CloudRuntimeException("Failed to batch insert project accounts", e);
203+
}
204+
}
205+
206+
/**
207+
* Performs a single batch DELETE of multiple users from a project.
208+
*/
209+
private int batchDeleteProjectUsers(long projectId, List<Long> userIds) {
210+
TransactionLegacy txn = TransactionLegacy.currentTxn();
211+
StringBuilder sql = new StringBuilder("DELETE FROM project_account WHERE project_id = ? AND user_id IN (");
212+
for (int i = 0; i < userIds.size(); i++) {
213+
if (i > 0) {
214+
sql.append(", ");
215+
}
216+
sql.append("?");
217+
}
218+
sql.append(")");
219+
try {
220+
txn.start();
221+
PreparedStatement pstmt = txn.prepareAutoCloseStatement(sql.toString());
222+
int index = 1;
223+
pstmt.setLong(index++, projectId);
224+
for (Long userId : userIds) {
225+
pstmt.setLong(index++, userId);
226+
}
227+
int removed = pstmt.executeUpdate();
228+
txn.commit();
229+
return removed;
230+
} catch (SQLException e) {
231+
txn.rollback();
232+
throw new CloudRuntimeException("Failed to batch delete project accounts", e);
233+
}
234+
}
235+
}

0 commit comments

Comments
 (0)