Skip to content

Commit 6302f17

Browse files
Paweł Andruszkiewiczrennox
authored andcommitted
BUG#35548572 Support OCI Object Storage Dedicated Endpoints in D&L utilities
Shell did not recognize the PAR URLs which use the dedicated endpoints: https://<namespace>.objectstorage.<region>.oci.customer-oci.com It treated them as generic HTTPS URLs, and failed the dump/load operations. This patch adds support for this format, keeping the backwards compatibility with the old format: https://objectstorage.<region>.oraclecloud.com Change-Id: I80d53926f79cf75fdc550d85f27bda2d34d3ddd5
1 parent aa4c60f commit 6302f17

File tree

12 files changed

+148
-158
lines changed

12 files changed

+148
-158
lines changed

modules/util/dump/dump_manifest.cc

Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include <chrono>
2727
#include <ctime>
2828
#include <stdexcept>
29+
#include <unordered_set>
2930
#include <utility>
3031

3132
#include "mysqlshdk/include/shellcore/scoped_contexts.h"
@@ -545,7 +546,7 @@ Dump_manifest_reader::Dump_manifest_reader(
545546
m_reader = std::make_shared<Manifest_reader>(
546547
std::make_unique<mysqlshdk::storage::backend::Http_object>(
547548
mysqlshdk::oci::anonymize_par(config->par().full_url()), true),
548-
m_config->par().object_prefix);
549+
m_config->par().object_prefix());
549550
}
550551
}
551552

@@ -556,35 +557,8 @@ std::string Dump_manifest_reader::join_path(const std::string &a,
556557

557558
std::unique_ptr<mysqlshdk::storage::IFile> Dump_manifest_reader::file(
558559
const std::string &name, const mysqlshdk::storage::File_options &) const {
559-
using mysqlshdk::oci::PAR_structure;
560-
using mysqlshdk::oci::PAR_type;
561-
// On read mode, the first file to be created is the handle for the manifest
562-
// file, if it has not been created then we skip the validation below for
563-
// additional files
564-
//
565-
// In READ mode, if a PAR is provided (i.e. a PAR to the progress file),
566-
// then the file is "created" and it is kept on a different registry (i.e.
567-
// not part of the manifest)
568-
PAR_structure data;
569-
570-
if (parse_par(name, &data) != PAR_type::NONE) {
571-
if (data.region == m_config->par().region &&
572-
data.domain == m_config->par().domain &&
573-
data.ns_name == m_config->par().ns_name &&
574-
data.bucket == m_config->par().bucket &&
575-
data.object_prefix == m_config->par().object_prefix) {
576-
m_created_objects.emplace(std::move(data.object_name),
577-
File_info{data.object_path(), 0});
578-
579-
return std::make_unique<mysqlshdk::storage::backend::Http_object>(
580-
mysqlshdk::oci::anonymize_par(name), true);
581-
} else {
582-
THROW_ERROR(SHERR_LOAD_MANIFEST_PAR_MISMATCH, name.c_str());
583-
}
584-
} else {
585-
return std::make_unique<Http_manifest_object>(
586-
m_reader, m_config, m_config->par().object_prefix + name);
587-
}
560+
return std::make_unique<Http_manifest_object>(
561+
m_reader, m_config, m_config->par().object_prefix() + name);
588562
}
589563

590564
std::unordered_set<File_info> Dump_manifest_reader::list_files(bool) const {
@@ -593,7 +567,7 @@ std::unordered_set<File_info> Dump_manifest_reader::list_files(bool) const {
593567
}
594568

595569
// File list must exclude the PAR portion
596-
const auto prefix_length = m_config->par().object_prefix.length();
570+
const auto prefix_length = m_config->par().object_prefix().length();
597571
std::unordered_set<IDirectory::File_info> files;
598572

599573
for (const auto &object : m_reader->list_objects()) {

modules/util/dump/dump_manifest.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <stdexcept>
2929
#include <string>
3030
#include <unordered_map>
31+
#include <unordered_set>
3132
#include <utility>
3233
#include <vector>
3334

@@ -213,7 +214,6 @@ class Dump_manifest_reader : public mysqlshdk::storage::IDirectory {
213214
private:
214215
std::shared_ptr<Manifest_reader> m_reader;
215216
Dump_manifest_read_config_ptr m_config;
216-
mutable std::unordered_map<std::string, File_info> m_created_objects;
217217
};
218218

219219
} // namespace dump

modules/util/load/load_dump_options.cc

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
#include <mysqld_error.h>
2727

2828
#include <algorithm>
29+
#include <regex>
30+
#include <utility>
2931

3032
#include "modules/mod_utils.h"
3133
#include "modules/util/common/dump/utils.h"
@@ -440,10 +442,13 @@ void Load_dump_options::on_unpacked_options() {
440442

441443
void Load_dump_options::on_log_options(const char *msg) const {
442444
std::string s = msg;
443-
const auto pos = s.find("\"progressFile\":\"https://objectstorage.");
444445

445-
if (std::string::npos != pos) {
446-
s = mysqlshdk::oci::hide_par_secret(s, pos);
446+
static const std::regex k_par_progress_file(
447+
R"("progressFile":"https://(?:[^\.]+\.)?objectstorage\.)");
448+
std::smatch match;
449+
450+
if (std::regex_search(s, match, k_par_progress_file)) {
451+
s = mysqlshdk::oci::hide_par_secret(s, match.position());
447452
}
448453

449454
log_info("Load options: %s", s.c_str());

mysqlshdk/libs/oci/oci_par.cc

Lines changed: 25 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,16 @@ namespace {
3939
const std::string k_at_manifest_json = "@.manifest.json";
4040

4141
const std::regex k_full_par_parser(
42-
R"(^https:\/\/objectstorage\.([^\.]+)\.([^\/]+)\/p\/(.+)\/n\/(.+)\/b\/(.*)\/o\/((.*)\/)?(.*)$)");
42+
R"(^(https:\/\/(?:[^\.]+\.)?objectstorage\.[^\/]+)\/p\/(.+)\/n\/(.+)\/b\/(.*)\/o\/((?:.*)\/)?(.*)$)");
4343

4444
namespace par_tokens {
4545

46-
const size_t REGION = 1;
47-
const size_t FULL_DOMAIN = 2;
48-
const size_t PARID = 3;
49-
const size_t NAMESPACE = 4;
50-
const size_t BUCKET = 5;
51-
const size_t PREFIX = 6;
52-
[[maybe_unused]] const size_t DIRNAME = 7;
53-
const size_t BASENAME = 8;
46+
const size_t ENDPOINT = 1;
47+
const size_t PAR_ID = 2;
48+
const size_t NAMESPACE = 3;
49+
const size_t BUCKET = 4;
50+
const size_t PREFIX = 5;
51+
const size_t BASENAME = 6;
5452

5553
} // namespace par_tokens
5654

@@ -127,26 +125,6 @@ std::string hide_par_secret(const std::string &par, std::size_t start_at) {
127125
return par.substr(0, p + 3) + "<secret>" + par.substr(n);
128126
}
129127

130-
std::string PAR_structure::full_url() const {
131-
return par_url() + db::uri::pctencode_path(object_prefix + object_name);
132-
}
133-
134-
std::string PAR_structure::par_url() const {
135-
return shcore::str_format("%s/p/%s/n/%s/b/%s/o/", endpoint().c_str(),
136-
par_id.c_str(), ns_name.c_str(), bucket.c_str());
137-
}
138-
139-
std::string PAR_structure::object_path() const {
140-
return shcore::str_format("/p/%s/n/%s/b/%s/o/%s%s", par_id.c_str(),
141-
ns_name.c_str(), bucket.c_str(),
142-
object_prefix.c_str(), object_name.c_str());
143-
}
144-
145-
std::string PAR_structure::endpoint() const {
146-
return shcore::str_format("https://objectstorage.%s.%s", region.c_str(),
147-
domain.c_str());
148-
}
149-
150128
PAR_type parse_par(const std::string &url, PAR_structure *data) {
151129
PAR_type ret_val = PAR_type::NONE;
152130
std::smatch results;
@@ -165,14 +143,24 @@ PAR_type parse_par(const std::string &url, PAR_structure *data) {
165143
}
166144

167145
if (data) {
168-
data->region = results[par_tokens::REGION];
169-
data->domain = results[par_tokens::FULL_DOMAIN];
170-
data->par_id = results[par_tokens::PARID];
171-
data->ns_name = results[par_tokens::NAMESPACE];
172-
data->bucket = results[par_tokens::BUCKET];
173-
data->object_prefix =
146+
data->m_endpoint = results[par_tokens::ENDPOINT];
147+
const auto par_id = results[par_tokens::PAR_ID].str();
148+
const auto ns_name = results[par_tokens::NAMESPACE].str();
149+
const auto bucket = results[par_tokens::BUCKET].str();
150+
data->m_object_prefix =
174151
shcore::pctdecode(results[par_tokens::PREFIX].str());
175-
data->object_name = std::move(object_name);
152+
data->m_object_name = std::move(object_name);
153+
154+
data->m_object_path = shcore::str_format(
155+
"/p/%s/n/%s/b/%s/o/%s%s", par_id.c_str(), ns_name.c_str(),
156+
bucket.c_str(), data->object_prefix().c_str(),
157+
data->object_name().c_str());
158+
data->m_par_url =
159+
shcore::str_format("%s/p/%s/n/%s/b/%s/o/", data->endpoint().c_str(),
160+
par_id.c_str(), ns_name.c_str(), bucket.c_str());
161+
data->m_full_url =
162+
data->par_url() +
163+
db::uri::pctencode_path(data->object_prefix() + data->object_name());
176164
}
177165
}
178166

@@ -189,7 +177,7 @@ std::string IPAR_config::describe_self() const {
189177
}
190178

191179
std::string IPAR_config::describe_url(const std::string &) const {
192-
return "prefix='" + m_par.object_prefix + "'";
180+
return "prefix='" + m_par.object_prefix() + "'";
193181
}
194182

195183
std::unique_ptr<storage::IFile> General_par_config::file(

mysqlshdk/libs/oci/oci_par.h

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,24 +61,38 @@ struct PAR {
6161
enum class PAR_type { MANIFEST, PREFIX, GENERAL, NONE };
6262

6363
struct PAR_structure {
64-
std::string region;
65-
std::string domain;
66-
std::string par_id;
67-
std::string ns_name;
68-
std::string bucket;
69-
std::string object_prefix;
70-
std::string object_name;
64+
inline PAR_type type() const noexcept { return m_type; }
65+
66+
inline const std::string &endpoint() const noexcept { return m_endpoint; }
67+
68+
inline const std::string &object_prefix() const noexcept {
69+
return m_object_prefix;
70+
}
71+
72+
inline const std::string &object_name() const noexcept {
73+
return m_object_name;
74+
}
7175

72-
std::string full_url() const;
73-
std::string par_url() const;
74-
std::string object_path() const;
75-
std::string endpoint() const;
76-
PAR_type type() const { return m_type; }
76+
inline const std::string &object_path() const noexcept {
77+
return m_object_path;
78+
}
79+
80+
inline const std::string &par_url() const noexcept { return m_par_url; }
81+
82+
inline const std::string &full_url() const noexcept { return m_full_url; }
7783

7884
private:
7985
friend PAR_type parse_par(const std::string &, PAR_structure *);
8086

8187
PAR_type m_type = PAR_type::NONE;
88+
89+
std::string m_endpoint;
90+
std::string m_object_prefix;
91+
std::string m_object_name;
92+
93+
std::string m_object_path;
94+
std::string m_par_url;
95+
std::string m_full_url;
8296
};
8397

8498
std::string to_string(PAR_access_type access_type);
@@ -112,7 +126,7 @@ class IPAR_config : public storage::Config {
112126
~IPAR_config() override {
113127
if (!m_temp_folder.empty()) {
114128
shcore::remove_directory(m_temp_folder);
115-
};
129+
}
116130
}
117131

118132
const PAR_structure &par() const { return m_par; }

mysqlshdk/libs/storage/backend/oci_par_directory.cc

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525

2626
#include <algorithm>
2727
#include <filesystem>
28+
#include <memory>
29+
#include <unordered_set>
30+
#include <utility>
2831

2932
#include "mysqlshdk/libs/db/uri_encoder.h"
3033
#include "mysqlshdk/libs/oci/oci_par.h"
@@ -161,8 +164,8 @@ std::string Oci_par_directory::get_list_url() const {
161164
// files in the current directory
162165
std::string url = "?fields=name,size&delimiter=/";
163166

164-
if (!m_config->par().object_prefix.empty()) {
165-
url += "&prefix=" + pctencode_query_value(m_config->par().object_prefix);
167+
if (!m_config->par().object_prefix().empty()) {
168+
url += "&prefix=" + pctencode_query_value(m_config->par().object_prefix());
166169
}
167170

168171
if (!m_next_start_with.empty()) {
@@ -179,7 +182,7 @@ std::unordered_set<IDirectory::File_info> Oci_par_directory::parse_file_list(
179182
const auto response = shcore::Value::parse(data.data(), data.size()).as_map();
180183
const auto objects = response->get_array("objects");
181184
m_next_start_with = response->get_string("nextStartWith", "");
182-
const auto prefix_length = m_config->par().object_prefix.length();
185+
const auto prefix_length = m_config->par().object_prefix().length();
183186

184187
list.reserve(objects->size());
185188

unittest/modules/util/dump/dump_manifest_t.cc

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2020, 2022, Oracle and/or its affiliates.
1+
/* Copyright (c) 2020, 2023, Oracle and/or its affiliates.
22
33
This program is free software; you can redistribute it and/or modify
44
it under the terms of the GNU General Public License, version 2.0,
@@ -295,34 +295,14 @@ TEST_F(Oci_os_tests, dump_manifest_read_mode) {
295295
EXPECT_STREQ(k_objects[Obj_index::SECOND].content.c_str(),
296296
second_file_data.c_str());
297297

298-
// Test the read manifest allows writing to file using Read/Write PAR
299-
auto rw_par = bucket.create_pre_authenticated_request(
298+
// any file which does not belong to the manifest results in an exception
299+
const auto rw_par = bucket.create_pre_authenticated_request(
300300
mysqlshdk::oci::PAR_access_type::OBJECT_READ_WRITE, time, "par-progress",
301301
"@.load.progress.json");
302302
const auto progress_file_uri =
303303
write_config->service_endpoint() + rw_par.access_uri;
304-
auto new_progress_file = read_manifest.file(progress_file_uri);
305-
306-
// Being a new file created with PAR it should NOT exist
307-
EXPECT_FALSE(new_progress_file->exists());
308-
EXPECT_THROW_LIKE(new_progress_file->file_size(), shcore::Exception,
309-
"Not Found");
310-
311-
new_progress_file->open(mysqlshdk::storage::Mode::WRITE);
312-
new_progress_file->write("MY PROGRESS DATA", 16);
313-
new_progress_file->close();
314-
315-
auto existing_progress_file = read_manifest.file(progress_file_uri);
316-
317-
// Being an existing file created with PAR it should exist
318-
EXPECT_TRUE(existing_progress_file->exists());
319-
320-
existing_progress_file->open(mysqlshdk::storage::Mode::READ);
321-
322-
read = existing_progress_file->read(&buffer, 16);
323-
std::string progress_data(buffer, read);
324-
existing_progress_file->close();
325-
EXPECT_STREQ("MY PROGRESS DATA", progress_data.c_str());
304+
EXPECT_THROW_LIKE(read_manifest.file(progress_file_uri), shcore::Exception,
305+
"Unknown object in manifest");
326306
}
327307

328308
} // namespace testing

unittest/scripts/auto/py_oci/scripts/oci_prefix_par_dump_norecord.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ def anonymize_par(par, full=True):
8585
"bucket": create_par(OS_NAMESPACE, OS_BUCKET_NAME, "AnyObjectReadWrite", "all-read-par", today_plus_days(1, RFC3339), None, "ListObjects"),
8686
}
8787

88+
# BUG#35548572 - PARs using dedicated endpoints
89+
for par_type, par in list(good_pars.items()):
90+
good_pars[par_type + "-converted"] = convert_par(par)
91+
8892
#@<> Testing PAR with conflicting options
8993
for par_type, par in good_pars.items():
9094
for name, callback in dump_util_cb.items():
@@ -150,6 +154,8 @@ def anonymize_par(par, full=True):
150154
#<> Testing dump attempt, with existing data in the target bucket/prefix
151155
for par_type, par in good_pars.items():
152156
# Performs an initial dump, to ensure there's data in the target location for the following tests
157+
print(f"--> Preparing util.dump_{name} test with existing files in target location using {par_type} PAR")
158+
prepare_empty_bucket(OS_BUCKET_NAME, OS_NAMESPACE, False)
153159
util.dump_tables("sakila", ["category"], par)
154160
for name, callback in dump_util_cb.items():
155161
print(f"--> Testing util.dump_{name} using {par_type} PAR with existing files in target location")
@@ -165,6 +171,7 @@ def anonymize_par(par, full=True):
165171
shell.connect(__sandbox_uri1)
166172
EXPECT_NO_THROWS(lambda: callback(par, None), f"Unexpected error calling util.dump_{name} with {par_type} PAR")
167173
session.close()
174+
print(f"--> Loading dump created by util.dump_{name} using {par_type} PAR")
168175
shell.connect(__sandbox_uri2)
169176
PREPARE_PAR_IS_SECRET_TEST()
170177
EXPECT_NO_THROWS(lambda: util.load_dump(par, {"progressFile":"progress.txt"}), f"Unexpected error loading dump using {par_type} PAR.")

unittest/scripts/auto/py_oci/scripts/oci_utils.inc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import oci
22
import datetime
33
import time
44
import queue
5+
import re
56
import threading
67

78
from oci.object_storage.models.create_preauthenticated_request_details import CreatePreauthenticatedRequestDetails
@@ -189,3 +190,14 @@ def EXPECT_PAR_IS_SECRET():
189190
check_text(testutil.fetch_captured_stdout(False), "stdout")
190191
check_text(testutil.fetch_captured_stderr(False), "stderr")
191192
check_text(testutil.cat_file(testutil.get_shell_log_path()), "Shell log")
193+
194+
def convert_par(par):
195+
m = re.match(r"^https:\/\/(?:([^\.]+)\.)?objectstorage\.([^\.]+)\.[^\/]+(\/p\/.+\/n\/(.+)\/b\/.*\/o\/(?:(?:.*)\/)?.*)$", par)
196+
if m is None:
197+
raise Exception(f"This is not a PAR: {par}")
198+
if m.group(1) is None:
199+
# old format, return an URL with a dedicated endpoint
200+
return f"https://{m.group(4)}.objectstorage.{m.group(2)}.oci.customer-oci.com{m.group(3)}"
201+
else:
202+
# PAR with a dedicated endpoint, return old format
203+
return f"https://objectstorage.{m.group(2)}.oraclecloud.com{m.group(3)}"

0 commit comments

Comments
 (0)