Skip to content

Commit df518fd

Browse files
committed
feat: S3 CopyObject and postgres for development compose
1 parent f6d029d commit df518fd

5 files changed

Lines changed: 355 additions & 17 deletions

File tree

components/data_proxy/src/data_backends/filesystem_backend.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,4 +382,38 @@ impl StorageBackend for FSBackend {
382382
..Default::default()
383383
})
384384
}
385+
386+
#[tracing::instrument(level = "trace", skip(self, source, target))]
387+
/// Initialize a new location for a specific object
388+
/// This takes the object_info into account and creates a new location for the object
389+
async fn copy_data(&self, source: ObjectLocation, target: ObjectLocation) -> Result<()> {
390+
let mut source_file = tokio::fs::File::open(
391+
Path::new(&self.base_path)
392+
.join(&source.bucket)
393+
.join(&source.key),
394+
)
395+
.await
396+
.map_err(|e| {
397+
tracing::error!(error = ?e, msg = e.to_string());
398+
e
399+
})?;
400+
401+
let mut target_file = tokio::fs::File::open(
402+
Path::new(&self.base_path)
403+
.join(&target.bucket)
404+
.join(&target.key),
405+
)
406+
.await
407+
.map_err(|e| {
408+
tracing::error!(error = ?e, msg = e.to_string());
409+
e
410+
})?;
411+
tokio::io::copy(&mut source_file, &mut target_file)
412+
.await
413+
.map_err(|e| {
414+
tracing::error!(error = ?e, msg = e.to_string());
415+
e
416+
})?;
417+
Ok(())
418+
}
385419
}

components/data_proxy/src/data_backends/s3_backend.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,21 @@ impl StorageBackend for S3Backend {
394394
..Default::default()
395395
})
396396
}
397+
398+
#[tracing::instrument(level = "trace", skip(self, source, target))]
399+
/// Initialize a new location for a specific object
400+
/// This takes the object_info into account and creates a new location for the object
401+
async fn copy_data(&self, source: ObjectLocation, target: ObjectLocation) -> Result<()> {
402+
self.check_and_create_bucket(target.bucket.clone()).await?;
403+
self.s3_client
404+
.copy_object()
405+
.copy_source(&[source.bucket, source.key].join("/"))
406+
.bucket(target.bucket)
407+
.key(target.key)
408+
.send()
409+
.await?;
410+
Ok(())
411+
}
397412
}
398413

399414
impl S3Backend {

components/data_proxy/src/data_backends/storage_backend.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,10 @@ pub trait StorageBackend: Debug + Send + Sync {
8181
upload_id: String,
8282
) -> Result<()>;
8383

84-
/// Finishes multipart uploads
84+
/// Aborts multipart uploads
8585
/// # Arguments
8686
///
8787
/// * `location` - The location of the object
88-
/// * `parts` - The sequence of all uploaded parts that contain their part_number and their ETag
8988
/// * `upload_id` - The upload id of the multipart uploads
9089
async fn abort_multipart_upload(
9190
&self,
@@ -114,4 +113,16 @@ pub trait StorageBackend: Debug + Send + Sync {
114113
names: [Option<(DieselUlid, String)>; 4],
115114
temp: bool,
116115
) -> Result<ObjectLocation>;
116+
117+
118+
/// Copies data from source to target
119+
/// # Arguments
120+
///
121+
/// * `source` - The location of the source object
122+
/// * `target` - The location of the target object
123+
async fn copy_data(
124+
&self,
125+
source: ObjectLocation,
126+
target: ObjectLocation,
127+
) -> Result<()>;
117128
}

components/data_proxy/src/s3_frontend/s3service.rs

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2126,4 +2126,267 @@ impl S3 for ArunaS3Service {
21262126

21272127
Ok(resp)
21282128
}
2129+
2130+
#[tracing::instrument(err)]
2131+
#[allow(clippy::blocks_in_conditions)]
2132+
async fn copy_object(
2133+
&self,
2134+
req: S3Request<CopyObjectInput>,
2135+
) -> S3Result<S3Response<CopyObjectOutput>> {
2136+
// Check access for target
2137+
let CheckAccessResult {
2138+
user_state,
2139+
headers,
2140+
objects_state,
2141+
} = req
2142+
.extensions
2143+
.get::<CheckAccessResult>()
2144+
.cloned()
2145+
.ok_or_else(|| {
2146+
error!(error = "Missing data context");
2147+
s3_error!(UnexpectedContent, "Missing data context")
2148+
})?;
2149+
2150+
// Query results from check access
2151+
let (states, _) = objects_state.require_regular()?;
2152+
let all = states.to_new_or_existing()?;
2153+
trace!(?all);
2154+
let (_, collection, dataset, new_object, path) = all;
2155+
2156+
// Authorize existing object and get object + location
2157+
let (existing_object, existing_location) =
2158+
if let CopySource::Bucket { key, bucket, .. } = &req.input.copy_source {
2159+
let lock = self.cache.auth.read().await;
2160+
let auth = match lock.as_ref() {
2161+
Some(auth) => auth,
2162+
None => {
2163+
return Err(s3_error!(
2164+
InternalError,
2165+
"Could not access authorization handler"
2166+
));
2167+
}
2168+
};
2169+
let CheckAccessResult { objects_state, .. } = auth
2170+
.handle_object(
2171+
bucket,
2172+
key,
2173+
&http::Method::GET,
2174+
req.credentials.as_ref(),
2175+
&req.headers,
2176+
)
2177+
.await?;
2178+
drop(lock);
2179+
2180+
let (states, location) = objects_state.require_regular()?;
2181+
let all = states.to_new_or_existing()?;
2182+
trace!(?all);
2183+
let (_, _, _, object, _) = all;
2184+
2185+
let NewOrExistingObject::Existing(object) = object else {
2186+
return Err(s3_error!(NoSuchKey, "Source object not found"));
2187+
};
2188+
let Some(location) = location else {
2189+
return Err(s3_error!(NoSuchKey, "Location of source object not found"));
2190+
};
2191+
(object, location)
2192+
} else {
2193+
return Err(s3_error!(
2194+
InvalidRequest,
2195+
"AWS Access points are unsupported"
2196+
));
2197+
};
2198+
2199+
let md5hash = existing_object.hashes.get("MD5").cloned();
2200+
let sha256hash = existing_object.hashes.get("SHA256").cloned();
2201+
2202+
// Create missing objects for copy
2203+
let impersonating_token =
2204+
user_state.sign_impersonating_token(self.cache.auth.read().await.as_ref());
2205+
2206+
let (mut new_object, exists) = match new_object {
2207+
NewOrExistingObject::Missing(mut new_object) => {
2208+
let mut collection_id = None;
2209+
if let NewOrExistingObject::Missing(collection) = collection {
2210+
trace!(?collection);
2211+
if let Some(handler) = self.cache.aruna_client.read().await.as_ref() {
2212+
if let Some(token) = &impersonating_token {
2213+
let col = handler.create_collection(collection, token).await.map_err(
2214+
|_| {
2215+
error!(error = "Unable to create collection");
2216+
s3_error!(InternalError, "Unable to create collection")
2217+
},
2218+
)?;
2219+
collection_id = Some(col.id);
2220+
}
2221+
}
2222+
}
2223+
2224+
let mut dataset_id = None;
2225+
if let NewOrExistingObject::Missing(mut dataset) = dataset {
2226+
trace!(?dataset);
2227+
if let Some(handler) = self.cache.aruna_client.read().await.as_ref() {
2228+
if let Some(token) = &impersonating_token {
2229+
if let Some(collection_id) = collection_id {
2230+
dataset.parents =
2231+
Some(HashSet::from_iter([TypedRelation::Collection(
2232+
collection_id,
2233+
)]));
2234+
}
2235+
let dataset =
2236+
handler.create_dataset(dataset, token).await.map_err(|_| {
2237+
error!(error = "Unable to create dataset");
2238+
s3_error!(InternalError, "Unable to create dataset")
2239+
})?;
2240+
dataset_id = Some(dataset.id);
2241+
}
2242+
}
2243+
}
2244+
2245+
if let Some(dataset_id) = dataset_id {
2246+
new_object.parents =
2247+
Some(HashSet::from_iter([TypedRelation::Dataset(dataset_id)]));
2248+
} else if let Some(collection_id) = collection_id {
2249+
new_object.parents = Some(HashSet::from_iter([TypedRelation::Collection(
2250+
collection_id,
2251+
)]));
2252+
}
2253+
(new_object, false)
2254+
}
2255+
NewOrExistingObject::Existing(mut object) => {
2256+
if object.object_status == Status::Initializing {
2257+
(object, true)
2258+
} else {
2259+
if let Some(handler) = self.cache.aruna_client.read().await.as_ref() {
2260+
if let Some(token) = &impersonating_token {
2261+
handler
2262+
.init_object_update(object.clone(), token, true)
2263+
.await
2264+
.map_err(|_| {
2265+
error!(error = "Object update failed");
2266+
s3_error!(InternalError, "Object update failed")
2267+
})?
2268+
} else {
2269+
error!("missing impersonating token");
2270+
return Err(s3_error!(InternalError, "Token creation failed"));
2271+
}
2272+
} else {
2273+
error!("ArunaServer client not available");
2274+
return Err(s3_error!(InternalError, "ArunaServer client not available"));
2275+
};
2276+
object.hashes = existing_object.hashes.clone();
2277+
object.synced = existing_object.synced;
2278+
object.children = None; // This should be None anyway, because this is an Object
2279+
object.dynamic = existing_object.dynamic;
2280+
2281+
(object, true)
2282+
}
2283+
}
2284+
NewOrExistingObject::None => {
2285+
return Err(s3_error!(
2286+
InvalidObjectState,
2287+
"Object in invalid state: None"
2288+
))
2289+
}
2290+
};
2291+
2292+
// Create new location
2293+
let new_location = self
2294+
.backend
2295+
.initialize_location(
2296+
&new_object,
2297+
Some(existing_location.raw_content_len),
2298+
path,
2299+
false,
2300+
)
2301+
.await
2302+
.map_err(|_| {
2303+
error!(error = "Unable to create object_location");
2304+
s3_error!(InternalError, "Unable to create object_location")
2305+
})?;
2306+
let content_len = new_location.raw_content_len;
2307+
2308+
// Copy data
2309+
self.backend
2310+
.copy_data(existing_location, new_location.clone())
2311+
.await
2312+
.map_err(|e| {
2313+
error!(error = ?e, "Unable to create and finish object");
2314+
s3_error!(InternalError, "{}", e.to_string())
2315+
})?;
2316+
2317+
// Finish object
2318+
if let Some(handler) = self.cache.aruna_client.read().await.as_ref() {
2319+
if let Some(token) = &impersonating_token {
2320+
if exists {
2321+
new_object = handler
2322+
.finish_object(
2323+
new_object.id,
2324+
content_len,
2325+
existing_object.get_hashes(),
2326+
token,
2327+
)
2328+
.await
2329+
.map_err(|_| {
2330+
error!(error = "Unable to finish object");
2331+
s3_error!(InternalError, "Unable to finish object")
2332+
})?;
2333+
} else {
2334+
trace!(?new_object);
2335+
new_object = handler
2336+
.create_and_finish(new_object.clone(), content_len, token)
2337+
.await
2338+
.map_err(|e| {
2339+
error!(error = ?e, "Unable to create and finish object");
2340+
s3_error!(InvalidObjectState, "{}", e.to_string())
2341+
})?;
2342+
}
2343+
}
2344+
}
2345+
2346+
trace!("Before finish");
2347+
2348+
// Finish location
2349+
self.cache
2350+
.add_location_with_binding(new_object.id, new_location)
2351+
.await
2352+
.map_err(|e| {
2353+
error!(error = ?e, msg = "Unable to add location with binding");
2354+
s3_error!(InternalError, "Unable to add location with binding")
2355+
})?;
2356+
2357+
trace!("After finish");
2358+
2359+
let response = CopyObjectOutput {
2360+
copy_object_result: Some(CopyObjectResult {
2361+
e_tag: md5hash,
2362+
checksum_sha256: sha256hash,
2363+
last_modified: Some(
2364+
time::OffsetDateTime::from_unix_timestamp(
2365+
(new_object.id.timestamp() / 1000) as i64,
2366+
)
2367+
.map_err(|_| {
2368+
error!(error = "Unable to parse timestamp");
2369+
s3_error!(InternalError, "Unable to parse timestamp")
2370+
})?
2371+
.into(),
2372+
),
2373+
..Default::default()
2374+
}),
2375+
..Default::default()
2376+
};
2377+
let mut resp = S3Response::new(response);
2378+
if let Some(headers) = headers {
2379+
for (k, v) in headers {
2380+
resp.headers.insert(
2381+
HeaderName::from_bytes(k.as_bytes())
2382+
.map_err(|_| s3_error!(InternalError, "Unable to parse header name"))?,
2383+
HeaderValue::from_str(&v)
2384+
.map_err(|_| s3_error!(InternalError, "Unable to parse header value"))?,
2385+
);
2386+
}
2387+
}
2388+
2389+
trace!("Respond");
2390+
Ok(resp)
2391+
}
21292392
}

0 commit comments

Comments
 (0)