Skip to content

Commit 300f643

Browse files
committed
rgw/cloudrestore: Add Restore support from Glacier/Tape cloud endpoints
Unlike regular S3 cloud services, restoring objects from S3/Tape or AWS Glacier services would require special handling. We need to first restore the object using Glacier RestoreObject API and then download it using GET. https://docs.aws.amazon.com/cli/latest/reference/s3api/restore-object.html This PR adds that support for "Expedited" tier retrieval type. That means the restore would be quick and the object can be downloaded soon. TODO: "Standard" tier-type support. Need to handle the case where in restore from cloud endpoint could take a longer time and need to be monitored periodically in the background. Signed-off-by: Soumya Koduri <[email protected]>
1 parent 38d2c11 commit 300f643

File tree

8 files changed

+210
-11
lines changed

8 files changed

+210
-11
lines changed
-68 KB
Binary file not shown.

src/rgw/driver/rados/rgw_lc_tier.cc

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,70 @@ static const struct generic_attr generic_attrs[] = {
252252
{ "ETAG", RGW_ATTR_ETAG },
253253
};
254254

255+
/* Restore object from remote endpoint.
256+
*/
257+
int rgw_cloud_tier_restore_object(RGWLCCloudTierCtx& tier_ctx,
258+
std::map<std::string, std::string>& headers,
259+
real_time* pset_mtime, std::string& etag,
260+
uint64_t& accounted_size, rgw::sal::Attrs& attrs,
261+
std::optional<uint64_t> days,
262+
RGWZoneGroupTierS3Glacier& glacier_params,
263+
void* cb) {
264+
RGWRESTConn::get_obj_params req_params;
265+
std::string target_obj_name;
266+
int ret = 0;
267+
rgw_lc_obj_properties obj_properties(tier_ctx.o.meta.mtime, tier_ctx.o.meta.etag,
268+
tier_ctx.o.versioned_epoch, tier_ctx.acl_mappings,
269+
tier_ctx.target_storage_class);
270+
271+
rgw_bucket dest_bucket;
272+
dest_bucket.name = tier_ctx.target_bucket_name;
273+
target_obj_name = tier_ctx.bucket_info.bucket.name + "/" +
274+
tier_ctx.obj->get_name();
275+
if (!tier_ctx.o.is_current()) {
276+
target_obj_name += get_key_instance(tier_ctx.obj->get_key());
277+
}
278+
279+
if (glacier_params.glacier_restore_tier_type != GlacierRestoreTierType::Expedited) {
280+
//XXX: Supporting STANDARD tier type is still in WIP
281+
ldpp_dout(tier_ctx.dpp, -1) << __func__ << "ERROR: Only Expedited tier_type is supported " << dendl;
282+
return -1;
283+
}
284+
285+
rgw_obj dest_obj(dest_bucket, rgw_obj_key(target_obj_name));
286+
287+
ret = cloud_tier_restore(tier_ctx.dpp, tier_ctx.conn, dest_obj, days, glacier_params);
288+
289+
ldpp_dout(tier_ctx.dpp, 20) << __func__ << "Restoring object=" << dest_obj << "returned ret = " << ret << dendl;
290+
291+
if (ret < 0 ) {
292+
ldpp_dout(tier_ctx.dpp, -1) << __func__ << "ERROR: failed to restore object=" << dest_obj << "; ret = " << ret << dendl;
293+
return ret;
294+
}
295+
296+
// now send HEAD request and verify if restore is complete on glacier/tape endpoint
297+
bool restore_in_progress = false;
298+
do {
299+
ret = rgw_cloud_tier_get_object(tier_ctx, true, headers, nullptr, etag,
300+
accounted_size, attrs, nullptr);
301+
302+
if (ret < 0) {
303+
ldpp_dout(tier_ctx.dpp, 0) << __func__ << "ERROR: failed to fetch HEAD from cloud for obj=" << tier_ctx.obj << " , ret = " << ret << dendl;
304+
return ret;
305+
}
306+
307+
restore_in_progress = is_restore_in_progress(tier_ctx.dpp, headers);
308+
} while(restore_in_progress);
309+
310+
// now do the actual GET
311+
ret = rgw_cloud_tier_get_object(tier_ctx, false, headers, pset_mtime, etag,
312+
accounted_size, attrs, cb);
313+
314+
ldpp_dout(tier_ctx.dpp, 20) << __func__ << "(): fetching object from cloud bucket:" << dest_bucket << ", object: " << target_obj_name << " returned ret:" << ret << dendl;
315+
316+
return ret;
317+
}
318+
255319
/* Read object or just head from remote endpoint.
256320
*/
257321
int rgw_cloud_tier_get_object(RGWLCCloudTierCtx& tier_ctx, bool head,
@@ -367,6 +431,30 @@ static bool is_already_tiered(const DoutPrefixProvider *dpp,
367431
return 0;
368432
}
369433

434+
bool is_restore_in_progress(const DoutPrefixProvider *dpp,
435+
std::map<std::string, std::string>& headers) {
436+
map<string, string> attrs = headers;
437+
438+
for (const auto& a : attrs) {
439+
ldpp_dout(dpp, 20) << "GetCrf attr[" << a.first << "] = " << a.second <<dendl;
440+
}
441+
string s = attrs["X_AMZ_RESTORE"];
442+
443+
if (s.empty())
444+
s = attrs["x_amz_restore"];
445+
446+
ldpp_dout(dpp, 0) << "is_already_tiered attrs[X_AMZ_RESTORE] = " << s <<dendl;
447+
448+
if (!s.empty()){
449+
const char *r_str = "ongoing-request=\"true\"";
450+
const char *found = std::strstr(s.c_str(), r_str);
451+
if (found) {
452+
return true;
453+
}
454+
}
455+
return false;;
456+
}
457+
370458
/* Read object locally & also initialize dest rest obj based on read attrs */
371459
class RGWLCStreamRead
372460
{
@@ -914,6 +1002,95 @@ static int cloud_tier_send_multipart_part(RGWLCCloudTierCtx& tier_ctx,
9141002
return 0;
9151003
}
9161004

1005+
int cloud_tier_restore(const DoutPrefixProvider *dpp, RGWRESTConn& dest_conn,
1006+
const rgw_obj& dest_obj, std::optional<uint64_t> days,
1007+
RGWZoneGroupTierS3Glacier& glacier_params) {
1008+
rgw_http_param_pair params[] = {{"restore", nullptr}, {nullptr, nullptr}};
1009+
// XXX: include versionId=VersionId in the params above
1010+
1011+
stringstream ss;
1012+
XMLFormatter formatter;
1013+
int ret;
1014+
1015+
bufferlist bl, out_bl;
1016+
string resource = obj_to_aws_path(dest_obj);
1017+
1018+
const std::string tier_v = (glacier_params.glacier_restore_tier_type == GlacierRestoreTierType::Expedited) ? "Expedited" : "Standard";
1019+
1020+
struct RestoreRequest {
1021+
std::optional<uint64_t> days;
1022+
std::optional<std::string> tier;
1023+
1024+
explicit RestoreRequest(std::optional<uint64_t> _days, std::optional<std::string> _tier) : days(_days), tier(_tier) {}
1025+
1026+
void dump_xml(Formatter *f) const {
1027+
encode_xml("Days", days, f);
1028+
if (tier) {
1029+
f->open_object_section("GlacierJobParameters");
1030+
encode_xml("Tier", tier, f);
1031+
f->close_section();
1032+
};
1033+
}
1034+
} req_enc(days, tier_v);
1035+
1036+
struct RestoreResult {
1037+
std::string code;
1038+
1039+
void decode_xml(XMLObj *obj) {
1040+
RGWXMLDecoder::decode_xml("Code", code, obj);
1041+
}
1042+
} result;
1043+
1044+
req_enc.days = glacier_params.glacier_restore_days;
1045+
req_enc.tier = tier_v;
1046+
encode_xml("RestoreRequest", req_enc, &formatter);
1047+
1048+
formatter.flush(ss);
1049+
bl.append(ss.str());
1050+
1051+
ret = dest_conn.send_resource(dpp, "POST", resource, params, nullptr,
1052+
out_bl, &bl, nullptr, null_yield);
1053+
1054+
if (ret < 0) {
1055+
ldpp_dout(dpp, 0) << __func__ << "ERROR: failed to send Restore request to cloud for obj=" << dest_obj << " , ret = " << ret << dendl;
1056+
} else {
1057+
ldpp_dout(dpp, 0) << __func__ << "Sent Restore request to cloud for obj=" << dest_obj << " , ret = " << ret << dendl;
1058+
}
1059+
1060+
if (out_bl.length() > 0) {
1061+
RGWXMLDecoder::XMLParser parser;
1062+
if (!parser.init()) {
1063+
ldpp_dout(dpp, 0) << "ERROR: failed to initialize xml parser for parsing restore request response from server" << dendl;
1064+
return -EIO;
1065+
}
1066+
1067+
if (!parser.parse(out_bl.c_str(), out_bl.length(), 1)) {
1068+
string str(out_bl.c_str(), out_bl.length());
1069+
ldpp_dout(dpp, 5) << "ERROR: failed to parse xml restore: " << str << dendl;
1070+
return -EIO;
1071+
}
1072+
1073+
try {
1074+
RGWXMLDecoder::decode_xml("Error", result, &parser, true);
1075+
} catch (RGWXMLDecoder::err& err) {
1076+
string str(out_bl.c_str(), out_bl.length());
1077+
ldpp_dout(dpp, 5) << "ERROR: unexpected xml: " << str << dendl;
1078+
return -EIO;
1079+
}
1080+
1081+
ldpp_dout(dpp, 0) << "ERROR: Restore request received result : " << result.code << dendl;
1082+
if (result.code != "RestoreAlreadyInProgress") {
1083+
return -EIO;
1084+
} else { // treat as success
1085+
return 0;
1086+
}
1087+
1088+
ldpp_dout(dpp, 0) << "ERROR: restore req failed with error: " << result.code << dendl;
1089+
}
1090+
1091+
return ret;
1092+
}
1093+
9171094
static int cloud_tier_abort_multipart(const DoutPrefixProvider *dpp,
9181095
RGWRESTConn& dest_conn, const rgw_obj& dest_obj,
9191096
const std::string& upload_id) {

src/rgw/driver/rados/rgw_lc_tier.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,18 @@ int rgw_cloud_tier_get_object(RGWLCCloudTierCtx& tier_ctx, bool head,
5757
real_time* pset_mtime, std::string& etag,
5858
uint64_t& accounted_size, rgw::sal::Attrs& attrs,
5959
void* cb);
60+
int rgw_cloud_tier_restore_object(RGWLCCloudTierCtx& tier_ctx,
61+
std::map<std::string, std::string>& headers,
62+
real_time* pset_mtime, std::string& etag,
63+
uint64_t& accounted_size, rgw::sal::Attrs& attrs,
64+
std::optional<uint64_t> days,
65+
RGWZoneGroupTierS3Glacier& glacier_params,
66+
void* cb);
67+
68+
int cloud_tier_restore(const DoutPrefixProvider *dpp,
69+
RGWRESTConn& dest_conn, const rgw_obj& dest_obj,
70+
std::optional<uint64_t> days,
71+
RGWZoneGroupTierS3Glacier& glacier_params);
72+
73+
bool is_restore_in_progress(const DoutPrefixProvider *dpp,
74+
std::map<std::string, std::string>& headers);

src/rgw/driver/rados/rgw_rados.cc

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5290,14 +5290,8 @@ int RGWRados::restore_obj_from_cloud(RGWLCCloudTierCtx& tier_ctx,
52905290
dest_placement.storage_class = tier_ctx.restore_storage_class;
52915291
RGWRadosPutObj cb(dpp, cct, plugin, compressor, &processor, progress_cb, progress_data,
52925292
[&](map<string, bufferlist> obj_attrs) {
5293-
// XXX: do we need filter() lke in fetch_remote_obj() cb
5293+
// XXX: do we need filter() like in fetch_remote_obj() cb
52945294
dest_placement.inherit_from(dest_bucket_info.placement_rule);
5295-
/* For now we always restore to STANDARD storage-class.
5296-
* Later we will add support to take restore-target-storage-class
5297-
* for permanent restore
5298-
*/
5299-
// dest_placement.storage_class = RGW_STORAGE_CLASS_STANDARD;
5300-
53015295
processor.set_tail_placement(dest_placement);
53025296

53035297
ret = processor.prepare(rctx.y);
@@ -5326,9 +5320,18 @@ int RGWRados::restore_obj_from_cloud(RGWLCCloudTierCtx& tier_ctx,
53265320
real_time set_mtime;
53275321
std::map<std::string, std::string> headers;
53285322
ldpp_dout(dpp, 20) << "Fetching from cloud, object:" << dest_obj << dendl;
5329-
ret = rgw_cloud_tier_get_object(tier_ctx, false, headers,
5323+
if (tier_config.tier_placement.tier_type == "cloud-s3-glacier") {
5324+
ldpp_dout(dpp, 20) << "Restoring object:" << dest_obj << " from the cloud" << dendl;
5325+
RGWZoneGroupTierS3Glacier& glacier_params = tier_config.tier_placement.s3_glacier;
5326+
ret = rgw_cloud_tier_restore_object(tier_ctx, headers,
5327+
&set_mtime, etag, accounted_size,
5328+
attrs, days, glacier_params, &cb);
5329+
} else {
5330+
ldpp_dout(dpp, 20) << "Fetching object:" << dest_obj << "from the cloud" << dendl;
5331+
ret = rgw_cloud_tier_get_object(tier_ctx, false, headers,
53305332
&set_mtime, etag, accounted_size,
53315333
attrs, &cb);
5334+
}
53325335

53335336
if (ret < 0) {
53345337
ldpp_dout(dpp, 20) << "Fetching from cloud failed, object:" << dest_obj << dendl;

src/rgw/rgw_common.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ rgw_http_errors rgw_http_s3_errors({
139139
{ ERR_NO_SUCH_BUCKET_ENCRYPTION_CONFIGURATION, {404, "ServerSideEncryptionConfigurationNotFoundError"}},
140140
{ ERR_NO_SUCH_PUBLIC_ACCESS_BLOCK_CONFIGURATION, {404, "NoSuchPublicAccessBlockConfiguration"}},
141141
{ ERR_ACCOUNT_EXISTS, {409, "AccountAlreadyExists"}},
142+
{ ERR_RESTORE_ALREADY_IN_PROGRESS, {409, "RestoreAlreadyInProgress"}},
142143
{ ECANCELED, {409, "ConcurrentModification"}},
143144
});
144145

src/rgw/rgw_common.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,8 @@ inline constexpr const char* RGW_REST_STS_XMLNS =
355355
#define ERR_NO_SUCH_TAG_SET 2402
356356
#define ERR_ACCOUNT_EXISTS 2403
357357

358+
#define ERR_RESTORE_ALREADY_IN_PROGRESS 2500
359+
358360
#ifndef UINT32_MAX
359361
#define UINT32_MAX (0xffffffffu)
360362
#endif

src/rgw/rgw_rest_s3.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3614,8 +3614,9 @@ void RGWRestoreObj_ObjStore_S3::send_response()
36143614
if (restore_ret == 0) {
36153615
s->err.http_ret = 202; // OK
36163616
} else if (restore_ret == 1) {
3617-
s->err.http_ret = 409; // Conflict
3618-
dump_header(s, "x-amz-restore", "on-going-request=\"true\"");
3617+
restore_ret = -ERR_RESTORE_ALREADY_IN_PROGRESS;
3618+
set_req_state_err(s, restore_ret);
3619+
dump_header(s, "x-amz-restore", "ongoing-request=\"true\"");
36193620
} else if (restore_ret == 2) {
36203621
rgw::sal::Attrs attrs;
36213622
ceph::real_time expiration_date;

src/rgw/rgw_sal.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1719,7 +1719,7 @@ class PlacementTier {
17191719

17201720
/** Get the type of this tier */
17211721
virtual const std::string& get_tier_type() = 0;
1722-
/** Is the type of this tier cloud-s3/clous-s3-glacier */
1722+
/** Is the type of this tier cloud-s3/cloud-s3-glacier */
17231723
virtual bool is_tier_type_s3() = 0;
17241724
/** Get the storage class of this tier */
17251725
virtual const std::string& get_storage_class() = 0;

0 commit comments

Comments
 (0)