Skip to content

Commit 7ce214f

Browse files
Merge pull request #73 from ral-facilities/36_limit_queue_files
Limit number of files that can be submitted, other bugfixes
2 parents 1069537 + ed57b56 commit 7ce214f

File tree

10 files changed

+374
-138
lines changed

10 files changed

+374
-138
lines changed

src/main/config/run.properties.example

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,11 @@ queue.maxActiveDownloads = 10
108108
# Limit the number files per queued Download part. Multiple Datasets will be combined into part
109109
# Downloads based on their fileCount up to this limit. If a single Dataset has a fileCount
110110
# greater than this limit, it will still be submitted in a part by itself.
111-
queue.maxFileCount = 10000
111+
queue.visit.maxPartFileCount = 10000
112+
113+
# Requests to the /queue/files endpoint will be rejected if they exceed this number of files
114+
# Any chunking should be done clientside
115+
queue.files.maxFileCount = 10000
112116

113117
# When queueing Downloads a positive priority will allow a User to proceed.
114118
# Non-positive values will block that User from submitting a request to the queue.

src/main/java/org/icatproject/topcat/IcatClient.java

Lines changed: 68 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package org.icatproject.topcat;
22

33
import java.util.Map;
4+
import java.util.Set;
45
import java.util.HashMap;
6+
import java.util.HashSet;
57
import java.util.List;
68
import java.util.ListIterator;
79
import java.util.ArrayList;
@@ -21,6 +23,30 @@
2123

2224
public class IcatClient {
2325

26+
public class DatafilesResponse {
27+
public final List<Long> ids = new ArrayList<>();
28+
public final Set<String> missing = new HashSet<>();
29+
public long totalSize = 0L;
30+
31+
/**
32+
* Submit a query for Datafiles, then appends the ids, increments the size, and
33+
* records any missing file locations.
34+
*
35+
* @param query Query to submit
36+
* @throws TopcatException If the query returns not authorized, or not found.
37+
*/
38+
public void submitDatafilesQuery(String query)
39+
throws TopcatException {
40+
JsonArray jsonArray = submitQuery(query);
41+
for (JsonObject jsonObject : jsonArray.getValuesAs(JsonObject.class)) {
42+
JsonObject datafile = jsonObject.getJsonObject("Datafile");
43+
ids.add(datafile.getJsonNumber("id").longValueExact());
44+
missing.remove(datafile.getString("location"));
45+
totalSize += datafile.getJsonNumber("fileSize").longValueExact();
46+
}
47+
}
48+
}
49+
2450
private Logger logger = LoggerFactory.getLogger(IcatClient.class);
2551

2652
private HttpClient httpClient;
@@ -139,7 +165,7 @@ public String getFullName() throws TopcatException {
139165
* @throws TopcatException
140166
*/
141167
public JsonArray getDatasets(String visitId) throws TopcatException {
142-
String query = "SELECT dataset.id, dataset.fileCount from Dataset dataset";
168+
String query = "SELECT dataset.id, dataset.fileCount, dataset.fileSize from Dataset dataset";
143169
query += " WHERE dataset.investigation.visitId = '" + visitId + "' ORDER BY dataset.id";
144170
return submitQuery(query);
145171
}
@@ -153,45 +179,44 @@ public JsonArray getDatasets(String visitId) throws TopcatException {
153179
* @throws TopcatException
154180
* @throws UnsupportedEncodingException
155181
*/
156-
public List<Long> getDatafiles(List<String> files) throws TopcatException, UnsupportedEncodingException {
157-
List<Long> datafileIds = new ArrayList<>();
182+
public DatafilesResponse getDatafiles(List<String> files) throws TopcatException, UnsupportedEncodingException {
183+
DatafilesResponse response = new DatafilesResponse();
158184
if (files.size() == 0) {
159185
// Ensure that we don't error when calling .next() below by returning early
160-
return datafileIds;
186+
return response;
161187
}
162188

163189
// Total limit - "entityManager?sessionId=" - `sessionId` - "?query=" - `queryPrefix` - `querySuffix
164-
// Limit is 1024 - 24 - 36 - 7 - 51 - 17
190+
// Limit is 1024 - 24 - 36 - 7 - 48 - 17
165191
int getUrlLimit = Integer.parseInt(Properties.getInstance().getProperty("getUrlLimit", "1024"));
166-
int chunkLimit = getUrlLimit - 135;
167-
String queryPrefix = "SELECT d.id from Datafile d WHERE d.location in (";
192+
int chunkLimit = getUrlLimit - 132;
193+
String queryPrefix = "SELECT d from Datafile d WHERE d.location in (";
168194
String querySuffix = ") ORDER BY d.id";
169195
ListIterator<String> iterator = files.listIterator();
170196

171-
String chunkedFiles = "'" + iterator.next() + "'";
197+
String file = iterator.next();
198+
String chunkedFiles = "'" + file + "'";
199+
response.missing.add(file);
172200
int chunkSize = URLEncoder.encode(chunkedFiles, "UTF8").length();
173201
while (iterator.hasNext()) {
174-
String file = "'" + iterator.next() + "'";
175-
int encodedFileLength = URLEncoder.encode(file, "UTF8").length();
202+
file = iterator.next();
203+
String quotedFile = "'" + file + "'";
204+
int encodedFileLength = URLEncoder.encode(quotedFile, "UTF8").length();
176205
if (chunkSize + 3 + encodedFileLength > chunkLimit) {
177-
JsonArray jsonArray = submitQuery(queryPrefix + chunkedFiles + querySuffix);
178-
for (JsonNumber datafileIdJsonNumber : jsonArray.getValuesAs(JsonNumber.class)) {
179-
datafileIds.add(datafileIdJsonNumber.longValueExact());
180-
}
206+
response.submitDatafilesQuery(queryPrefix + chunkedFiles + querySuffix);
181207

182-
chunkedFiles = file;
208+
chunkedFiles = quotedFile;
183209
chunkSize = encodedFileLength;
210+
response.missing.add(file);
184211
} else {
185-
chunkedFiles += "," + file;
212+
chunkedFiles += "," + quotedFile;
186213
chunkSize += 3 + encodedFileLength; // 3 is size of , when encoded as %2C
214+
response.missing.add(file);
187215
}
188216
}
189-
JsonArray jsonArray = submitQuery(queryPrefix + chunkedFiles + querySuffix);
190-
for (JsonNumber datafileIdJsonNumber : jsonArray.getValuesAs(JsonNumber.class)) {
191-
datafileIds.add(datafileIdJsonNumber.longValueExact());
192-
}
217+
response.submitDatafilesQuery(queryPrefix + chunkedFiles + querySuffix);
193218

194-
return datafileIds;
219+
return response;
195220
}
196221

197222
/**
@@ -209,6 +234,21 @@ public long getDatasetFileCount(long datasetId) throws TopcatException {
209234
return jsonArray.getJsonNumber(0).longValueExact();
210235
}
211236

237+
/**
238+
* Utility method to get the fileSize (not size) of a Dataset by SELECTing its
239+
* child Datafiles. Ideally the fileSize field should be used, this is a
240+
* fallback option if that field is not set.
241+
*
242+
* @param datasetId ICAT Dataset.id
243+
* @return The total size of Datafiles in the specified Dataset
244+
* @throws TopcatException
245+
*/
246+
public long getDatasetFileSize(long datasetId) throws TopcatException {
247+
String query = "SELECT SUM(datafile.fileSize) FROM Datafile datafile WHERE datafile.dataset.id = " + datasetId;
248+
JsonArray jsonArray = submitQuery(query);
249+
return jsonArray.getJsonNumber(0).longValueExact();
250+
}
251+
212252
/**
213253
* Utility method for submitting an unencoded query to the entityManager
214254
* endpoint, and returning the resultant JsonArray.
@@ -238,6 +278,11 @@ private JsonArray submitQuery(String query) throws TopcatException {
238278
/**
239279
* Gets a single Entity of the specified type, without any other conditions.
240280
*
281+
* NOTE: This function is written and intended for getting Investigation,
282+
* Dataset or Datafile entities as part of the tests. It does not handle casing of
283+
* entities containing multiple words, or querying for a specific instance of an
284+
* entity.
285+
*
241286
* @param entityType Type of ICAT Entity to get
242287
* @return A single ICAT Entity of the specified type as a JsonObject
243288
* @throws TopcatException
@@ -391,7 +436,8 @@ public int getQueuePriority(String userName) throws TopcatException {
391436
}
392437
}
393438

394-
if (!userName.startsWith(Properties.getInstance().getProperty("anonUserName"))) {
439+
String anonUserName = Properties.getInstance().getProperty("anonUserName");
440+
if (anonUserName == null || !userName.startsWith(anonUserName)) {
395441
// The anonymous cart username will end with the user's sessionId so cannot do .equals
396442
return priorityMap.getAuthenticatedPriority();
397443
} else {

src/main/java/org/icatproject/topcat/PriorityMap.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ public PriorityMap() {
3535
Properties properties = Properties.getInstance();
3636

3737
anonUserName = properties.getProperty("anonUserName", "");
38+
if (anonUserName.equals("")) {
39+
logger.warn("anonUserName not defined, cannot distinguish anonymous and authenticated users so "
40+
+ "authenticated priority will be used as default level");
41+
}
3842
anonDownloadEnabled = Boolean.parseBoolean(properties.getProperty("anonDownloadEnabled", "true"));
3943
String defaultString;
4044
if (anonDownloadEnabled) {

src/main/java/org/icatproject/topcat/StatusCheck.java

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import jakarta.ejb.EJB;
1515
import jakarta.ejb.Schedule;
1616
import jakarta.ejb.Singleton;
17+
import jakarta.json.JsonObject;
1718
import jakarta.persistence.EntityManager;
1819
import jakarta.persistence.PersistenceContext;
1920
import jakarta.persistence.TypedQuery;
@@ -114,35 +115,35 @@ public void updateStatuses(int pollDelay, int pollIntervalWait, IdsClient inject
114115
String queryString = selectString + " and " + notExpiredCondition + " and (" + isActiveCondition + ")";
115116

116117
TypedQuery<Download> query = em.createQuery(queryString, Download.class);
117-
List<Download> downloads = query.getResultList();
118+
List<Download> downloads = query.getResultList();
118119

119120
for (Download download : downloads) {
120-
Date lastCheck = lastChecks.get(download.getId());
121-
Date now = new Date();
122-
long createdSecondsAgo = (now.getTime() - download.getCreatedAt().getTime()) / 1000;
121+
Date lastCheck = lastChecks.get(download.getId());
122+
Date now = new Date();
123+
long createdSecondsAgo = (now.getTime() - download.getCreatedAt().getTime()) / 1000;
123124
if (download.getStatus() == DownloadStatus.PREPARING) {
124125
// If prepareDownload was called previously but caught an exception (other than
125126
// TopcatException), we should not call it again immediately, but should impose
126127
// a delay. See issue #462.
127128
if (lastCheck == null) {
128-
prepareDownload(download, injectedIdsClient);
129-
} else {
130-
long lastCheckSecondsAgo = (now.getTime() - lastCheck.getTime()) / 1000;
129+
prepareDownload(download, injectedIdsClient);
130+
} else {
131+
long lastCheckSecondsAgo = (now.getTime() - lastCheck.getTime()) / 1000;
131132
if (lastCheckSecondsAgo >= pollIntervalWait) {
132-
prepareDownload(download, injectedIdsClient);
133-
}
134-
}
135-
} else if (createdSecondsAgo >= pollDelay) {
133+
prepareDownload(download, injectedIdsClient);
134+
}
135+
}
136+
} else if (download.getPreparedId() != null && createdSecondsAgo >= pollDelay) {
136137
if (lastCheck == null) {
137-
performCheck(download, injectedIdsClient);
138-
} else {
139-
long lastCheckSecondsAgo = (now.getTime() - lastCheck.getTime()) / 1000;
138+
performCheck(download, injectedIdsClient);
139+
} else {
140+
long lastCheckSecondsAgo = (now.getTime() - lastCheck.getTime()) / 1000;
140141
if (lastCheckSecondsAgo >= pollIntervalWait) {
141-
performCheck(download, injectedIdsClient);
142-
}
142+
performCheck(download, injectedIdsClient);
143143
}
144144
}
145-
}
145+
}
146+
}
146147
}
147148

148149
private void performCheck(Download download, IdsClient injectedIdsClient) {
@@ -250,12 +251,14 @@ private void prepareDownload(Download download, IdsClient injectedIdsClient, Str
250251
String preparedId = idsClient.prepareData(sessionId, download.getInvestigationIds(), download.getDatasetIds(), download.getDatafileIds());
251252
download.setPreparedId(preparedId);
252253

253-
try {
254-
Long size = idsClient.getSize(sessionId, download.getInvestigationIds(), download.getDatasetIds(), download.getDatafileIds());
255-
download.setSize(size);
256-
} catch(Exception e) {
257-
logger.error("prepareDownload: setting size to -1 as getSize threw exception: " + e.getMessage());
258-
download.setSize(-1);
254+
if (download.getSize() <= 0) {
255+
try {
256+
Long size = idsClient.getSize(sessionId, download.getInvestigationIds(), download.getDatasetIds(), download.getDatafileIds());
257+
download.setSize(size);
258+
} catch(Exception e) {
259+
logger.error("prepareDownload: setting size to -1 as getSize threw exception: " + e.getMessage());
260+
download.setSize(-1);
261+
}
259262
}
260263

261264
if (download.getIsTwoLevel() || !download.getTransport().matches("https|http")) {
@@ -285,19 +288,20 @@ private void prepareDownload(Download download, IdsClient injectedIdsClient, Str
285288
* @param sessionIds Map from Facility to functional sessionId
286289
* @param facilityName Name of ICAT Facility to get the sessionId for
287290
* @return Functional ICAT sessionId
288-
* @throws InternalException If the facilityName cannot be mapped to an ICAT url
289-
* @throws BadRequestException If the login fails
291+
* @throws Exception If the login fails
290292
*/
291293
private String getQueueSessionId(Map<String, String> sessionIds, String facilityName)
292-
throws InternalException, BadRequestException {
294+
throws Exception {
293295
String sessionId = sessionIds.get(facilityName);
294296
if (sessionId == null) {
295297
IcatClient icatClient = new IcatClient(FacilityMap.getInstance().getIcatUrl(facilityName));
296298
Properties properties = Properties.getInstance();
297299
String plugin = properties.getProperty("queue.account." + facilityName + ".plugin");
298300
String username = properties.getProperty("queue.account." + facilityName + ".username");
299301
String password = properties.getProperty("queue.account." + facilityName + ".password");
300-
sessionId = icatClient.login(plugin, username, password);
302+
String jsonString = icatClient.login(plugin, username, password);
303+
JsonObject jsonObject = Utils.parseJsonObject(jsonString);
304+
sessionId = jsonObject.getString("sessionId");
301305
sessionIds.put(facilityName, sessionId);
302306
}
303307
return sessionId;
@@ -315,7 +319,7 @@ private String getQueueSessionId(Map<String, String> sessionIds, String facility
315319
*/
316320
public void startQueuedDownloads(int maxActiveDownloads) throws Exception {
317321
if (maxActiveDownloads == 0) {
318-
logger.debug("Preparing of queued jobs disabled by config, skipping");
322+
logger.trace("Preparing of queued jobs disabled by config, skipping");
319323
return;
320324
}
321325

@@ -332,7 +336,7 @@ public void startQueuedDownloads(int maxActiveDownloads) throws Exception {
332336
int activeDownloadsSize = activeDownloads.size();
333337
if (activeDownloadsSize >= maxActiveDownloads) {
334338
String format = "More downloads currently RESTORING {} than maxActiveDownloads {}, cannot prepare queued jobs";
335-
logger.info(format, activeDownloadsSize, maxActiveDownloads);
339+
logger.trace(format, activeDownloadsSize, maxActiveDownloads);
336340
return;
337341
}
338342
availableDownloads -= activeDownloadsSize;
@@ -346,13 +350,13 @@ public void startQueuedDownloads(int maxActiveDownloads) throws Exception {
346350
Map<String, String> sessionIds = new HashMap<>();
347351
if (maxActiveDownloads <= 0) {
348352
// No limits on how many to submit
349-
logger.info("Preparing {} queued downloads", queuedDownloads.size());
353+
logger.trace("Preparing {} queued downloads", queuedDownloads.size());
350354
for (Download queuedDownload : queuedDownloads) {
351355
queuedDownload.setStatus(DownloadStatus.PREPARING);
352356
prepareDownload(queuedDownload, null, getQueueSessionId(sessionIds, queuedDownload.getFacilityName()));
353357
}
354358
} else {
355-
logger.info("Preparing up to {} queued downloads", availableDownloads);
359+
logger.trace("Preparing up to {} queued downloads", availableDownloads);
356360
HashMap<Integer, List<Download>> mapping = new HashMap<>();
357361
for (Download queuedDownload : queuedDownloads) {
358362
String sessionId = getQueueSessionId(sessionIds, queuedDownload.getFacilityName());

0 commit comments

Comments
 (0)