Skip to content

Commit 2b9e7b7

Browse files
feature: pull in work from Aaron's pr related factory globus and metadata (#1603)
Co-authored-by: Aaron Perez <perezam@ornl.gov>
1 parent 9317569 commit 2b9e7b7

File tree

3 files changed

+448
-0
lines changed

3 files changed

+448
-0
lines changed
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
"use strict";
2+
3+
const {
4+
RepositoryType,
5+
Result,
6+
createRepository,
7+
createRepositoryData,
8+
createGlobusConfig,
9+
} = require("./types");
10+
const { validateGlobusConfig, validateMetadataConfig } = require("./validation");
11+
const globusRepo = require("./globus");
12+
const metadataRepo = require("./metadata");
13+
const g_lib = require("../support");
14+
15+
/**
16+
* Repository factory using Rust-compatible patterns
17+
* Uses switch/case for type-based polymorphism instead of inheritance
18+
*/
19+
20+
/**
21+
* Create repository based on type (similar to Rust match expression)
22+
* Rust's match expression provides exhaustive pattern matching
23+
* JavaScript's switch is used here to emulate this pattern
24+
* @param {object} config - Repository configuration object
25+
* @param {string} config.id - Repository ID
26+
* @param {string} config.type - Repository type (from RepositoryType enum)
27+
* @param {string} config.title - Repository title
28+
* @param {string} [config.desc] - Repository description
29+
* @param {number} config.capacity - Storage capacity in bytes
30+
* @param {string[]} config.admins - Array of admin user IDs
31+
* @param {string} [config.endpoint] - Globus endpoint (required for GLOBUS type)
32+
* @param {string} [config.path] - File path (required for GLOBUS type)
33+
* @param {string} [config.pub_key] - Public key for ZeroMQ CURVE authentication (required for GLOBUS type)
34+
* @param {string} [config.address] - Network address (required for GLOBUS type)
35+
* @param {string} [config.exp_path] - Export path (optional for GLOBUS type)
36+
* @returns {{ok: boolean, error: *}|{ok: boolean, value: *}} Result object containing repository or error
37+
* @see https://doc.rust-lang.org/book/ch06-02-match.html
38+
*/
39+
const createRepositoryByType = (config) => {
40+
const missingFields = [];
41+
if (!config.id) missingFields.push("id");
42+
if (!config.type) missingFields.push("type");
43+
if (!config.title) missingFields.push("title");
44+
if (!config.capacity) missingFields.push("capacity");
45+
if (!config.admins) missingFields.push("admins");
46+
47+
if (missingFields.length > 0) {
48+
return Result.err({
49+
code: g_lib.ERR_INVALID_PARAM,
50+
message: `Missing required repository fields: ${missingFields.join(", ")}`,
51+
});
52+
}
53+
/**
54+
* Type-based creation using switch (Rust match pattern)
55+
* Each case is like a match arm in Rust, handling a specific variant
56+
* @see https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html
57+
*/
58+
switch (config.type) {
59+
case RepositoryType.GLOBUS: {
60+
const validationResult = validateGlobusConfig(config);
61+
if (!validationResult.ok) {
62+
return validationResult;
63+
}
64+
65+
const globusConfig = createGlobusConfig({
66+
endpoint: config.endpoint,
67+
path: config.path,
68+
pub_key: config.pub_key,
69+
address: config.address,
70+
exp_path: config.exp_path,
71+
});
72+
73+
const repoData = createRepositoryData({
74+
id: config.id,
75+
type: config.type,
76+
title: config.title,
77+
desc: config.desc,
78+
capacity: config.capacity,
79+
admins: config.admins,
80+
typeSpecific: globusConfig,
81+
});
82+
83+
return Result.ok(createRepository(RepositoryType.GLOBUS, repoData));
84+
}
85+
86+
case RepositoryType.METADATA_ONLY: {
87+
const validationResult = validateMetadataConfig(config);
88+
if (!validationResult.ok) {
89+
return validationResult;
90+
}
91+
92+
const repoData = createRepositoryData({
93+
id: config.id,
94+
type: config.type,
95+
title: config.title,
96+
desc: config.desc,
97+
capacity: config.capacity,
98+
admins: config.admins,
99+
});
100+
101+
return Result.ok(createRepository(RepositoryType.METADATA_ONLY, repoData));
102+
}
103+
104+
default:
105+
/**
106+
* In Rust, match must be exhaustive - all cases must be handled
107+
* The default case ensures we handle unknown variants
108+
* @see https://doc.rust-lang.org/book/ch06-02-match.html#matching-with-option-t
109+
*/
110+
return Result.err({
111+
code: g_lib.ERR_INVALID_PARAM,
112+
message: `Unknown repository type: ${config.type}`,
113+
});
114+
}
115+
};
116+
117+
/**
118+
* Get repository implementation based on type
119+
* This emulates Rust's trait object dynamic dispatch
120+
* @param {string} repositoryType - Repository type from RepositoryType enum
121+
* @returns {object|null} Repository implementation object or null if not found
122+
* @see https://doc.rust-lang.org/book/ch17-02-trait-objects.html
123+
*/
124+
const getRepositoryImplementation = (repositoryType) => {
125+
switch (repositoryType) {
126+
case RepositoryType.GLOBUS:
127+
return globusRepo;
128+
case RepositoryType.METADATA_ONLY:
129+
return metadataRepo;
130+
default:
131+
return null;
132+
}
133+
};
134+
135+
/**
136+
* Execute operation on repository using dynamic dispatch
137+
* This pattern emulates Rust's trait method dispatch
138+
* @param {object} repository - Repository object with type and data fields
139+
* @param {string} operation - Operation name to execute
140+
* @param {...*} args - Additional arguments to pass to the operation
141+
* @returns {{ok: boolean, error: *}|*} Result of the operation
142+
* @see https://doc.rust-lang.org/book/ch17-02-trait-objects.html#trait-objects-perform-dynamic-dispatch
143+
*/
144+
const executeRepositoryOperation = (repository, operation, ...args) => {
145+
const impl = getRepositoryImplementation(repository.type);
146+
if (!impl) {
147+
return Result.err({
148+
code: g_lib.ERR_INVALID_PARAM,
149+
message: `No implementation for repository type: ${repository.type}`,
150+
});
151+
}
152+
153+
if (typeof impl[operation] !== "function") {
154+
return Result.err({
155+
code: g_lib.ERR_NOT_IMPLEMENTED,
156+
message: `Operation '${operation}' not implemented for type: ${repository.type}`,
157+
});
158+
}
159+
160+
return impl[operation](repository.data, ...args);
161+
};
162+
163+
module.exports = {
164+
createRepositoryByType,
165+
getRepositoryImplementation,
166+
executeRepositoryOperation,
167+
};
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
"use strict";
2+
3+
const { Result, ExecutionMethod, createAllocationResult } = require("./types");
4+
const { validateAllocationParams } = require("./validation");
5+
const g_tasks = require("../tasks");
6+
const g_lib = require("../support");
7+
8+
/**
9+
* @module globus
10+
* Globus repository implementation
11+
* Implements repository operations specific to Globus-backed repositories
12+
*/
13+
14+
/**
15+
* This module acts like a trait implementation for the Globus repository type
16+
* Each function implements a trait method for this specific type
17+
* @see https://doc.rust-lang.org/book/ch10-02-traits.html#implementing-a-trait-on-a-type
18+
*/
19+
20+
// Validate Globus repository (already validated in factory)
21+
const validate = (repoData) => {
22+
return Result.ok(true);
23+
};
24+
25+
// Create allocation in Globus repository (async via task)
26+
const createAllocation = (repoData, params) => {
27+
// Validate allocation parameters
28+
const validationResult = validateAllocationParams(params);
29+
if (!validationResult.ok) {
30+
return validationResult;
31+
}
32+
33+
try {
34+
// Create task for async Globus allocation
35+
// Note: taskInitAllocCreate expects (client, repo_id, subject_id, data_limit, rec_limit)
36+
// For now, using a system client since this is called from the new repository pattern
37+
const systemClient = { _id: "system", is_admin: true };
38+
39+
const taskResult = g_tasks.taskInitAllocCreate(
40+
systemClient,
41+
repoData._id,
42+
params.subject,
43+
params.size || params.data_limit, // Handle both parameter names
44+
params.rec_limit || 1000000, // Default to 1M records if not specified
45+
);
46+
47+
// The taskResult contains { task: taskObject }
48+
// We need to return the task properties that the web service expects
49+
const task = taskResult.task;
50+
51+
// Return a structure that matches what the original API expects
52+
// The web service needs properties like state, task_id, status, etc.
53+
return Result.ok({
54+
id: `alloc/${Date.now()}`, // Temporary allocation ID format
55+
repo_id: repoData._id,
56+
subject: params.subject,
57+
task_id: task._id,
58+
status: task.status,
59+
state: task.state, // Important: include the state property
60+
queue_time: task.ct || Date.now(),
61+
});
62+
} catch (e) {
63+
// Handle both Error objects and array-style errors
64+
const errorMessage = e.message || (Array.isArray(e) && e[1]) || String(e);
65+
return Result.err({
66+
code: g_lib.ERR_INTERNAL_FAULT,
67+
message: `Failed to create allocation task: ${errorMessage}`,
68+
});
69+
}
70+
};
71+
72+
// Delete allocation from Globus repository (async via task)
73+
const deleteAllocation = (repoData, subjectId) => {
74+
if (!subjectId || typeof subjectId !== "string") {
75+
return Result.err({
76+
code: g_lib.ERR_INVALID_PARAM,
77+
message: "Subject ID is required for allocation deletion",
78+
});
79+
}
80+
81+
try {
82+
// Create task for async Globus allocation deletion
83+
const task = g_tasks.repoAllocationDeleteTask({
84+
repo_id: repoData._id,
85+
subject: subjectId,
86+
});
87+
88+
return Result.ok(
89+
createAllocationResult(ExecutionMethod.TASK, {
90+
task_id: task.task_id,
91+
status: task.status,
92+
queue_time: task.queue_time,
93+
}),
94+
);
95+
} catch (e) {
96+
return Result.err({
97+
code: g_lib.ERR_INTERNAL_FAULT,
98+
message: `Failed to create deletion task: ${e.message}`,
99+
});
100+
}
101+
};
102+
103+
// Globus repositories support data operations
104+
const supportsDataOperations = (repoData) => {
105+
return Result.ok(true);
106+
};
107+
108+
// Get capacity information for Globus repository
109+
const getCapacityInfo = (repoData) => {
110+
try {
111+
// For Globus repos, we'd typically query the actual filesystem
112+
// For now, return the configured capacity
113+
return Result.ok({
114+
total_capacity: repoData.capacity,
115+
used_capacity: 0, // Would be populated from actual usage
116+
available_capacity: repoData.capacity,
117+
supports_quotas: true,
118+
});
119+
} catch (e) {
120+
return Result.err({
121+
code: g_lib.ERR_INTERNAL_FAULT,
122+
message: `Failed to get capacity info: ${e.message}`,
123+
});
124+
}
125+
};
126+
127+
/**
128+
* Export all operations (trait implementation)
129+
* These exports define the trait implementation for Globus repository type
130+
* allowing polymorphic behavior through dynamic dispatch
131+
* @type {object}
132+
* @property {function(object): {ok: boolean, value: boolean}} validate - Validate Globus repository
133+
* @property {function(object, object): {ok: boolean, error?: *, value?: *}} createAllocation - Create allocation in Globus repository
134+
* @property {function(object, string): {ok: boolean, error?: *, value?: *}} deleteAllocation - Delete allocation from Globus repository
135+
* @property {function(object): {ok: boolean, value: boolean}} supportsDataOperations - Check if supports data operations
136+
* @property {function(object): {ok: boolean, error?: *, value?: *}} getCapacityInfo - Get capacity information
137+
* @see https://doc.rust-lang.org/book/ch17-02-trait-objects.html
138+
*/
139+
module.exports = {
140+
validate,
141+
createAllocation,
142+
deleteAllocation,
143+
supportsDataOperations,
144+
getCapacityInfo,
145+
};

0 commit comments

Comments
 (0)