-
Notifications
You must be signed in to change notification settings - Fork 456
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make pull_timeline work with auth enabled.
- Make safekeeper read SAFEKEEPER_AUTH_TOKEN env variable with JWT token to connect to other safekeepers. - Set it in neon_local when auth is enabled. - Create simple rust http client supporting it, and use it in pull_timeline implementation. - Enable auth in all pull_timeline tests. - Make sk http_client() by default generate safekeeper wide token, it makes easier enabling auth in all tests by default.
- Loading branch information
Showing
10 changed files
with
245 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
//! Safekeeper http client. | ||
//! | ||
//! Partially copied from pageserver client; some parts might be better to be | ||
//! united. | ||
//! | ||
//! It would be also good to move it out to separate crate, but this needs | ||
//! duplication of internal-but-reported structs like WalSenderState, ServerInfo | ||
//! etc. | ||
use reqwest::{IntoUrl, Method, StatusCode}; | ||
use utils::{ | ||
http::error::HttpErrorBody, | ||
id::{TenantId, TimelineId}, | ||
logging::SecretString, | ||
}; | ||
|
||
use super::routes::TimelineStatus; | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct Client { | ||
mgmt_api_endpoint: String, | ||
authorization_header: Option<SecretString>, | ||
client: reqwest::Client, | ||
} | ||
|
||
#[derive(thiserror::Error, Debug)] | ||
pub enum Error { | ||
/// Failed to receive body (reqwest error). | ||
#[error("receive body: {0}")] | ||
ReceiveBody(reqwest::Error), | ||
|
||
/// Status is not ok, but failed to parse body as `HttpErrorBody`. | ||
#[error("receive error body: {0}")] | ||
ReceiveErrorBody(String), | ||
|
||
/// Status is not ok; parsed error in body as `HttpErrorBody`. | ||
#[error("safekeeper API: {1}")] | ||
ApiError(StatusCode, String), | ||
} | ||
|
||
pub type Result<T> = std::result::Result<T, Error>; | ||
|
||
pub trait ResponseErrorMessageExt: Sized { | ||
fn error_from_body(self) -> impl std::future::Future<Output = Result<Self>> + Send; | ||
} | ||
|
||
/// If status is not ok, try to extract error message from the body. | ||
impl ResponseErrorMessageExt for reqwest::Response { | ||
async fn error_from_body(self) -> Result<Self> { | ||
let status = self.status(); | ||
if !(status.is_client_error() || status.is_server_error()) { | ||
return Ok(self); | ||
} | ||
|
||
let url = self.url().to_owned(); | ||
Err(match self.json::<HttpErrorBody>().await { | ||
Ok(HttpErrorBody { msg }) => Error::ApiError(status, msg), | ||
Err(_) => { | ||
Error::ReceiveErrorBody(format!("http error ({}) at {}.", status.as_u16(), url)) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
impl Client { | ||
pub fn new(mgmt_api_endpoint: String, jwt: Option<SecretString>) -> Self { | ||
Self::from_client(reqwest::Client::new(), mgmt_api_endpoint, jwt) | ||
} | ||
|
||
pub fn from_client( | ||
client: reqwest::Client, | ||
mgmt_api_endpoint: String, | ||
jwt: Option<SecretString>, | ||
) -> Self { | ||
Self { | ||
mgmt_api_endpoint, | ||
authorization_header: jwt | ||
.map(|jwt| SecretString::from(format!("Bearer {}", jwt.get_contents()))), | ||
client, | ||
} | ||
} | ||
|
||
pub async fn timeline_status( | ||
&self, | ||
tenant_id: TenantId, | ||
timeline_id: TimelineId, | ||
) -> Result<TimelineStatus> { | ||
let uri = format!( | ||
"{}/v1/tenant/{}/timeline/{}", | ||
self.mgmt_api_endpoint, tenant_id, timeline_id | ||
); | ||
let resp = self.get(&uri).await?; | ||
resp.json().await.map_err(Error::ReceiveBody) | ||
} | ||
|
||
pub async fn snapshot( | ||
&self, | ||
tenant_id: TenantId, | ||
timeline_id: TimelineId, | ||
) -> Result<reqwest::Response> { | ||
let uri = format!( | ||
"{}/v1/tenant/{}/timeline/{}/snapshot", | ||
self.mgmt_api_endpoint, tenant_id, timeline_id | ||
); | ||
self.get(&uri).await | ||
} | ||
|
||
async fn get<U: IntoUrl>(&self, uri: U) -> Result<reqwest::Response> { | ||
self.request(Method::GET, uri, ()).await | ||
} | ||
|
||
/// Send the request and check that the status code is good. | ||
async fn request<B: serde::Serialize, U: reqwest::IntoUrl>( | ||
&self, | ||
method: Method, | ||
uri: U, | ||
body: B, | ||
) -> Result<reqwest::Response> { | ||
let res = self.request_noerror(method, uri, body).await?; | ||
let response = res.error_from_body().await?; | ||
Ok(response) | ||
} | ||
|
||
/// Just send the request. | ||
async fn request_noerror<B: serde::Serialize, U: reqwest::IntoUrl>( | ||
&self, | ||
method: Method, | ||
uri: U, | ||
body: B, | ||
) -> Result<reqwest::Response> { | ||
let req = self.client.request(method, uri); | ||
let req = if let Some(value) = &self.authorization_header { | ||
req.header(reqwest::header::AUTHORIZATION, value.get_contents()) | ||
} else { | ||
req | ||
}; | ||
req.json(&body).send().await.map_err(Error::ReceiveBody) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
pub mod client; | ||
pub mod routes; | ||
pub use routes::make_router; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
4feb6ba
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.
3310 tests run: 3184 passed, 0 failed, 126 skipped (full report)
Flaky tests (1)
Postgres 16
test_subscriber_restart
: debugCode coverage* (full report)
functions
:32.4% (6831 of 21069 functions)
lines
:49.9% (53213 of 106597 lines)
* collected from Rust tests only
4feb6ba at 2024-06-18T15:22:18.964Z :recycle: