|
42 | 42 | #include "iceberg/catalog/rest/rest_util.h" |
43 | 43 | #include "iceberg/catalog/rest/types.h" |
44 | 44 | #include "iceberg/json_serde_internal.h" |
| 45 | +#include "iceberg/logging/log_macros.h" |
45 | 46 | #include "iceberg/metrics/metrics_reporters.h" |
46 | 47 | #include "iceberg/partition_spec.h" |
47 | 48 | #include "iceberg/result.h" |
@@ -508,12 +509,56 @@ Result<std::shared_ptr<auth::AuthSession>> RestCatalog::TableAuthSession( |
508 | 509 | std::move(contextual_session)); |
509 | 510 | } |
510 | 511 |
|
| 512 | +StorageCredentialRefresher RestCatalog::MakeCredentialRefresher( |
| 513 | + const TableIdentifier& identifier, |
| 514 | + std::shared_ptr<auth::AuthSession> table_session) const { |
| 515 | + if (!supported_endpoints_.contains(Endpoint::TableCredentials())) { |
| 516 | + // Not an error, but it surfaces much later as credentials expiring. |
| 517 | + ICEBERG_LOG_DEBUG( |
| 518 | + "Catalog does not advertise {}; vended credentials for '{}' will not be " |
| 519 | + "refreshed", |
| 520 | + Endpoint::TableCredentials().ToString(), ToString(identifier)); |
| 521 | + return nullptr; |
| 522 | + } |
| 523 | + auto path = paths_->Credentials(identifier); |
| 524 | + if (!path.has_value()) { |
| 525 | + ICEBERG_LOG_WARN( |
| 526 | + "Cannot build the credentials path for '{}' ({}); its vended credentials " |
| 527 | + "will not be refreshed", |
| 528 | + ToString(identifier), path.error().message); |
| 529 | + return nullptr; |
| 530 | + } |
| 531 | + auto client = client_; |
| 532 | + auto credentials_path = std::move(path.value()); |
| 533 | + auto session = std::move(table_session); |
| 534 | + // The catalog's destructor closes the session, and a table's FileIO can |
| 535 | + // outlive the table keeping the catalog alive. No cycle: the catalog's own |
| 536 | + // FileIO never gets a refresher. |
| 537 | + auto catalog = shared_from_this(); |
| 538 | + return [catalog, client, credentials_path, |
| 539 | + session]() -> Result<std::vector<StorageCredential>> { |
| 540 | + ICEBERG_ASSIGN_OR_RAISE(const auto response, |
| 541 | + client->Get(credentials_path, /*params=*/{}, /*headers=*/{}, |
| 542 | + *TableErrorHandler::Instance(), *session)); |
| 543 | + ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); |
| 544 | + ICEBERG_ASSIGN_OR_RAISE(auto result, LoadCredentialsResponseFromJson(json)); |
| 545 | + return std::move(result.storage_credentials); |
| 546 | + }; |
| 547 | +} |
| 548 | + |
511 | 549 | Result<std::shared_ptr<FileIO>> RestCatalog::TableFileIO( |
512 | | - const SessionContext& /*context*/, |
| 550 | + const SessionContext& /*context*/, const TableIdentifier& identifier, |
513 | 551 | const std::unordered_map<std::string, std::string>& table_config, |
514 | | - const std::vector<StorageCredential>& storage_credentials) const { |
| 552 | + const std::vector<StorageCredential>& storage_credentials, |
| 553 | + std::shared_ptr<auth::AuthSession> table_session) const { |
515 | 554 | if (!table_config.empty() || !storage_credentials.empty()) { |
516 | | - return MakeTableFileIO(config_.configs(), table_config, storage_credentials); |
| 555 | + // Only vended credentials expire, so only they need a refresher. |
| 556 | + StorageCredentialRefresher refresher; |
| 557 | + if (!storage_credentials.empty()) { |
| 558 | + refresher = MakeCredentialRefresher(identifier, std::move(table_session)); |
| 559 | + } |
| 560 | + return MakeTableFileIO(config_.configs(), table_config, storage_credentials, |
| 561 | + std::move(refresher)); |
517 | 562 | } |
518 | 563 |
|
519 | 564 | return file_io_; |
@@ -772,11 +817,12 @@ Result<std::shared_ptr<Transaction>> RestCatalog::StageCreateTable( |
772 | 817 | /*stage_create=*/true, *contextual_session)); |
773 | 818 | auto table_config = std::move(result.config); |
774 | 819 | auto storage_credentials = std::move(result.storage_credentials); |
775 | | - ICEBERG_ASSIGN_OR_RAISE(auto table_io, |
776 | | - TableFileIO(context, table_config, storage_credentials)); |
| 820 | + // Before the FileIO: refreshing its credentials reuses the table session. |
777 | 821 | ICEBERG_ASSIGN_OR_RAISE( |
778 | 822 | auto table_session, |
779 | 823 | TableAuthSession(identifier, table_config, std::move(contextual_session))); |
| 824 | + ICEBERG_ASSIGN_OR_RAISE(auto table_io, TableFileIO(context, identifier, table_config, |
| 825 | + storage_credentials, table_session)); |
780 | 826 | ICEBERG_ASSIGN_OR_RAISE(auto reporter, MakeTableReporter(identifier, table_session)); |
781 | 827 | auto table_catalog = std::make_shared<TableScopedCatalog>( |
782 | 828 | shared_from_this(), context, identifier, table_config, std::move(table_session), |
@@ -890,11 +936,12 @@ Result<std::shared_ptr<Table>> RestCatalog::MakeTableFromLoadResult( |
890 | 936 | std::shared_ptr<auth::AuthSession> contextual_session) { |
891 | 937 | auto table_config = std::move(result.config); |
892 | 938 | auto storage_credentials = std::move(result.storage_credentials); |
893 | | - ICEBERG_ASSIGN_OR_RAISE(auto table_io, |
894 | | - TableFileIO(context, table_config, storage_credentials)); |
| 939 | + // Before the FileIO: refreshing its credentials reuses the table session. |
895 | 940 | ICEBERG_ASSIGN_OR_RAISE( |
896 | 941 | auto table_session, |
897 | 942 | TableAuthSession(identifier, table_config, std::move(contextual_session))); |
| 943 | + ICEBERG_ASSIGN_OR_RAISE(auto table_io, TableFileIO(context, identifier, table_config, |
| 944 | + storage_credentials, table_session)); |
898 | 945 | ICEBERG_ASSIGN_OR_RAISE(auto reporter, MakeTableReporter(identifier, table_session)); |
899 | 946 | auto table_catalog = std::make_shared<TableScopedCatalog>( |
900 | 947 | shared_from_this(), context, identifier, table_config, table_session, table_io); |
|
0 commit comments