-
Notifications
You must be signed in to change notification settings - Fork 18
Datalake catalog auth token profile events #2010
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: antalya-26.3
Are you sure you want to change the base?
Changes from all commits
274c81b
6175986
fc04328
c2a7bab
01aaeb0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -73,6 +73,10 @@ namespace ProfileEvents | |
| extern const Event DataLakeRestCatalogGetCredentialsMicroseconds; | ||
| extern const Event DataLakeRestCatalogCredentialsVended; | ||
| extern const Event DataLakeRestCatalogCredentialsCacheHits; | ||
| extern const Event DataLakeRestCatalogAuthTokenCacheHits; | ||
| extern const Event DataLakeRestCatalogAuthTokenRefreshed; | ||
| extern const Event DataLakeRestCatalogAuthTokenRefreshedMicroseconds; | ||
| extern const Event DataLakeRestCatalogAuthTokenRefreshedOnUnauthorized; | ||
| extern const Event DataLakeRestCatalogCreateNamespace; | ||
| extern const Event DataLakeRestCatalogCreateNamespaceMicroseconds; | ||
| extern const Event DataLakeRestCatalogCreateTable; | ||
|
|
@@ -260,8 +264,12 @@ DB::HTTPHeaderEntries RestCatalog::getAuthHeaders( | |
| const String & /*method*/, | ||
| const Poco::URI & /*url*/, | ||
| const DB::HTTPHeaderEntries & /*extra_headers*/, | ||
| const String & /*body*/) const | ||
| const String & /*body*/, | ||
| bool * used_cached_oauth_token) const | ||
| { | ||
| if (used_cached_oauth_token) | ||
| *used_cached_oauth_token = false; | ||
|
|
||
| /// Option 1: user specified auth header manually. | ||
| /// Header has format: 'Authorization: <scheme> <token>'. | ||
| if (auth_header.has_value()) | ||
|
|
@@ -274,10 +282,14 @@ DB::HTTPHeaderEntries RestCatalog::getAuthHeaders( | |
| /// https://github.com/apache/iceberg/blob/3badfe0c1fcf0c0adfc7aa4a10f0b50365c48cf9/open-api/rest-catalog-open-api.yaml#L3498C5-L3498C34 | ||
| if (!client_id.empty()) | ||
| { | ||
| if (!access_token.has_value() || update_token) | ||
| if (!access_token.has_value() || update_token || access_token->isExpired()) | ||
| { | ||
| access_token = retrieveAccessToken(); | ||
| } | ||
| else if (used_cached_oauth_token) | ||
| { | ||
| *used_cached_oauth_token = true; | ||
| } | ||
|
|
||
| DB::HTTPHeaderEntries headers; | ||
| headers.emplace_back("Authorization", "Bearer " + access_token.value().token); | ||
|
|
@@ -313,6 +325,9 @@ OneLakeCatalog::OneLakeCatalog( | |
|
|
||
| AccessToken RestCatalog::retrieveAccessToken() const | ||
| { | ||
| ProfileEvents::increment(ProfileEvents::DataLakeRestCatalogAuthTokenRefreshed); | ||
| auto timer = DB::CurrentThread::getProfileEvents().timer(ProfileEvents::DataLakeRestCatalogAuthTokenRefreshedMicroseconds); | ||
|
|
||
| static constexpr auto oauth_tokens_endpoint = "oauth/tokens"; | ||
|
|
||
| /// TODO: | ||
|
|
@@ -429,21 +444,29 @@ BigLakeCatalog::BigLakeCatalog( | |
|
|
||
| DB::HTTPHeaderEntries BigLakeCatalog::getAuthHeaders( | ||
| bool update_token, | ||
| const String & /*method*/, | ||
| const Poco::URI & /*url*/, | ||
| const DB::HTTPHeaderEntries & /*extra_headers*/, | ||
| const String & /*body*/) const | ||
| const String & method, | ||
| const Poco::URI & url, | ||
| const DB::HTTPHeaderEntries & extra_headers, | ||
| const String & body, | ||
| bool * used_cached_oauth_token) const | ||
| { | ||
| /// Google Cloud OAuth2 for BigLake. | ||
| /// Uses GCP metadata service or Application Default Credentials to get access token. | ||
| /// Only use Google OAuth if explicitly configured (google_project_id or google_adc_client_id). | ||
| /// https://developers.google.com/identity/protocols/oauth2 | ||
| if (!google_project_id.empty() || !google_adc_client_id.empty()) | ||
| { | ||
| if (used_cached_oauth_token) | ||
| *used_cached_oauth_token = false; | ||
|
|
||
| if (!access_token.has_value() || update_token || access_token->isExpired()) | ||
| { | ||
| access_token = retrieveGoogleCloudAccessToken(); | ||
| } | ||
| else if (used_cached_oauth_token) | ||
| { | ||
| *used_cached_oauth_token = true; | ||
| } | ||
|
|
||
| DB::HTTPHeaderEntries headers; | ||
| headers.emplace_back("Authorization", "Bearer " + access_token->token); | ||
|
|
@@ -462,7 +485,7 @@ DB::HTTPHeaderEntries BigLakeCatalog::getAuthHeaders( | |
| return headers; | ||
| } | ||
|
|
||
| return RestCatalog::getAuthHeaders(update_token); | ||
| return RestCatalog::getAuthHeaders(update_token, method, url, extra_headers, body, used_cached_oauth_token); | ||
| } | ||
|
|
||
| AccessToken BigLakeCatalog::retrieveGoogleCloudAccessTokenFromRefreshToken() const | ||
|
|
@@ -484,6 +507,9 @@ AccessToken BigLakeCatalog::retrieveGoogleCloudAccessTokenFromRefreshToken() con | |
|
|
||
| AccessToken BigLakeCatalog::retrieveGoogleCloudAccessToken() const | ||
| { | ||
| ProfileEvents::increment(ProfileEvents::DataLakeRestCatalogAuthTokenRefreshed); | ||
| auto timer = DB::CurrentThread::getProfileEvents().timer(ProfileEvents::DataLakeRestCatalogAuthTokenRefreshedMicroseconds); | ||
|
|
||
| if (!google_adc_client_id.empty() && !google_adc_client_secret.empty() && !google_adc_refresh_token.empty()) | ||
| { | ||
| try | ||
|
|
@@ -585,9 +611,9 @@ DB::ReadWriteBufferFromHTTPPtr RestCatalog::createReadBuffer( | |
| if (!params.empty()) | ||
| url.setQueryParameters(params); | ||
|
|
||
| auto create_buffer = [&](bool update_token) | ||
| auto create_buffer = [&](bool update_token, bool & used_cached_oauth_token) | ||
| { | ||
| auto result_headers = getAuthHeaders(update_token, Poco::Net::HTTPRequest::HTTP_GET, url, headers, {}); | ||
| auto result_headers = getAuthHeaders(update_token, Poco::Net::HTTPRequest::HTTP_GET, url, headers, {}, &used_cached_oauth_token); | ||
| std::move(headers.begin(), headers.end(), std::back_inserter(result_headers)); | ||
|
|
||
| return DB::BuilderRWBufferFromHTTP(url) | ||
|
|
@@ -605,7 +631,11 @@ DB::ReadWriteBufferFromHTTPPtr RestCatalog::createReadBuffer( | |
|
|
||
| try | ||
| { | ||
| return create_buffer(false); | ||
| bool used_cached_oauth_token = false; | ||
| auto buf = create_buffer(false, used_cached_oauth_token); | ||
| if (used_cached_oauth_token) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not increment
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is not an event about caching, it is event about token requests. |
||
| ProfileEvents::increment(ProfileEvents::DataLakeRestCatalogAuthTokenCacheHits); | ||
| return buf; | ||
| } | ||
| catch (const DB::HTTPException & e) | ||
| { | ||
|
|
@@ -614,7 +644,9 @@ DB::ReadWriteBufferFromHTTPPtr RestCatalog::createReadBuffer( | |
| (status == Poco::Net::HTTPResponse::HTTPStatus::HTTP_UNAUTHORIZED | ||
| || status == Poco::Net::HTTPResponse::HTTPStatus::HTTP_FORBIDDEN)) | ||
| { | ||
| return create_buffer(true); | ||
| ProfileEvents::increment(ProfileEvents::DataLakeRestCatalogAuthTokenRefreshedOnUnauthorized); | ||
| bool used_cached_oauth_token_on_retry = false; | ||
| return create_buffer(true, used_cached_oauth_token_on_retry); | ||
| } | ||
| throw; | ||
| } | ||
|
|
@@ -1077,24 +1109,58 @@ void RestCatalog::sendRequest(const String & endpoint, Poco::JSON::Object::Ptr r | |
| DB::HTTPHeaderEntries extra_headers; | ||
| extra_headers.emplace_back("Content-Type", "application/json"); | ||
|
|
||
| DB::HTTPHeaderEntries headers = getAuthHeaders(/* update_token = */ true, method, url, extra_headers, body_str); | ||
| headers.emplace_back("Content-Type", "application/json"); | ||
| auto wb = DB::BuilderRWBufferFromHTTP(url) | ||
| .withConnectionGroup(DB::HTTPConnectionGroupType::HTTP) | ||
| .withMethod(method) | ||
| .withSettings(context->getReadSettings()) | ||
| .withTimeouts(DB::ConnectionTimeouts::getHTTPTimeouts(context->getSettingsRef(), context->getServerSettings())) | ||
| .withHostFilter(&context->getRemoteHostFilter()) | ||
| .withHeaders(headers) | ||
| .withOutCallback(out_stream_callback) | ||
| .withSkipNotFound(false) | ||
| .create(credentials); | ||
|
|
||
| String response_str; | ||
| if (!ignore_result) | ||
| readJSONObjectPossiblyInvalid(response_str, *wb); | ||
| else | ||
| wb->ignoreAll(); | ||
| auto create_buffer = [&](bool update_token, bool & used_cached_oauth_token) | ||
| { | ||
| DB::HTTPHeaderEntries headers = getAuthHeaders(update_token, method, url, extra_headers, body_str, &used_cached_oauth_token); | ||
| headers.emplace_back("Content-Type", "application/json"); | ||
| return DB::BuilderRWBufferFromHTTP(url) | ||
| .withConnectionGroup(DB::HTTPConnectionGroupType::HTTP) | ||
| .withMethod(method) | ||
| .withSettings(context->getReadSettings()) | ||
| .withTimeouts(DB::ConnectionTimeouts::getHTTPTimeouts(context->getSettingsRef(), context->getServerSettings())) | ||
| .withHostFilter(&context->getRemoteHostFilter()) | ||
| .withHeaders(headers) | ||
| .withOutCallback(out_stream_callback) | ||
| .withSkipNotFound(false) | ||
| .create(credentials); | ||
| }; | ||
|
|
||
| try | ||
| { | ||
| bool used_cached_oauth_token = false; | ||
| auto wb = create_buffer(false, used_cached_oauth_token); | ||
|
|
||
| String response_str; | ||
| if (!ignore_result) | ||
| readJSONObjectPossiblyInvalid(response_str, *wb); | ||
| else | ||
| wb->ignoreAll(); | ||
|
|
||
| if (used_cached_oauth_token) | ||
| ProfileEvents::increment(ProfileEvents::DataLakeRestCatalogAuthTokenCacheHits); | ||
| } | ||
| catch (const DB::HTTPException & e) | ||
| { | ||
| const auto status = e.getHTTPStatus(); | ||
| if (update_token_if_expired && | ||
| (status == Poco::Net::HTTPResponse::HTTPStatus::HTTP_UNAUTHORIZED | ||
| || status == Poco::Net::HTTPResponse::HTTPStatus::HTTP_FORBIDDEN)) | ||
| { | ||
| ProfileEvents::increment(ProfileEvents::DataLakeRestCatalogAuthTokenRefreshedOnUnauthorized); | ||
| bool used_cached_oauth_token_on_retry = false; | ||
| auto wb = create_buffer(true, used_cached_oauth_token_on_retry); | ||
|
|
||
| String response_str; | ||
| if (!ignore_result) | ||
| readJSONObjectPossiblyInvalid(response_str, *wb); | ||
| else | ||
| wb->ignoreAll(); | ||
| } | ||
| else | ||
| { | ||
| throw; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void RestCatalog::createNamespaceIfNotExists(const String & namespace_name, const String & location) const | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hm.. this is a bit confusing, tho I understand you are checking if the pointer is valid. Can't we pass a reference instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Method is overridden in
S3TablesCatalog, and thereused_cached_oauth_tokenis not used.