Skip to content

Commit 51d4973

Browse files
Merge pull request #1728 from satya-bodapati/trunk
PXB-3643 [trunk] : Improve xbcloud delete to support Hierarchical Namespaces
2 parents ee5be6f + 5f99db4 commit 51d4973

6 files changed

Lines changed: 190 additions & 19 deletions

File tree

storage/innobase/xtrabackup/src/xbcloud/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ MYSQL_ADD_EXECUTABLE(xbcloud
4545
xbcloud.cc
4646
../xbstream_read.cc
4747
http.cc
48+
object_store.cc
4849
azure.cc
4950
s3.cc
5051
s3_ec2.cc

storage/innobase/xtrabackup/src/xbcloud/azure.cc

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const std::string AZURE_DATE_HEADER = "x-ms-date";
4040
const std::string AZURE_VERSION_HEADER = "x-ms-version";
4141
const std::string AZURE_BLOB_TYPE_HEADER = "x-ms-blob-type";
4242
const std::string AZURE_STORAGE_CLASS_HEADER = "x-ms-access-tier";
43-
const std::string AZURE_VERSION_DATE = "2020-06-12";
43+
const std::string AZURE_VERSION_DATE = "2020-10-02";
4444
const std::string AZURE_DEVELOPMENT_HOST = "127.0.0.1:10000";
4545
const std::string AZURE_HOST = ".blob.core.windows.net";
4646

@@ -531,9 +531,12 @@ Azure_client::Azure_client(const Http_client *client,
531531
storage_account, access_key, development_storage, storage_class));
532532
}
533533

534-
bool Azure_client::list_objects_with_prefix(const std::string &container,
535-
const std::string &prefix,
536-
std::vector<std::string> &objects) {
534+
// Common helper function for listing objects - handles pagination and XML
535+
// parsing
536+
template <typename ProcessBlob>
537+
bool Azure_client::list_objects_common(const std::string &container,
538+
const std::string &prefix,
539+
ProcessBlob &&process_blob) {
537540
bool truncated = true;
538541
std::string next_marker;
539542

@@ -602,20 +605,67 @@ bool Azure_client::list_objects_with_prefix(const std::string &container,
602605

603606
auto node = blobs_node->first_node("Blob");
604607
while (node != nullptr) {
605-
auto name = node->first_node("Name");
606-
if (name == nullptr) {
607-
msg_ts(
608-
"%s: Failed to parse list container result. Cannot find object "
609-
"name.\n",
610-
my_progname);
611-
return false;
608+
if (!process_blob(node)) {
609+
return false; // Processing failed
612610
}
613-
objects.push_back(name->value());
614611
node = node->next_sibling("Blob");
615612
}
616613
}
617614

618615
return true;
619616
}
620617

618+
bool Azure_client::list_objects_with_prefix(const std::string &container,
619+
const std::string &prefix,
620+
std::vector<std::string> &objects) {
621+
return list_objects_common(
622+
container, prefix, [&objects](rapidxml::xml_node<> *node) {
623+
auto name = node->first_node("Name");
624+
if (name == nullptr) {
625+
msg_ts(
626+
"%s: Failed to parse list container result. Cannot find object "
627+
"name.\n",
628+
my_progname);
629+
return false;
630+
}
631+
objects.push_back(name->value());
632+
return true;
633+
});
634+
}
635+
636+
bool Azure_client::list_objects_files_and_dirs(const std::string &container,
637+
const std::string &prefix,
638+
std::vector<std::string> &files,
639+
std::vector<std::string> &dirs) {
640+
return list_objects_common(
641+
container, prefix, [&](rapidxml::xml_node<> *node) {
642+
auto name = node->first_node("Name");
643+
if (name == nullptr) {
644+
msg_ts(
645+
"%s: Failed to parse list container result. Cannot find object "
646+
"name.\n",
647+
my_progname);
648+
return false;
649+
}
650+
651+
// HNS returns directories explicitly via the ResourceType property.
652+
bool is_directory = false;
653+
auto properties_node = node->first_node("Properties");
654+
if (properties_node) {
655+
auto type_node = properties_node->first_node("ResourceType");
656+
if (type_node && type_node->value() &&
657+
strcmp(type_node->value(), "directory") == 0) {
658+
is_directory = true;
659+
}
660+
}
661+
662+
if (is_directory) {
663+
dirs.push_back(name->value());
664+
} else {
665+
files.push_back(name->value());
666+
}
667+
return true;
668+
});
669+
}
670+
621671
} // namespace xbcloud

storage/innobase/xtrabackup/src/xbcloud/azure.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,14 @@ class Azure_client {
107107
Event_handler *h, Azure_client::async_download_callback_t callback,
108108
CURLcode rc, const Http_connection *conn, ulong count);
109109

110+
// Common helper function for listing objects - handles pagination and XML
111+
// parsing ProcessBlob is a callable that takes (rapidxml::xml_node<>* node)
112+
// and returns bool Returns false to stop processing, true to continue
113+
template <typename ProcessBlob>
114+
bool list_objects_common(const std::string &container,
115+
const std::string &prefix,
116+
ProcessBlob &&process_blob);
117+
110118
public:
111119
Azure_client(const Http_client *client, const std::string &storage_account,
112120
const std::string &access_key, bool development_storage,
@@ -147,6 +155,22 @@ class Azure_client {
147155
const std::string &prefix,
148156
std::vector<std::string> &objects);
149157

158+
/**
159+
* List objects under a prefix and split them into files and directories.
160+
*
161+
* For HNS-enabled containers, directory entries are returned explicitly.
162+
*
163+
* @param container Container name.
164+
* @param prefix Prefix to list.
165+
* @param files Output list of file objects.
166+
* @param dirs Output list of directory objects.
167+
* @return true on success, false on error.
168+
*/
169+
bool list_objects_files_and_dirs(const std::string &container,
170+
const std::string &prefix,
171+
std::vector<std::string> &files,
172+
std::vector<std::string> &dirs);
173+
150174
ulong get_max_retries() { return max_retries; }
151175

152176
ulong get_max_backoff() { return max_backoff; }
@@ -235,6 +259,13 @@ class Azure_object_store : public Object_store {
235259
bool &success) override {
236260
return azure_client.download_object(container, name, success);
237261
}
262+
virtual bool list_objects_files_and_dirs(
263+
const std::string &container, const std::string &directory,
264+
std::vector<std::string> &files,
265+
std::vector<std::string> &dirs) override {
266+
return azure_client.list_objects_files_and_dirs(container, directory + "/",
267+
files, dirs);
268+
}
238269
};
239270
} // namespace xbcloud
240271

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/******************************************************
2+
Copyright (c) 2026 Percona LLC and/or its affiliates.
3+
4+
Object Store interface.
5+
6+
This program is free software; you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation; version 2 of the License.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU General Public License for more details.
14+
15+
You should have received a copy of the GNU General Public License
16+
along with this program; if not, write to the Free Software
17+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18+
19+
*******************************************************/
20+
21+
#include "xbcloud/object_store.h"
22+
23+
namespace xbcloud {
24+
25+
/**
26+
* Default implementation that lists objects and treats all as files.
27+
*
28+
* @param container Container/bucket name.
29+
* @param directory Directory prefix to list.
30+
* @param files Output list of file objects.
31+
* @param dirs Output list of directory objects (unused here).
32+
* @return true on success, false on error.
33+
*/
34+
bool Object_store::list_objects_files_and_dirs(const std::string &container,
35+
const std::string &directory,
36+
std::vector<std::string> &files,
37+
std::vector<std::string> &dirs) {
38+
return list_objects_in_directory(container, directory, files);
39+
}
40+
41+
} // namespace xbcloud

storage/innobase/xtrabackup/src/xbcloud/object_store.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,21 @@ class Object_store {
5656
virtual Http_buffer download_object(const std::string &container,
5757
const std::string &name,
5858
bool &success) = 0;
59+
/**
60+
* List objects under a directory prefix and split them into files and dirs.
61+
*
62+
* Default implementation treats all returned objects as files.
63+
*
64+
* @param container Container/bucket name.
65+
* @param directory Directory prefix to list.
66+
* @param files Output list of file objects.
67+
* @param dirs Output list of directory objects (may be empty).
68+
* @return true on success, false on error.
69+
*/
70+
virtual bool list_objects_files_and_dirs(const std::string &container,
71+
const std::string &directory,
72+
std::vector<std::string> &files,
73+
std::vector<std::string> &dirs);
5974
virtual ~Object_store() {}
6075
};
6176

storage/innobase/xtrabackup/src/xbcloud/xbcloud.cc

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,16 +1046,27 @@ bool chunk_name_to_file_name(const std::string &chunk_name,
10461046
return true;
10471047
}
10481048

1049+
/**
1050+
* Delete a backup directory from object storage.
1051+
*
1052+
* @param store Object store implementation.
1053+
* @param container Container/bucket name.
1054+
* @param backup_name Backup directory to delete.
1055+
* @return true on success, false on error.
1056+
*/
10491057
bool xbcloud_delete(Object_store *store, const std::string &container,
10501058
const std::string &backup_name) {
1051-
std::vector<std::string> object_list;
1059+
std::vector<std::string> files;
1060+
std::vector<std::string> dirs;
10521061

1053-
if (!store->list_objects_in_directory(container, backup_name, object_list)) {
1062+
// First pass lists files/dirs to validate existence and drive deletion.
1063+
if (!store->list_objects_files_and_dirs(container, backup_name, files,
1064+
dirs)) {
10541065
msg_ts("%s: Delete failed. Cannot list %s.\n", my_progname,
10551066
backup_name.c_str());
10561067
return false;
10571068
}
1058-
if (object_list.empty()) {
1069+
if (files.empty() && dirs.empty()) {
10591070
msg_ts("%s: error: backup named %s doesn't exists!\n", my_progname,
10601071
backup_name.c_str());
10611072
return false;
@@ -1069,7 +1080,7 @@ bool xbcloud_delete(Object_store *store, const std::string &container,
10691080
auto thread = h.run();
10701081

10711082
bool error = false;
1072-
for (const auto &obj : object_list) {
1083+
for (const auto &obj : files) {
10731084
std::string file_name;
10741085
my_off_t idx;
10751086
if (error) break;
@@ -1091,6 +1102,8 @@ bool xbcloud_delete(Object_store *store, const std::string &container,
10911102
}
10921103
},
10931104
std::placeholders::_1, obj, &error))) {
1105+
h.stop();
1106+
thread.join();
10941107
return false;
10951108
}
10961109
}
@@ -1100,11 +1113,31 @@ bool xbcloud_delete(Object_store *store, const std::string &container,
11001113

11011114
if (error) {
11021115
msg_ts("%s: Delete failed.\n", my_progname);
1103-
} else {
1104-
msg_ts("%s: Delete completed.\n", my_progname);
1116+
return false;
1117+
}
1118+
1119+
if (!dirs.empty()) {
1120+
std::sort(dirs.begin(), dirs.end(), std::greater<std::string>());
1121+
for (const auto &d : dirs) {
1122+
msg_ts("%s: Deleting directory %s.\n", my_progname, d.c_str());
1123+
if (!store->delete_object(container, d)) {
1124+
msg_ts("%s: Delete failed. Cannot delete directory %s.\n", my_progname,
1125+
d.c_str());
1126+
return false;
1127+
}
1128+
}
1129+
1130+
// Delete the root directory of the backup
1131+
msg_ts("%s: Deleting directory %s.\n", my_progname, backup_name.c_str());
1132+
if (!store->delete_object(container, backup_name)) {
1133+
msg_ts("%s: Warning: Failed to delete root directory %s.\n", my_progname,
1134+
backup_name.c_str());
1135+
}
11051136
}
11061137

1107-
return !error;
1138+
msg_ts("%s: Delete completed.\n", my_progname);
1139+
1140+
return true;
11081141
}
11091142

11101143
void download_func(download_thread_ctxt_t &cntx) {

0 commit comments

Comments
 (0)