Run clippy on the whole project (v2) #73
46 changed files with 538 additions and 519 deletions
20
.drone.yml
20
.drone.yml
|
@ -36,8 +36,19 @@ steps:
|
||||||
branch:
|
branch:
|
||||||
- nonexistent_skip_this_step
|
- nonexistent_skip_this_step
|
||||||
|
|
||||||
|
- name: code quality
|
||||||
|
image: superboum/garage_builder_amd64:4
|
||||||
|
volumes:
|
||||||
|
- name: cargo_home
|
||||||
|
path: /drone/cargo
|
||||||
|
environment:
|
||||||
|
CARGO_HOME: /drone/cargo
|
||||||
|
commands:
|
||||||
|
- cargo fmt -- --check
|
||||||
|
- cargo clippy -- --deny warnings
|
||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
image: superboum/garage_builder_amd64:3
|
image: superboum/garage_builder_amd64:4
|
||||||
volumes:
|
volumes:
|
||||||
- name: cargo_home
|
- name: cargo_home
|
||||||
path: /drone/cargo
|
path: /drone/cargo
|
||||||
|
@ -45,11 +56,10 @@ steps:
|
||||||
CARGO_HOME: /drone/cargo
|
CARGO_HOME: /drone/cargo
|
||||||
commands:
|
commands:
|
||||||
- pwd
|
- pwd
|
||||||
- cargo fmt -- --check
|
|
||||||
- cargo build
|
- cargo build
|
||||||
|
|
||||||
- name: cargo-test
|
- name: cargo-test
|
||||||
image: superboum/garage_builder_amd64:3
|
image: superboum/garage_builder_amd64:4
|
||||||
volumes:
|
volumes:
|
||||||
- name: cargo_home
|
- name: cargo_home
|
||||||
path: /drone/cargo
|
path: /drone/cargo
|
||||||
|
@ -85,7 +95,7 @@ steps:
|
||||||
- nonexistent_skip_this_step
|
- nonexistent_skip_this_step
|
||||||
|
|
||||||
- name: smoke-test
|
- name: smoke-test
|
||||||
image: superboum/garage_builder_amd64:3
|
image: superboum/garage_builder_amd64:4
|
||||||
volumes:
|
volumes:
|
||||||
- name: cargo_home
|
- name: cargo_home
|
||||||
path: /drone/cargo
|
path: /drone/cargo
|
||||||
|
@ -129,6 +139,6 @@ steps:
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: signature
|
kind: signature
|
||||||
hmac: d584c2a15ede6d5702fbe27ae5ae2b2bf7a04461ae7aed2d53cbda83b7fd503e
|
hmac: e919f8a66d20ebfeeec56b291a8a0fdd59a482601da987fcf533d96d24768744
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
|
@ -3,5 +3,5 @@ RUN apt-get update && \
|
||||||
apt-get install --yes libsodium-dev awscli python-pip wget rclone openssl socat && \
|
apt-get install --yes libsodium-dev awscli python-pip wget rclone openssl socat && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
RUN wget https://dl.min.io/client/mc/release/linux-amd64/mc -O /usr/local/bin/mc && chmod +x /usr/local/bin/mc
|
RUN wget https://dl.min.io/client/mc/release/linux-amd64/mc -O /usr/local/bin/mc && chmod +x /usr/local/bin/mc
|
||||||
RUN rustup component add rustfmt
|
RUN rustup component add rustfmt clippy
|
||||||
RUN pip install s3cmd
|
RUN pip install s3cmd
|
||||||
|
|
|
@ -92,9 +92,9 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
|
||||||
_ => api_key.allow_write(&bucket),
|
_ => api_key.allow_write(&bucket),
|
||||||
};
|
};
|
||||||
if !allowed {
|
if !allowed {
|
||||||
return Err(Error::Forbidden(format!(
|
return Err(Error::Forbidden(
|
||||||
"Operation is not allowed for this key."
|
"Operation is not allowed for this key.".to_string(),
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut params = HashMap::new();
|
let mut params = HashMap::new();
|
||||||
|
@ -106,16 +106,16 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(key) = key {
|
if let Some(key) = key {
|
||||||
match req.method() {
|
match *req.method() {
|
||||||
&Method::HEAD => {
|
Method::HEAD => {
|
||||||
// HeadObject query
|
// HeadObject query
|
||||||
Ok(handle_head(garage, &req, &bucket, &key).await?)
|
Ok(handle_head(garage, &req, &bucket, &key).await?)
|
||||||
}
|
}
|
||||||
&Method::GET => {
|
Method::GET => {
|
||||||
// GetObject query
|
// GetObject query
|
||||||
Ok(handle_get(garage, &req, &bucket, &key).await?)
|
Ok(handle_get(garage, &req, &bucket, &key).await?)
|
||||||
}
|
}
|
||||||
&Method::PUT => {
|
Method::PUT => {
|
||||||
if params.contains_key(&"partnumber".to_string())
|
if params.contains_key(&"partnumber".to_string())
|
||||||
&& params.contains_key(&"uploadid".to_string())
|
&& params.contains_key(&"uploadid".to_string())
|
||||||
{
|
{
|
||||||
|
@ -154,7 +154,7 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
|
||||||
Ok(handle_put(garage, req, &bucket, &key, content_sha256).await?)
|
Ok(handle_put(garage, req, &bucket, &key, content_sha256).await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&Method::DELETE => {
|
Method::DELETE => {
|
||||||
if params.contains_key(&"uploadid".to_string()) {
|
if params.contains_key(&"uploadid".to_string()) {
|
||||||
// AbortMultipartUpload query
|
// AbortMultipartUpload query
|
||||||
let upload_id = params.get("uploadid").unwrap();
|
let upload_id = params.get("uploadid").unwrap();
|
||||||
|
@ -164,7 +164,7 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
|
||||||
Ok(handle_delete(garage, &bucket, &key).await?)
|
Ok(handle_delete(garage, &bucket, &key).await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&Method::POST => {
|
Method::POST => {
|
||||||
if params.contains_key(&"uploads".to_string()) {
|
if params.contains_key(&"uploads".to_string()) {
|
||||||
// CreateMultipartUpload call
|
// CreateMultipartUpload call
|
||||||
Ok(handle_create_multipart_upload(garage, &req, &bucket, &key).await?)
|
Ok(handle_create_multipart_upload(garage, &req, &bucket, &key).await?)
|
||||||
|
@ -181,16 +181,16 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
|
||||||
)
|
)
|
||||||
.await?)
|
.await?)
|
||||||
} else {
|
} else {
|
||||||
Err(Error::BadRequest(format!(
|
Err(Error::BadRequest(
|
||||||
"Not a CreateMultipartUpload call, what is it?"
|
"Not a CreateMultipartUpload call, what is it?".to_string(),
|
||||||
)))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Err(Error::BadRequest(format!("Invalid method"))),
|
_ => Err(Error::BadRequest("Invalid method".to_string())),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match req.method() {
|
match *req.method() {
|
||||||
&Method::PUT => {
|
Method::PUT => {
|
||||||
// CreateBucket
|
// CreateBucket
|
||||||
// If we're here, the bucket already exists, so just answer ok
|
// If we're here, the bucket already exists, so just answer ok
|
||||||
debug!(
|
debug!(
|
||||||
|
@ -205,19 +205,19 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
&Method::HEAD => {
|
Method::HEAD => {
|
||||||
// HeadBucket
|
// HeadBucket
|
||||||
let empty_body: Body = Body::from(vec![]);
|
let empty_body: Body = Body::from(vec![]);
|
||||||
let response = Response::builder().body(empty_body).unwrap();
|
let response = Response::builder().body(empty_body).unwrap();
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
&Method::DELETE => {
|
Method::DELETE => {
|
||||||
// DeleteBucket query
|
// DeleteBucket query
|
||||||
Err(Error::Forbidden(
|
Err(Error::Forbidden(
|
||||||
"Cannot delete buckets using S3 api, please talk to Garage directly".into(),
|
"Cannot delete buckets using S3 api, please talk to Garage directly".into(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
&Method::GET => {
|
Method::GET => {
|
||||||
if params.contains_key("location") {
|
if params.contains_key("location") {
|
||||||
// GetBucketLocation call
|
// GetBucketLocation call
|
||||||
Ok(handle_get_bucket_location(garage)?)
|
Ok(handle_get_bucket_location(garage)?)
|
||||||
|
@ -227,7 +227,7 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
|
||||||
Ok(handle_list(garage, &q).await?)
|
Ok(handle_list(garage, &q).await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&Method::POST => {
|
Method::POST => {
|
||||||
if params.contains_key(&"delete".to_string()) {
|
if params.contains_key(&"delete".to_string()) {
|
||||||
// DeleteObjects
|
// DeleteObjects
|
||||||
Ok(handle_delete_objects(garage, bucket, req, content_sha256).await?)
|
Ok(handle_delete_objects(garage, bucket, req, content_sha256).await?)
|
||||||
|
@ -237,10 +237,10 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
|
||||||
std::str::from_utf8(&hyper::body::to_bytes(req.into_body()).await?)
|
std::str::from_utf8(&hyper::body::to_bytes(req.into_body()).await?)
|
||||||
.unwrap_or("<invalid utf8>")
|
.unwrap_or("<invalid utf8>")
|
||||||
);
|
);
|
||||||
Err(Error::BadRequest(format!("Unsupported call")))
|
Err(Error::BadRequest("Unsupported call".to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Err(Error::BadRequest(format!("Invalid method"))),
|
_ => Err(Error::BadRequest("Invalid method".to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,7 +255,7 @@ fn parse_bucket_key(path: &str) -> Result<(&str, Option<&str>), Error> {
|
||||||
let (bucket, key) = match path.find('/') {
|
let (bucket, key) = match path.find('/') {
|
||||||
Some(i) => {
|
Some(i) => {
|
||||||
let key = &path[i + 1..];
|
let key = &path[i + 1..];
|
||||||
if key.len() > 0 {
|
if !key.is_empty() {
|
||||||
(&path[..i], Some(key))
|
(&path[..i], Some(key))
|
||||||
} else {
|
} else {
|
||||||
(&path[..i], None)
|
(&path[..i], None)
|
||||||
|
@ -263,8 +263,8 @@ fn parse_bucket_key(path: &str) -> Result<(&str, Option<&str>), Error> {
|
||||||
}
|
}
|
||||||
None => (path, None),
|
None => (path, None),
|
||||||
};
|
};
|
||||||
if bucket.len() == 0 {
|
if bucket.is_empty() {
|
||||||
return Err(Error::BadRequest(format!("No bucket specified")));
|
return Err(Error::BadRequest("No bucket specified".to_string()));
|
||||||
}
|
}
|
||||||
Ok((bucket, key))
|
Ok((bucket, key))
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub enum Error {
|
||||||
|
|
||||||
/// Error related to HTTP
|
/// Error related to HTTP
|
||||||
#[error(display = "Internal error (HTTP error): {}", _0)]
|
#[error(display = "Internal error (HTTP error): {}", _0)]
|
||||||
HTTP(#[error(source)] http::Error),
|
Http(#[error(source)] http::Error),
|
||||||
|
|
||||||
// Category: cannot process
|
// Category: cannot process
|
||||||
/// No proper api key was used, or the signature was invalid
|
/// No proper api key was used, or the signature was invalid
|
||||||
|
@ -39,11 +39,11 @@ pub enum Error {
|
||||||
// Category: bad request
|
// Category: bad request
|
||||||
/// The request contained an invalid UTF-8 sequence in its path or in other parameters
|
/// The request contained an invalid UTF-8 sequence in its path or in other parameters
|
||||||
#[error(display = "Invalid UTF-8: {}", _0)]
|
#[error(display = "Invalid UTF-8: {}", _0)]
|
||||||
InvalidUTF8Str(#[error(source)] std::str::Utf8Error),
|
InvalidUtf8Str(#[error(source)] std::str::Utf8Error),
|
||||||
|
|
||||||
/// The request used an invalid path
|
/// The request used an invalid path
|
||||||
#[error(display = "Invalid UTF-8: {}", _0)]
|
#[error(display = "Invalid UTF-8: {}", _0)]
|
||||||
InvalidUTF8String(#[error(source)] std::string::FromUtf8Error),
|
InvalidUtf8String(#[error(source)] std::string::FromUtf8Error),
|
||||||
|
|
||||||
/// Some base64 encoded data was badly encoded
|
/// Some base64 encoded data was badly encoded
|
||||||
#[error(display = "Invalid base64: {}", _0)]
|
#[error(display = "Invalid base64: {}", _0)]
|
||||||
|
@ -51,7 +51,7 @@ pub enum Error {
|
||||||
|
|
||||||
/// The client sent invalid XML data
|
/// The client sent invalid XML data
|
||||||
#[error(display = "Invalid XML: {}", _0)]
|
#[error(display = "Invalid XML: {}", _0)]
|
||||||
InvalidXML(String),
|
InvalidXml(String),
|
||||||
|
|
||||||
/// The client sent a header with invalid value
|
/// The client sent a header with invalid value
|
||||||
#[error(display = "Invalid header value: {}", _0)]
|
#[error(display = "Invalid header value: {}", _0)]
|
||||||
|
@ -68,13 +68,13 @@ pub enum Error {
|
||||||
|
|
||||||
impl From<roxmltree::Error> for Error {
|
impl From<roxmltree::Error> for Error {
|
||||||
fn from(err: roxmltree::Error) -> Self {
|
fn from(err: roxmltree::Error) -> Self {
|
||||||
Self::InvalidXML(format!("{}", err))
|
Self::InvalidXml(format!("{}", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<quick_xml::de::DeError> for Error {
|
impl From<quick_xml::de::DeError> for Error {
|
||||||
fn from(err: quick_xml::de::DeError) -> Self {
|
fn from(err: quick_xml::de::DeError) -> Self {
|
||||||
Self::InvalidXML(format!("{}", err))
|
Self::InvalidXml(format!("{}", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,8 +84,8 @@ impl Error {
|
||||||
match self {
|
match self {
|
||||||
Error::NotFound => StatusCode::NOT_FOUND,
|
Error::NotFound => StatusCode::NOT_FOUND,
|
||||||
Error::Forbidden(_) => StatusCode::FORBIDDEN,
|
Error::Forbidden(_) => StatusCode::FORBIDDEN,
|
||||||
Error::InternalError(GarageError::RPC(_)) => StatusCode::SERVICE_UNAVAILABLE,
|
Error::InternalError(GarageError::Rpc(_)) => StatusCode::SERVICE_UNAVAILABLE,
|
||||||
Error::InternalError(_) | Error::Hyper(_) | Error::HTTP(_) => {
|
Error::InternalError(_) | Error::Hyper(_) | Error::Http(_) => {
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
}
|
}
|
||||||
_ => StatusCode::BAD_REQUEST,
|
_ => StatusCode::BAD_REQUEST,
|
||||||
|
@ -97,8 +97,8 @@ impl Error {
|
||||||
Error::NotFound => "NoSuchKey",
|
Error::NotFound => "NoSuchKey",
|
||||||
Error::Forbidden(_) => "AccessDenied",
|
Error::Forbidden(_) => "AccessDenied",
|
||||||
Error::AuthorizationHeaderMalformed(_) => "AuthorizationHeaderMalformed",
|
Error::AuthorizationHeaderMalformed(_) => "AuthorizationHeaderMalformed",
|
||||||
Error::InternalError(GarageError::RPC(_)) => "ServiceUnavailable",
|
Error::InternalError(GarageError::Rpc(_)) => "ServiceUnavailable",
|
||||||
Error::InternalError(_) | Error::Hyper(_) | Error::HTTP(_) => "InternalError",
|
Error::InternalError(_) | Error::Hyper(_) | Error::Http(_) => "InternalError",
|
||||||
_ => "InvalidRequest",
|
_ => "InvalidRequest",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,7 +140,7 @@ impl<T> OkOrBadRequest for Option<T> {
|
||||||
fn ok_or_bad_request(self, reason: &'static str) -> Result<T, Error> {
|
fn ok_or_bad_request(self, reason: &'static str) -> Result<T, Error> {
|
||||||
match self {
|
match self {
|
||||||
Some(x) => Ok(x),
|
Some(x) => Ok(x),
|
||||||
None => Err(Error::BadRequest(format!("{}", reason))),
|
None => Err(Error::BadRequest(reason.to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,10 +172,9 @@ impl<T> OkOrInternalError for Option<T> {
|
||||||
fn ok_or_internal_error(self, reason: &'static str) -> Result<T, Error> {
|
fn ok_or_internal_error(self, reason: &'static str) -> Result<T, Error> {
|
||||||
match self {
|
match self {
|
||||||
Some(x) => Ok(x),
|
Some(x) => Ok(x),
|
||||||
None => Err(Error::InternalError(GarageError::Message(format!(
|
None => Err(Error::InternalError(GarageError::Message(
|
||||||
"{}",
|
reason.to_string(),
|
||||||
reason
|
))),
|
||||||
)))),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ struct DisplayName {
|
||||||
pub body: String,
|
pub body: String,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Serialize, PartialEq)]
|
#[derive(Debug, Serialize, PartialEq)]
|
||||||
struct ID {
|
struct Id {
|
||||||
#[serde(rename = "$value")]
|
#[serde(rename = "$value")]
|
||||||
pub body: String,
|
pub body: String,
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ struct Owner {
|
||||||
#[serde(rename = "DisplayName")]
|
#[serde(rename = "DisplayName")]
|
||||||
display_name: DisplayName,
|
display_name: DisplayName,
|
||||||
#[serde(rename = "ID")]
|
#[serde(rename = "ID")]
|
||||||
id: ID,
|
id: Id,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Serialize, PartialEq)]
|
#[derive(Debug, Serialize, PartialEq)]
|
||||||
struct BucketList {
|
struct BucketList {
|
||||||
|
@ -80,7 +80,7 @@ pub fn handle_list_buckets(api_key: &Key) -> Result<Response<Body>, Error> {
|
||||||
display_name: DisplayName {
|
display_name: DisplayName {
|
||||||
body: api_key.name.get().to_string(),
|
body: api_key.name.get().to_string(),
|
||||||
},
|
},
|
||||||
id: ID {
|
id: Id {
|
||||||
body: api_key.key_id.to_string(),
|
body: api_key.key_id.to_string(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -33,8 +33,7 @@ pub async fn handle_copy(
|
||||||
.versions()
|
.versions()
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.filter(|v| v.is_complete())
|
.find(|v| v.is_complete())
|
||||||
.next()
|
|
||||||
.ok_or(Error::NotFound)?;
|
.ok_or(Error::NotFound)?;
|
||||||
|
|
||||||
let source_last_state = match &source_last_v.state {
|
let source_last_state = match &source_last_v.state {
|
||||||
|
|
|
@ -17,17 +17,19 @@ async fn handle_delete_internal(
|
||||||
garage: &Garage,
|
garage: &Garage,
|
||||||
bucket: &str,
|
bucket: &str,
|
||||||
key: &str,
|
key: &str,
|
||||||
) -> Result<(UUID, UUID), Error> {
|
) -> Result<(Uuid, Uuid), Error> {
|
||||||
let object = garage
|
let object = garage
|
||||||
.object_table
|
.object_table
|
||||||
.get(&bucket.to_string(), &key.to_string())
|
.get(&bucket.to_string(), &key.to_string())
|
||||||
.await?
|
.await?
|
||||||
.ok_or(Error::NotFound)?; // No need to delete
|
.ok_or(Error::NotFound)?; // No need to delete
|
||||||
|
|
||||||
let interesting_versions = object.versions().iter().filter(|v| match v.state {
|
let interesting_versions = object.versions().iter().filter(|v| {
|
||||||
ObjectVersionState::Aborted => false,
|
!matches!(
|
||||||
ObjectVersionState::Complete(ObjectVersionData::DeleteMarker) => false,
|
v.state,
|
||||||
_ => true,
|
ObjectVersionState::Aborted
|
||||||
|
| ObjectVersionState::Complete(ObjectVersionData::DeleteMarker)
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut version_to_delete = None;
|
let mut version_to_delete = None;
|
||||||
|
|
|
@ -28,7 +28,7 @@ fn object_headers(
|
||||||
version_meta.headers.content_type.to_string(),
|
version_meta.headers.content_type.to_string(),
|
||||||
)
|
)
|
||||||
.header("Last-Modified", date_str)
|
.header("Last-Modified", date_str)
|
||||||
.header("Accept-Ranges", format!("bytes"));
|
.header("Accept-Ranges", "bytes".to_string());
|
||||||
|
|
||||||
if !version_meta.etag.is_empty() {
|
if !version_meta.etag.is_empty() {
|
||||||
resp = resp.header("ETag", format!("\"{}\"", version_meta.etag));
|
resp = resp.header("ETag", format!("\"{}\"", version_meta.etag));
|
||||||
|
@ -97,8 +97,7 @@ pub async fn handle_head(
|
||||||
.versions()
|
.versions()
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.filter(|v| v.is_data())
|
.find(|v| v.is_data())
|
||||||
.next()
|
|
||||||
.ok_or(Error::NotFound)?;
|
.ok_or(Error::NotFound)?;
|
||||||
|
|
||||||
let version_meta = match &version.state {
|
let version_meta = match &version.state {
|
||||||
|
@ -137,8 +136,7 @@ pub async fn handle_get(
|
||||||
.versions()
|
.versions()
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.filter(|v| v.is_complete())
|
.find(|v| v.is_complete())
|
||||||
.next()
|
|
||||||
.ok_or(Error::NotFound)?;
|
.ok_or(Error::NotFound)?;
|
||||||
|
|
||||||
let last_v_data = match &last_v.state {
|
let last_v_data = match &last_v.state {
|
||||||
|
@ -160,7 +158,9 @@ pub async fn handle_get(
|
||||||
let range_str = range.to_str()?;
|
let range_str = range.to_str()?;
|
||||||
let mut ranges = http_range::HttpRange::parse(range_str, last_v_meta.size)?;
|
let mut ranges = http_range::HttpRange::parse(range_str, last_v_meta.size)?;
|
||||||
if ranges.len() > 1 {
|
if ranges.len() > 1 {
|
||||||
return Err(Error::BadRequest(format!("Multiple ranges not supported")));
|
return Err(Error::BadRequest(
|
||||||
|
"Multiple ranges not supported".to_string(),
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
ranges.pop()
|
ranges.pop()
|
||||||
}
|
}
|
||||||
|
@ -236,7 +236,7 @@ async fn handle_get_range(
|
||||||
end: u64,
|
end: u64,
|
||||||
) -> Result<Response<Body>, Error> {
|
) -> Result<Response<Body>, Error> {
|
||||||
if end > version_meta.size {
|
if end > version_meta.size {
|
||||||
return Err(Error::BadRequest(format!("Range not included in file")));
|
return Err(Error::BadRequest("Range not included in file".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let resp_builder = object_headers(version, version_meta)
|
let resp_builder = object_headers(version, version_meta)
|
||||||
|
@ -282,7 +282,7 @@ async fn handle_get_range(
|
||||||
}
|
}
|
||||||
// Keep only blocks that have an intersection with the requested range
|
// Keep only blocks that have an intersection with the requested range
|
||||||
if true_offset < end && true_offset + b.size > begin {
|
if true_offset < end && true_offset + b.size > begin {
|
||||||
blocks.push((b.clone(), true_offset));
|
blocks.push((*b, true_offset));
|
||||||
}
|
}
|
||||||
true_offset += b.size;
|
true_offset += b.size;
|
||||||
}
|
}
|
||||||
|
@ -303,9 +303,9 @@ async fn handle_get_range(
|
||||||
} else {
|
} else {
|
||||||
end - true_offset
|
end - true_offset
|
||||||
};
|
};
|
||||||
Result::<Bytes, Error>::Ok(Bytes::from(
|
Result::<Bytes, Error>::Ok(
|
||||||
data.slice(start_in_block as usize..end_in_block as usize),
|
data.slice(start_in_block as usize..end_in_block as usize),
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.buffered(2);
|
.buffered(2);
|
||||||
|
|
|
@ -50,7 +50,7 @@ pub fn parse_list_objects_query(
|
||||||
.ok_or_bad_request("Invalid value for max-keys")
|
.ok_or_bad_request("Invalid value for max-keys")
|
||||||
})
|
})
|
||||||
.unwrap_or(Ok(1000))?,
|
.unwrap_or(Ok(1000))?,
|
||||||
prefix: params.get("prefix").cloned().unwrap_or(String::new()),
|
prefix: params.get("prefix").cloned().unwrap_or_default(),
|
||||||
marker: params.get("marker").cloned(),
|
marker: params.get("marker").cloned(),
|
||||||
continuation_token: params.get("continuation-token").cloned(),
|
continuation_token: params.get("continuation-token").cloned(),
|
||||||
start_after: params.get("start-after").cloned(),
|
start_after: params.get("start-after").cloned(),
|
||||||
|
@ -72,10 +72,13 @@ pub async fn handle_list(
|
||||||
if let Some(ct) = &query.continuation_token {
|
if let Some(ct) = &query.continuation_token {
|
||||||
String::from_utf8(base64::decode(ct.as_bytes())?)?
|
String::from_utf8(base64::decode(ct.as_bytes())?)?
|
||||||
} else {
|
} else {
|
||||||
query.start_after.clone().unwrap_or(query.prefix.clone())
|
query
|
||||||
|
.start_after
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| query.prefix.clone())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
query.marker.clone().unwrap_or(query.prefix.clone())
|
query.marker.clone().unwrap_or_else(|| query.prefix.clone())
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
|
@ -155,7 +158,7 @@ pub async fn handle_list(
|
||||||
truncated = None;
|
truncated = None;
|
||||||
break 'query_loop;
|
break 'query_loop;
|
||||||
}
|
}
|
||||||
if objects.len() > 0 {
|
if !objects.is_empty() {
|
||||||
next_chunk_start = objects[objects.len() - 1].key.clone();
|
next_chunk_start = objects[objects.len() - 1].key.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ pub async fn handle_put(
|
||||||
let body = req.into_body();
|
let body = req.into_body();
|
||||||
|
|
||||||
let mut chunker = BodyChunker::new(body, garage.config.block_size);
|
let mut chunker = BodyChunker::new(body, garage.config.block_size);
|
||||||
let first_block = chunker.next().await?.unwrap_or(vec![]);
|
let first_block = chunker.next().await?.unwrap_or_default();
|
||||||
|
|
||||||
// If body is small enough, store it directly in the object table
|
// If body is small enough, store it directly in the object table
|
||||||
// as "inline data". We can then return immediately.
|
// as "inline data". We can then return immediately.
|
||||||
|
@ -160,16 +160,18 @@ fn ensure_checksum_matches(
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
if let Some(expected_sha256) = content_sha256 {
|
if let Some(expected_sha256) = content_sha256 {
|
||||||
if expected_sha256 != data_sha256sum {
|
if expected_sha256 != data_sha256sum {
|
||||||
return Err(Error::BadRequest(format!(
|
return Err(Error::BadRequest(
|
||||||
"Unable to validate x-amz-content-sha256"
|
"Unable to validate x-amz-content-sha256".to_string(),
|
||||||
)));
|
));
|
||||||
} else {
|
} else {
|
||||||
trace!("Successfully validated x-amz-content-sha256");
|
trace!("Successfully validated x-amz-content-sha256");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(expected_md5) = content_md5 {
|
if let Some(expected_md5) = content_md5 {
|
||||||
if expected_md5.trim_matches('"') != base64::encode(data_md5sum) {
|
if expected_md5.trim_matches('"') != base64::encode(data_md5sum) {
|
||||||
return Err(Error::BadRequest(format!("Unable to validate content-md5")));
|
return Err(Error::BadRequest(
|
||||||
|
"Unable to validate content-md5".to_string(),
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
trace!("Successfully validated content-md5");
|
trace!("Successfully validated content-md5");
|
||||||
}
|
}
|
||||||
|
@ -291,7 +293,7 @@ impl BodyChunker {
|
||||||
self.read_all = true;
|
self.read_all = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.buf.len() == 0 {
|
if self.buf.is_empty() {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
} else if self.buf.len() <= self.block_size {
|
} else if self.buf.len() <= self.block_size {
|
||||||
let block = self.buf.drain(..).collect::<Vec<u8>>();
|
let block = self.buf.drain(..).collect::<Vec<u8>>();
|
||||||
|
@ -303,7 +305,7 @@ impl BodyChunker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn put_response(version_uuid: UUID, md5sum_hex: String) -> Response<Body> {
|
pub fn put_response(version_uuid: Uuid, md5sum_hex: String) -> Response<Body> {
|
||||||
Response::builder()
|
Response::builder()
|
||||||
.header("x-amz-version-id", hex::encode(version_uuid))
|
.header("x-amz-version-id", hex::encode(version_uuid))
|
||||||
.header("ETag", format!("\"{}\"", md5sum_hex))
|
.header("ETag", format!("\"{}\"", md5sum_hex))
|
||||||
|
@ -387,8 +389,8 @@ pub async fn handle_put_part(
|
||||||
futures::try_join!(garage.object_table.get(&bucket, &key), chunker.next(),)?;
|
futures::try_join!(garage.object_table.get(&bucket, &key), chunker.next(),)?;
|
||||||
|
|
||||||
// Check object is valid and multipart block can be accepted
|
// Check object is valid and multipart block can be accepted
|
||||||
let first_block = first_block.ok_or(Error::BadRequest(format!("Empty body")))?;
|
let first_block = first_block.ok_or_else(|| Error::BadRequest("Empty body".to_string()))?;
|
||||||
let object = object.ok_or(Error::BadRequest(format!("Object not found")))?;
|
let object = object.ok_or_else(|| Error::BadRequest("Object not found".to_string()))?;
|
||||||
|
|
||||||
if !object
|
if !object
|
||||||
.versions()
|
.versions()
|
||||||
|
@ -462,21 +464,21 @@ pub async fn handle_complete_multipart_upload(
|
||||||
garage.version_table.get(&version_uuid, &EmptyKey),
|
garage.version_table.get(&version_uuid, &EmptyKey),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let object = object.ok_or(Error::BadRequest(format!("Object not found")))?;
|
let object = object.ok_or_else(|| Error::BadRequest("Object not found".to_string()))?;
|
||||||
let mut object_version = object
|
let mut object_version = object
|
||||||
.versions()
|
.versions()
|
||||||
.iter()
|
.iter()
|
||||||
.find(|v| v.uuid == version_uuid && v.is_uploading())
|
.find(|v| v.uuid == version_uuid && v.is_uploading())
|
||||||
.cloned()
|
.cloned()
|
||||||
.ok_or(Error::BadRequest(format!("Version not found")))?;
|
.ok_or_else(|| Error::BadRequest("Version not found".to_string()))?;
|
||||||
|
|
||||||
let version = version.ok_or(Error::BadRequest(format!("Version not found")))?;
|
let version = version.ok_or_else(|| Error::BadRequest("Version not found".to_string()))?;
|
||||||
if version.blocks.len() == 0 {
|
if version.blocks.is_empty() {
|
||||||
return Err(Error::BadRequest(format!("No data was uploaded")));
|
return Err(Error::BadRequest("No data was uploaded".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let headers = match object_version.state {
|
let headers = match object_version.state {
|
||||||
ObjectVersionState::Uploading(headers) => headers.clone(),
|
ObjectVersionState::Uploading(headers) => headers,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -493,7 +495,9 @@ pub async fn handle_complete_multipart_upload(
|
||||||
.map(|x| (&x.part_number, &x.etag))
|
.map(|x| (&x.part_number, &x.etag))
|
||||||
.eq(parts);
|
.eq(parts);
|
||||||
if !same_parts {
|
if !same_parts {
|
||||||
return Err(Error::BadRequest(format!("We don't have the same parts")));
|
return Err(Error::BadRequest(
|
||||||
|
"We don't have the same parts".to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate etag of final object
|
// Calculate etag of final object
|
||||||
|
@ -557,7 +561,7 @@ pub async fn handle_abort_multipart_upload(
|
||||||
.object_table
|
.object_table
|
||||||
.get(&bucket.to_string(), &key.to_string())
|
.get(&bucket.to_string(), &key.to_string())
|
||||||
.await?;
|
.await?;
|
||||||
let object = object.ok_or(Error::BadRequest(format!("Object not found")))?;
|
let object = object.ok_or_else(|| Error::BadRequest("Object not found".to_string()))?;
|
||||||
|
|
||||||
let object_version = object
|
let object_version = object
|
||||||
.versions()
|
.versions()
|
||||||
|
@ -629,14 +633,14 @@ pub(crate) fn get_headers(req: &Request<Body>) -> Result<ObjectVersionHeaders, E
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_upload_id(id: &str) -> Result<UUID, Error> {
|
fn decode_upload_id(id: &str) -> Result<Uuid, Error> {
|
||||||
let id_bin = hex::decode(id).ok_or_bad_request("Invalid upload ID")?;
|
let id_bin = hex::decode(id).ok_or_bad_request("Invalid upload ID")?;
|
||||||
if id_bin.len() != 32 {
|
if id_bin.len() != 32 {
|
||||||
return None.ok_or_bad_request("Invalid upload ID");
|
return None.ok_or_bad_request("Invalid upload ID");
|
||||||
}
|
}
|
||||||
let mut uuid = [0u8; 32];
|
let mut uuid = [0u8; 32];
|
||||||
uuid.copy_from_slice(&id_bin[..]);
|
uuid.copy_from_slice(&id_bin[..]);
|
||||||
Ok(UUID::from(uuid))
|
Ok(Uuid::from(uuid))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -43,13 +43,12 @@ pub async fn check_signature(
|
||||||
let date = headers
|
let date = headers
|
||||||
.get("x-amz-date")
|
.get("x-amz-date")
|
||||||
.ok_or_bad_request("Missing X-Amz-Date field")?;
|
.ok_or_bad_request("Missing X-Amz-Date field")?;
|
||||||
let date: NaiveDateTime = NaiveDateTime::parse_from_str(date, LONG_DATETIME)
|
let date: NaiveDateTime =
|
||||||
.ok_or_bad_request("Invalid date")?
|
NaiveDateTime::parse_from_str(date, LONG_DATETIME).ok_or_bad_request("Invalid date")?;
|
||||||
.into();
|
|
||||||
let date: DateTime<Utc> = DateTime::from_utc(date, Utc);
|
let date: DateTime<Utc> = DateTime::from_utc(date, Utc);
|
||||||
|
|
||||||
if Utc::now() - date > Duration::hours(24) {
|
if Utc::now() - date > Duration::hours(24) {
|
||||||
return Err(Error::BadRequest(format!("Date is too old")));
|
return Err(Error::BadRequest("Date is too old".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let scope = format!(
|
let scope = format!(
|
||||||
|
@ -66,10 +65,7 @@ pub async fn check_signature(
|
||||||
.get(&EmptyKey, &authorization.key_id)
|
.get(&EmptyKey, &authorization.key_id)
|
||||||
.await?
|
.await?
|
||||||
.filter(|k| !k.deleted.get())
|
.filter(|k| !k.deleted.get())
|
||||||
.ok_or(Error::Forbidden(format!(
|
.ok_or_else(|| Error::Forbidden(format!("No such key: {}", authorization.key_id)))?;
|
||||||
"No such key: {}",
|
|
||||||
authorization.key_id
|
|
||||||
)))?;
|
|
||||||
|
|
||||||
let canonical_request = canonical_request(
|
let canonical_request = canonical_request(
|
||||||
request.method(),
|
request.method(),
|
||||||
|
@ -95,7 +91,7 @@ pub async fn check_signature(
|
||||||
trace!("Canonical request: ``{}``", canonical_request);
|
trace!("Canonical request: ``{}``", canonical_request);
|
||||||
trace!("String to sign: ``{}``", string_to_sign);
|
trace!("String to sign: ``{}``", string_to_sign);
|
||||||
trace!("Expected: {}, got: {}", signature, authorization.signature);
|
trace!("Expected: {}, got: {}", signature, authorization.signature);
|
||||||
return Err(Error::Forbidden(format!("Invalid signature")));
|
return Err(Error::Forbidden("Invalid signature".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let content_sha256 = if authorization.content_sha256 == "UNSIGNED-PAYLOAD" {
|
let content_sha256 = if authorization.content_sha256 == "UNSIGNED-PAYLOAD" {
|
||||||
|
@ -105,7 +101,7 @@ pub async fn check_signature(
|
||||||
.ok_or_bad_request("Invalid content sha256 hash")?;
|
.ok_or_bad_request("Invalid content sha256 hash")?;
|
||||||
Some(
|
Some(
|
||||||
Hash::try_from(&bytes[..])
|
Hash::try_from(&bytes[..])
|
||||||
.ok_or(Error::BadRequest(format!("Invalid content sha256 hash")))?,
|
.ok_or_else(|| Error::BadRequest("Invalid content sha256 hash".to_string()))?,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -173,9 +169,9 @@ fn parse_query_authorization(headers: &HashMap<String, String>) -> Result<Author
|
||||||
.get("x-amz-algorithm")
|
.get("x-amz-algorithm")
|
||||||
.ok_or_bad_request("X-Amz-Algorithm not found in query parameters")?;
|
.ok_or_bad_request("X-Amz-Algorithm not found in query parameters")?;
|
||||||
if algo != "AWS4-HMAC-SHA256" {
|
if algo != "AWS4-HMAC-SHA256" {
|
||||||
return Err(Error::BadRequest(format!(
|
return Err(Error::BadRequest(
|
||||||
"Unsupported authorization method"
|
"Unsupported authorization method".to_string(),
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let cred = headers
|
let cred = headers
|
||||||
|
@ -293,9 +289,9 @@ pub fn verify_signed_content(content_sha256: Option<Hash>, body: &[u8]) -> Resul
|
||||||
let expected_sha256 =
|
let expected_sha256 =
|
||||||
content_sha256.ok_or_bad_request("Request content hash not signed, aborting.")?;
|
content_sha256.ok_or_bad_request("Request content hash not signed, aborting.")?;
|
||||||
if expected_sha256 != sha256sum(body) {
|
if expected_sha256 != sha256sum(body) {
|
||||||
return Err(Error::BadRequest(format!(
|
return Err(Error::BadRequest(
|
||||||
"Request content hash does not match signed hash"
|
"Request content hash does not match signed hash".to_string(),
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use garage_util::error::Error;
|
use garage_util::error::Error;
|
||||||
|
|
||||||
use garage_table::crdt::CRDT;
|
use garage_table::crdt::Crdt;
|
||||||
use garage_table::replication::*;
|
use garage_table::replication::*;
|
||||||
use garage_table::*;
|
use garage_table::*;
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ pub const ADMIN_RPC_TIMEOUT: Duration = Duration::from_secs(30);
|
||||||
pub const ADMIN_RPC_PATH: &str = "_admin";
|
pub const ADMIN_RPC_PATH: &str = "_admin";
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub enum AdminRPC {
|
pub enum AdminRpc {
|
||||||
BucketOperation(BucketOperation),
|
BucketOperation(BucketOperation),
|
||||||
KeyOperation(KeyOperation),
|
KeyOperation(KeyOperation),
|
||||||
LaunchRepair(RepairOpt),
|
LaunchRepair(RepairOpt),
|
||||||
|
@ -39,35 +39,35 @@ pub enum AdminRPC {
|
||||||
KeyInfo(Key),
|
KeyInfo(Key),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RpcMessage for AdminRPC {}
|
impl RpcMessage for AdminRpc {}
|
||||||
|
|
||||||
pub struct AdminRpcHandler {
|
pub struct AdminRpcHandler {
|
||||||
garage: Arc<Garage>,
|
garage: Arc<Garage>,
|
||||||
rpc_client: Arc<RpcClient<AdminRPC>>,
|
rpc_client: Arc<RpcClient<AdminRpc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AdminRpcHandler {
|
impl AdminRpcHandler {
|
||||||
pub fn new(garage: Arc<Garage>) -> Arc<Self> {
|
pub fn new(garage: Arc<Garage>) -> Arc<Self> {
|
||||||
let rpc_client = garage.system.clone().rpc_client::<AdminRPC>(ADMIN_RPC_PATH);
|
let rpc_client = garage.system.clone().rpc_client::<AdminRpc>(ADMIN_RPC_PATH);
|
||||||
Arc::new(Self { garage, rpc_client })
|
Arc::new(Self { garage, rpc_client })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_handler(self: Arc<Self>, rpc_server: &mut RpcServer) {
|
pub fn register_handler(self: Arc<Self>, rpc_server: &mut RpcServer) {
|
||||||
rpc_server.add_handler::<AdminRPC, _, _>(ADMIN_RPC_PATH.to_string(), move |msg, _addr| {
|
rpc_server.add_handler::<AdminRpc, _, _>(ADMIN_RPC_PATH.to_string(), move |msg, _addr| {
|
||||||
let self2 = self.clone();
|
let self2 = self.clone();
|
||||||
async move {
|
async move {
|
||||||
match msg {
|
match msg {
|
||||||
AdminRPC::BucketOperation(bo) => self2.handle_bucket_cmd(bo).await,
|
AdminRpc::BucketOperation(bo) => self2.handle_bucket_cmd(bo).await,
|
||||||
AdminRPC::KeyOperation(ko) => self2.handle_key_cmd(ko).await,
|
AdminRpc::KeyOperation(ko) => self2.handle_key_cmd(ko).await,
|
||||||
AdminRPC::LaunchRepair(opt) => self2.handle_launch_repair(opt).await,
|
AdminRpc::LaunchRepair(opt) => self2.handle_launch_repair(opt).await,
|
||||||
AdminRPC::Stats(opt) => self2.handle_stats(opt).await,
|
AdminRpc::Stats(opt) => self2.handle_stats(opt).await,
|
||||||
_ => Err(Error::BadRPC(format!("Invalid RPC"))),
|
_ => Err(Error::BadRpc("Invalid RPC".to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_bucket_cmd(&self, cmd: BucketOperation) -> Result<AdminRPC, Error> {
|
async fn handle_bucket_cmd(&self, cmd: BucketOperation) -> Result<AdminRpc, Error> {
|
||||||
match cmd {
|
match cmd {
|
||||||
BucketOperation::List => {
|
BucketOperation::List => {
|
||||||
let bucket_names = self
|
let bucket_names = self
|
||||||
|
@ -78,17 +78,17 @@ impl AdminRpcHandler {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|b| b.name.to_string())
|
.map(|b| b.name.to_string())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
Ok(AdminRPC::BucketList(bucket_names))
|
Ok(AdminRpc::BucketList(bucket_names))
|
||||||
}
|
}
|
||||||
BucketOperation::Info(query) => {
|
BucketOperation::Info(query) => {
|
||||||
let bucket = self.get_existing_bucket(&query.name).await?;
|
let bucket = self.get_existing_bucket(&query.name).await?;
|
||||||
Ok(AdminRPC::BucketInfo(bucket))
|
Ok(AdminRpc::BucketInfo(bucket))
|
||||||
}
|
}
|
||||||
BucketOperation::Create(query) => {
|
BucketOperation::Create(query) => {
|
||||||
let bucket = match self.garage.bucket_table.get(&EmptyKey, &query.name).await? {
|
let bucket = match self.garage.bucket_table.get(&EmptyKey, &query.name).await? {
|
||||||
Some(mut bucket) => {
|
Some(mut bucket) => {
|
||||||
if !bucket.is_deleted() {
|
if !bucket.is_deleted() {
|
||||||
return Err(Error::BadRPC(format!(
|
return Err(Error::BadRpc(format!(
|
||||||
"Bucket {} already exists",
|
"Bucket {} already exists",
|
||||||
query.name
|
query.name
|
||||||
)));
|
)));
|
||||||
|
@ -101,7 +101,7 @@ impl AdminRpcHandler {
|
||||||
None => Bucket::new(query.name.clone()),
|
None => Bucket::new(query.name.clone()),
|
||||||
};
|
};
|
||||||
self.garage.bucket_table.insert(&bucket).await?;
|
self.garage.bucket_table.insert(&bucket).await?;
|
||||||
Ok(AdminRPC::Ok(format!("Bucket {} was created.", query.name)))
|
Ok(AdminRpc::Ok(format!("Bucket {} was created.", query.name)))
|
||||||
}
|
}
|
||||||
BucketOperation::Delete(query) => {
|
BucketOperation::Delete(query) => {
|
||||||
let mut bucket = self.get_existing_bucket(&query.name).await?;
|
let mut bucket = self.get_existing_bucket(&query.name).await?;
|
||||||
|
@ -111,12 +111,12 @@ impl AdminRpcHandler {
|
||||||
.get_range(&query.name, None, Some(DeletedFilter::NotDeleted), 10)
|
.get_range(&query.name, None, Some(DeletedFilter::NotDeleted), 10)
|
||||||
.await?;
|
.await?;
|
||||||
if !objects.is_empty() {
|
if !objects.is_empty() {
|
||||||
return Err(Error::BadRPC(format!("Bucket {} is not empty", query.name)));
|
return Err(Error::BadRpc(format!("Bucket {} is not empty", query.name)));
|
||||||
}
|
}
|
||||||
if !query.yes {
|
if !query.yes {
|
||||||
return Err(Error::BadRPC(format!(
|
return Err(Error::BadRpc(
|
||||||
"Add --yes flag to really perform this operation"
|
"Add --yes flag to really perform this operation".to_string(),
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
// --- done checking, now commit ---
|
// --- done checking, now commit ---
|
||||||
for (key_id, _, _) in bucket.authorized_keys() {
|
for (key_id, _, _) in bucket.authorized_keys() {
|
||||||
|
@ -131,7 +131,7 @@ impl AdminRpcHandler {
|
||||||
}
|
}
|
||||||
bucket.state.update(BucketState::Deleted);
|
bucket.state.update(BucketState::Deleted);
|
||||||
self.garage.bucket_table.insert(&bucket).await?;
|
self.garage.bucket_table.insert(&bucket).await?;
|
||||||
Ok(AdminRPC::Ok(format!("Bucket {} was deleted.", query.name)))
|
Ok(AdminRpc::Ok(format!("Bucket {} was deleted.", query.name)))
|
||||||
}
|
}
|
||||||
BucketOperation::Allow(query) => {
|
BucketOperation::Allow(query) => {
|
||||||
let key = self.get_existing_key(&query.key_pattern).await?;
|
let key = self.get_existing_key(&query.key_pattern).await?;
|
||||||
|
@ -142,7 +142,7 @@ impl AdminRpcHandler {
|
||||||
.await?;
|
.await?;
|
||||||
self.update_bucket_key(bucket, &key.key_id, allow_read, allow_write)
|
self.update_bucket_key(bucket, &key.key_id, allow_read, allow_write)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(AdminRPC::Ok(format!(
|
Ok(AdminRpc::Ok(format!(
|
||||||
"New permissions for {} on {}: read {}, write {}.",
|
"New permissions for {} on {}: read {}, write {}.",
|
||||||
&key.key_id, &query.bucket, allow_read, allow_write
|
&key.key_id, &query.bucket, allow_read, allow_write
|
||||||
)))
|
)))
|
||||||
|
@ -156,7 +156,7 @@ impl AdminRpcHandler {
|
||||||
.await?;
|
.await?;
|
||||||
self.update_bucket_key(bucket, &key.key_id, allow_read, allow_write)
|
self.update_bucket_key(bucket, &key.key_id, allow_read, allow_write)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(AdminRPC::Ok(format!(
|
Ok(AdminRpc::Ok(format!(
|
||||||
"New permissions for {} on {}: read {}, write {}.",
|
"New permissions for {} on {}: read {}, write {}.",
|
||||||
&key.key_id, &query.bucket, allow_read, allow_write
|
&key.key_id, &query.bucket, allow_read, allow_write
|
||||||
)))
|
)))
|
||||||
|
@ -165,9 +165,9 @@ impl AdminRpcHandler {
|
||||||
let mut bucket = self.get_existing_bucket(&query.bucket).await?;
|
let mut bucket = self.get_existing_bucket(&query.bucket).await?;
|
||||||
|
|
||||||
if !(query.allow ^ query.deny) {
|
if !(query.allow ^ query.deny) {
|
||||||
return Err(Error::Message(format!(
|
return Err(Error::Message(
|
||||||
"You must specify exactly one flag, either --allow or --deny"
|
"You must specify exactly one flag, either --allow or --deny".to_string(),
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let BucketState::Present(state) = bucket.state.get_mut() {
|
if let BucketState::Present(state) = bucket.state.get_mut() {
|
||||||
|
@ -179,7 +179,7 @@ impl AdminRpcHandler {
|
||||||
format!("Website access denied for {}", &query.bucket)
|
format!("Website access denied for {}", &query.bucket)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(AdminRPC::Ok(msg.to_string()))
|
Ok(AdminRpc::Ok(msg))
|
||||||
} else {
|
} else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
}
|
}
|
||||||
|
@ -187,7 +187,7 @@ impl AdminRpcHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_key_cmd(&self, cmd: KeyOperation) -> Result<AdminRPC, Error> {
|
async fn handle_key_cmd(&self, cmd: KeyOperation) -> Result<AdminRpc, Error> {
|
||||||
match cmd {
|
match cmd {
|
||||||
KeyOperation::List => {
|
KeyOperation::List => {
|
||||||
let key_ids = self
|
let key_ids = self
|
||||||
|
@ -203,29 +203,29 @@ impl AdminRpcHandler {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|k| (k.key_id.to_string(), k.name.get().clone()))
|
.map(|k| (k.key_id.to_string(), k.name.get().clone()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
Ok(AdminRPC::KeyList(key_ids))
|
Ok(AdminRpc::KeyList(key_ids))
|
||||||
}
|
}
|
||||||
KeyOperation::Info(query) => {
|
KeyOperation::Info(query) => {
|
||||||
let key = self.get_existing_key(&query.key_pattern).await?;
|
let key = self.get_existing_key(&query.key_pattern).await?;
|
||||||
Ok(AdminRPC::KeyInfo(key))
|
Ok(AdminRpc::KeyInfo(key))
|
||||||
}
|
}
|
||||||
KeyOperation::New(query) => {
|
KeyOperation::New(query) => {
|
||||||
let key = Key::new(query.name);
|
let key = Key::new(query.name);
|
||||||
self.garage.key_table.insert(&key).await?;
|
self.garage.key_table.insert(&key).await?;
|
||||||
Ok(AdminRPC::KeyInfo(key))
|
Ok(AdminRpc::KeyInfo(key))
|
||||||
}
|
}
|
||||||
KeyOperation::Rename(query) => {
|
KeyOperation::Rename(query) => {
|
||||||
let mut key = self.get_existing_key(&query.key_pattern).await?;
|
let mut key = self.get_existing_key(&query.key_pattern).await?;
|
||||||
key.name.update(query.new_name);
|
key.name.update(query.new_name);
|
||||||
self.garage.key_table.insert(&key).await?;
|
self.garage.key_table.insert(&key).await?;
|
||||||
Ok(AdminRPC::KeyInfo(key))
|
Ok(AdminRpc::KeyInfo(key))
|
||||||
}
|
}
|
||||||
KeyOperation::Delete(query) => {
|
KeyOperation::Delete(query) => {
|
||||||
let key = self.get_existing_key(&query.key_pattern).await?;
|
let key = self.get_existing_key(&query.key_pattern).await?;
|
||||||
if !query.yes {
|
if !query.yes {
|
||||||
return Err(Error::BadRPC(format!(
|
return Err(Error::BadRpc(
|
||||||
"Add --yes flag to really perform this operation"
|
"Add --yes flag to really perform this operation".to_string(),
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
// --- done checking, now commit ---
|
// --- done checking, now commit ---
|
||||||
for (ab_name, _, _) in key.authorized_buckets.items().iter() {
|
for (ab_name, _, _) in key.authorized_buckets.items().iter() {
|
||||||
|
@ -240,7 +240,7 @@ impl AdminRpcHandler {
|
||||||
}
|
}
|
||||||
let del_key = Key::delete(key.key_id.to_string());
|
let del_key = Key::delete(key.key_id.to_string());
|
||||||
self.garage.key_table.insert(&del_key).await?;
|
self.garage.key_table.insert(&del_key).await?;
|
||||||
Ok(AdminRPC::Ok(format!(
|
Ok(AdminRpc::Ok(format!(
|
||||||
"Key {} was deleted successfully.",
|
"Key {} was deleted successfully.",
|
||||||
key.key_id
|
key.key_id
|
||||||
)))
|
)))
|
||||||
|
@ -252,11 +252,12 @@ impl AdminRpcHandler {
|
||||||
}
|
}
|
||||||
let imported_key = Key::import(&query.key_id, &query.secret_key, &query.name);
|
let imported_key = Key::import(&query.key_id, &query.secret_key, &query.name);
|
||||||
self.garage.key_table.insert(&imported_key).await?;
|
self.garage.key_table.insert(&imported_key).await?;
|
||||||
Ok(AdminRPC::KeyInfo(imported_key))
|
Ok(AdminRpc::KeyInfo(imported_key))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::ptr_arg)]
|
||||||
async fn get_existing_bucket(&self, bucket: &String) -> Result<Bucket, Error> {
|
async fn get_existing_bucket(&self, bucket: &String) -> Result<Bucket, Error> {
|
||||||
self.garage
|
self.garage
|
||||||
.bucket_table
|
.bucket_table
|
||||||
|
@ -264,10 +265,7 @@ impl AdminRpcHandler {
|
||||||
.await?
|
.await?
|
||||||
.filter(|b| !b.is_deleted())
|
.filter(|b| !b.is_deleted())
|
||||||
.map(Ok)
|
.map(Ok)
|
||||||
.unwrap_or(Err(Error::BadRPC(format!(
|
.unwrap_or_else(|| Err(Error::BadRpc(format!("Bucket {} does not exist", bucket))))
|
||||||
"Bucket {} does not exist",
|
|
||||||
bucket
|
|
||||||
))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_existing_key(&self, pattern: &str) -> Result<Key, Error> {
|
async fn get_existing_key(&self, pattern: &str) -> Result<Key, Error> {
|
||||||
|
@ -298,7 +296,7 @@ impl AdminRpcHandler {
|
||||||
async fn update_bucket_key(
|
async fn update_bucket_key(
|
||||||
&self,
|
&self,
|
||||||
mut bucket: Bucket,
|
mut bucket: Bucket,
|
||||||
key_id: &String,
|
key_id: &str,
|
||||||
allow_read: bool,
|
allow_read: bool,
|
||||||
allow_write: bool,
|
allow_write: bool,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
@ -313,9 +311,9 @@ impl AdminRpcHandler {
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::Message(format!(
|
return Err(Error::Message(
|
||||||
"Bucket is deleted in update_bucket_key"
|
"Bucket is deleted in update_bucket_key".to_string(),
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
self.garage.bucket_table.insert(&bucket).await?;
|
self.garage.bucket_table.insert(&bucket).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -325,14 +323,14 @@ impl AdminRpcHandler {
|
||||||
async fn update_key_bucket(
|
async fn update_key_bucket(
|
||||||
&self,
|
&self,
|
||||||
key: &Key,
|
key: &Key,
|
||||||
bucket: &String,
|
bucket: &str,
|
||||||
allow_read: bool,
|
allow_read: bool,
|
||||||
allow_write: bool,
|
allow_write: bool,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut key = key.clone();
|
let mut key = key.clone();
|
||||||
let old_map = key.authorized_buckets.take_and_clear();
|
let old_map = key.authorized_buckets.take_and_clear();
|
||||||
key.authorized_buckets.merge(&old_map.update_mutator(
|
key.authorized_buckets.merge(&old_map.update_mutator(
|
||||||
bucket.clone(),
|
bucket.to_string(),
|
||||||
PermissionSet {
|
PermissionSet {
|
||||||
allow_read,
|
allow_read,
|
||||||
allow_write,
|
allow_write,
|
||||||
|
@ -342,11 +340,11 @@ impl AdminRpcHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_launch_repair(self: &Arc<Self>, opt: RepairOpt) -> Result<AdminRPC, Error> {
|
async fn handle_launch_repair(self: &Arc<Self>, opt: RepairOpt) -> Result<AdminRpc, Error> {
|
||||||
if !opt.yes {
|
if !opt.yes {
|
||||||
return Err(Error::BadRPC(format!(
|
return Err(Error::BadRpc(
|
||||||
"Please provide the --yes flag to initiate repair operations."
|
"Please provide the --yes flag to initiate repair operations.".to_string(),
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
if opt.all_nodes {
|
if opt.all_nodes {
|
||||||
let mut opt_to_send = opt.clone();
|
let mut opt_to_send = opt.clone();
|
||||||
|
@ -359,17 +357,17 @@ impl AdminRpcHandler {
|
||||||
.rpc_client
|
.rpc_client
|
||||||
.call(
|
.call(
|
||||||
*node,
|
*node,
|
||||||
AdminRPC::LaunchRepair(opt_to_send.clone()),
|
AdminRpc::LaunchRepair(opt_to_send.clone()),
|
||||||
ADMIN_RPC_TIMEOUT,
|
ADMIN_RPC_TIMEOUT,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
failures.push(node.clone());
|
failures.push(*node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if failures.is_empty() {
|
if failures.is_empty() {
|
||||||
Ok(AdminRPC::Ok(format!("Repair launched on all nodes")))
|
Ok(AdminRpc::Ok("Repair launched on all nodes".to_string()))
|
||||||
} else {
|
} else {
|
||||||
Err(Error::Message(format!(
|
Err(Error::Message(format!(
|
||||||
"Could not launch repair on nodes: {:?} (launched successfully on other nodes)",
|
"Could not launch repair on nodes: {:?} (launched successfully on other nodes)",
|
||||||
|
@ -386,14 +384,14 @@ impl AdminRpcHandler {
|
||||||
.spawn_worker("Repair worker".into(), move |must_exit| async move {
|
.spawn_worker("Repair worker".into(), move |must_exit| async move {
|
||||||
repair.repair_worker(opt, must_exit).await
|
repair.repair_worker(opt, must_exit).await
|
||||||
});
|
});
|
||||||
Ok(AdminRPC::Ok(format!(
|
Ok(AdminRpc::Ok(format!(
|
||||||
"Repair launched on {:?}",
|
"Repair launched on {:?}",
|
||||||
self.garage.system.id
|
self.garage.system.id
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_stats(&self, opt: StatsOpt) -> Result<AdminRPC, Error> {
|
async fn handle_stats(&self, opt: StatsOpt) -> Result<AdminRpc, Error> {
|
||||||
if opt.all_nodes {
|
if opt.all_nodes {
|
||||||
let mut ret = String::new();
|
let mut ret = String::new();
|
||||||
let ring = self.garage.system.ring.borrow().clone();
|
let ring = self.garage.system.ring.borrow().clone();
|
||||||
|
@ -406,21 +404,21 @@ impl AdminRpcHandler {
|
||||||
writeln!(&mut ret, "Stats for node {:?}:", node).unwrap();
|
writeln!(&mut ret, "Stats for node {:?}:", node).unwrap();
|
||||||
match self
|
match self
|
||||||
.rpc_client
|
.rpc_client
|
||||||
.call(*node, AdminRPC::Stats(opt), ADMIN_RPC_TIMEOUT)
|
.call(*node, AdminRpc::Stats(opt), ADMIN_RPC_TIMEOUT)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(AdminRPC::Ok(s)) => writeln!(&mut ret, "{}", s).unwrap(),
|
Ok(AdminRpc::Ok(s)) => writeln!(&mut ret, "{}", s).unwrap(),
|
||||||
Ok(x) => writeln!(&mut ret, "Bad answer: {:?}", x).unwrap(),
|
Ok(x) => writeln!(&mut ret, "Bad answer: {:?}", x).unwrap(),
|
||||||
Err(e) => writeln!(&mut ret, "Error: {}", e).unwrap(),
|
Err(e) => writeln!(&mut ret, "Error: {}", e).unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(AdminRPC::Ok(ret))
|
Ok(AdminRpc::Ok(ret))
|
||||||
} else {
|
} else {
|
||||||
Ok(AdminRPC::Ok(self.gather_stats_local(opt)?))
|
Ok(AdminRpc::Ok(self.gather_stats_local(opt)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gather_stats_local(&self, opt: StatsOpt) -> Result<String, Error> {
|
fn gather_stats_local(&self, opt: StatsOpt) -> String {
|
||||||
let mut ret = String::new();
|
let mut ret = String::new();
|
||||||
writeln!(
|
writeln!(
|
||||||
&mut ret,
|
&mut ret,
|
||||||
|
@ -445,11 +443,11 @@ impl AdminRpcHandler {
|
||||||
writeln!(&mut ret, " {:?} {}", n, c).unwrap();
|
writeln!(&mut ret, " {:?} {}", n, c).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.gather_table_stats(&mut ret, &self.garage.bucket_table, &opt)?;
|
self.gather_table_stats(&mut ret, &self.garage.bucket_table, &opt);
|
||||||
self.gather_table_stats(&mut ret, &self.garage.key_table, &opt)?;
|
self.gather_table_stats(&mut ret, &self.garage.key_table, &opt);
|
||||||
self.gather_table_stats(&mut ret, &self.garage.object_table, &opt)?;
|
self.gather_table_stats(&mut ret, &self.garage.object_table, &opt);
|
||||||
self.gather_table_stats(&mut ret, &self.garage.version_table, &opt)?;
|
self.gather_table_stats(&mut ret, &self.garage.version_table, &opt);
|
||||||
self.gather_table_stats(&mut ret, &self.garage.block_ref_table, &opt)?;
|
self.gather_table_stats(&mut ret, &self.garage.block_ref_table, &opt);
|
||||||
|
|
||||||
writeln!(&mut ret, "\nBlock manager stats:").unwrap();
|
writeln!(&mut ret, "\nBlock manager stats:").unwrap();
|
||||||
if opt.detailed {
|
if opt.detailed {
|
||||||
|
@ -467,15 +465,10 @@ impl AdminRpcHandler {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Ok(ret)
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gather_table_stats<F, R>(
|
fn gather_table_stats<F, R>(&self, to: &mut String, t: &Arc<Table<F, R>>, opt: &StatsOpt)
|
||||||
&self,
|
|
||||||
to: &mut String,
|
|
||||||
t: &Arc<Table<F, R>>,
|
|
||||||
opt: &StatsOpt,
|
|
||||||
) -> Result<(), Error>
|
|
||||||
where
|
where
|
||||||
F: TableSchema + 'static,
|
F: TableSchema + 'static,
|
||||||
R: TableReplication + 'static,
|
R: TableReplication + 'static,
|
||||||
|
@ -497,6 +490,5 @@ impl AdminRpcHandler {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
writeln!(to, " GC todo queue length: {}", t.data.gc_todo_len()).unwrap();
|
writeln!(to, " GC todo queue length: {}", t.data.gc_todo_len()).unwrap();
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::path::PathBuf;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
use garage_util::data::UUID;
|
use garage_util::data::Uuid;
|
||||||
use garage_util::error::Error;
|
use garage_util::error::Error;
|
||||||
use garage_util::time::*;
|
use garage_util::time::*;
|
||||||
|
|
||||||
|
@ -294,7 +294,7 @@ pub struct StatsOpt {
|
||||||
pub async fn cli_cmd(
|
pub async fn cli_cmd(
|
||||||
cmd: Command,
|
cmd: Command,
|
||||||
membership_rpc_cli: RpcAddrClient<Message>,
|
membership_rpc_cli: RpcAddrClient<Message>,
|
||||||
admin_rpc_cli: RpcAddrClient<AdminRPC>,
|
admin_rpc_cli: RpcAddrClient<AdminRpc>,
|
||||||
rpc_host: SocketAddr,
|
rpc_host: SocketAddr,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
match cmd {
|
match cmd {
|
||||||
|
@ -306,11 +306,11 @@ pub async fn cli_cmd(
|
||||||
cmd_remove(membership_rpc_cli, rpc_host, remove_opt).await
|
cmd_remove(membership_rpc_cli, rpc_host, remove_opt).await
|
||||||
}
|
}
|
||||||
Command::Bucket(bo) => {
|
Command::Bucket(bo) => {
|
||||||
cmd_admin(admin_rpc_cli, rpc_host, AdminRPC::BucketOperation(bo)).await
|
cmd_admin(admin_rpc_cli, rpc_host, AdminRpc::BucketOperation(bo)).await
|
||||||
}
|
}
|
||||||
Command::Key(ko) => cmd_admin(admin_rpc_cli, rpc_host, AdminRPC::KeyOperation(ko)).await,
|
Command::Key(ko) => cmd_admin(admin_rpc_cli, rpc_host, AdminRpc::KeyOperation(ko)).await,
|
||||||
Command::Repair(ro) => cmd_admin(admin_rpc_cli, rpc_host, AdminRPC::LaunchRepair(ro)).await,
|
Command::Repair(ro) => cmd_admin(admin_rpc_cli, rpc_host, AdminRpc::LaunchRepair(ro)).await,
|
||||||
Command::Stats(so) => cmd_admin(admin_rpc_cli, rpc_host, AdminRPC::Stats(so)).await,
|
Command::Stats(so) => cmd_admin(admin_rpc_cli, rpc_host, AdminRpc::Stats(so)).await,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -385,9 +385,9 @@ pub async fn cmd_status(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_matching_node(
|
pub fn find_matching_node(
|
||||||
cand: impl std::iter::Iterator<Item = UUID>,
|
cand: impl std::iter::Iterator<Item = Uuid>,
|
||||||
pattern: &str,
|
pattern: &str,
|
||||||
) -> Result<UUID, Error> {
|
) -> Result<Uuid, Error> {
|
||||||
let mut candidates = vec![];
|
let mut candidates = vec![];
|
||||||
for c in cand {
|
for c in cand {
|
||||||
if hex::encode(&c).starts_with(&pattern) {
|
if hex::encode(&c).starts_with(&pattern) {
|
||||||
|
@ -446,12 +446,14 @@ pub async fn cmd_configure(
|
||||||
capacity: args
|
capacity: args
|
||||||
.capacity
|
.capacity
|
||||||
.expect("Please specifiy a capacity with the -c flag"),
|
.expect("Please specifiy a capacity with the -c flag"),
|
||||||
tag: args.tag.unwrap_or("".to_string()),
|
tag: args.tag.unwrap_or_default(),
|
||||||
},
|
},
|
||||||
Some(old) => NetworkConfigEntry {
|
Some(old) => NetworkConfigEntry {
|
||||||
datacenter: args.datacenter.unwrap_or(old.datacenter.to_string()),
|
datacenter: args
|
||||||
|
.datacenter
|
||||||
|
.unwrap_or_else(|| old.datacenter.to_string()),
|
||||||
capacity: args.capacity.unwrap_or(old.capacity),
|
capacity: args.capacity.unwrap_or(old.capacity),
|
||||||
tag: args.tag.unwrap_or(old.tag.to_string()),
|
tag: args.tag.unwrap_or_else(|| old.tag.to_string()),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -504,30 +506,30 @@ pub async fn cmd_remove(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn cmd_admin(
|
pub async fn cmd_admin(
|
||||||
rpc_cli: RpcAddrClient<AdminRPC>,
|
rpc_cli: RpcAddrClient<AdminRpc>,
|
||||||
rpc_host: SocketAddr,
|
rpc_host: SocketAddr,
|
||||||
args: AdminRPC,
|
args: AdminRpc,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
match rpc_cli.call(&rpc_host, args, ADMIN_RPC_TIMEOUT).await?? {
|
match rpc_cli.call(&rpc_host, args, ADMIN_RPC_TIMEOUT).await?? {
|
||||||
AdminRPC::Ok(msg) => {
|
AdminRpc::Ok(msg) => {
|
||||||
println!("{}", msg);
|
println!("{}", msg);
|
||||||
}
|
}
|
||||||
AdminRPC::BucketList(bl) => {
|
AdminRpc::BucketList(bl) => {
|
||||||
println!("List of buckets:");
|
println!("List of buckets:");
|
||||||
for bucket in bl {
|
for bucket in bl {
|
||||||
println!("{}", bucket);
|
println!("{}", bucket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AdminRPC::BucketInfo(bucket) => {
|
AdminRpc::BucketInfo(bucket) => {
|
||||||
print_bucket_info(&bucket);
|
print_bucket_info(&bucket);
|
||||||
}
|
}
|
||||||
AdminRPC::KeyList(kl) => {
|
AdminRpc::KeyList(kl) => {
|
||||||
println!("List of keys:");
|
println!("List of keys:");
|
||||||
for key in kl {
|
for key in kl {
|
||||||
println!("{}\t{}", key.0, key.1);
|
println!("{}\t{}", key.0, key.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AdminRPC::KeyInfo(key) => {
|
AdminRpc::KeyInfo(key) => {
|
||||||
print_key_info(&key);
|
print_key_info(&key);
|
||||||
}
|
}
|
||||||
r => {
|
r => {
|
||||||
|
|
|
@ -25,7 +25,7 @@ async fn shutdown_signal(send_cancel: watch::Sender<bool>) -> Result<(), Error>
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn wait_from(mut chan: watch::Receiver<bool>) -> () {
|
async fn wait_from(mut chan: watch::Receiver<bool>) {
|
||||||
while !*chan.borrow() {
|
while !*chan.borrow() {
|
||||||
if chan.changed().await.is_err() {
|
if chan.changed().await.is_err() {
|
||||||
return;
|
return;
|
||||||
|
@ -48,7 +48,7 @@ pub async fn run_server(config_file: PathBuf) -> Result<(), Error> {
|
||||||
.expect("Unable to open sled DB");
|
.expect("Unable to open sled DB");
|
||||||
|
|
||||||
info!("Initialize RPC server...");
|
info!("Initialize RPC server...");
|
||||||
let mut rpc_server = RpcServer::new(config.rpc_bind_addr.clone(), config.rpc_tls.clone());
|
let mut rpc_server = RpcServer::new(config.rpc_bind_addr, config.rpc_tls.clone());
|
||||||
|
|
||||||
info!("Initializing background runner...");
|
info!("Initializing background runner...");
|
||||||
let (send_cancel, watch_cancel) = watch::channel(false);
|
let (send_cancel, watch_cancel) = watch::channel(false);
|
||||||
|
@ -71,9 +71,9 @@ pub async fn run_server(config_file: PathBuf) -> Result<(), Error> {
|
||||||
let web_server = run_web_server(garage, wait_from(watch_cancel.clone()));
|
let web_server = run_web_server(garage, wait_from(watch_cancel.clone()));
|
||||||
|
|
||||||
futures::try_join!(
|
futures::try_join!(
|
||||||
bootstrap.map(|rv| {
|
bootstrap.map(|()| {
|
||||||
info!("Bootstrap done");
|
info!("Bootstrap done");
|
||||||
Ok(rv)
|
Ok(())
|
||||||
}),
|
}),
|
||||||
run_rpc_server.map(|rv| {
|
run_rpc_server.map(|rv| {
|
||||||
info!("RPC server exited");
|
info!("RPC server exited");
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ impl BlockManager {
|
||||||
Message::PutBlock(m) => self.write_block(&m.hash, &m.data).await,
|
Message::PutBlock(m) => self.write_block(&m.hash, &m.data).await,
|
||||||
Message::GetBlock(h) => self.read_block(h).await,
|
Message::GetBlock(h) => self.read_block(h).await,
|
||||||
Message::NeedBlockQuery(h) => self.need_block(h).await.map(Message::NeedBlockReply),
|
Message::NeedBlockQuery(h) => self.need_block(h).await.map(Message::NeedBlockReply),
|
||||||
_ => Err(Error::BadRPC(format!("Unexpected RPC message"))),
|
_ => Err(Error::BadRpc("Unexpected RPC message".to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,8 +280,8 @@ impl BlockManager {
|
||||||
if let Err(e) = self.resync_iter(&mut must_exit).await {
|
if let Err(e) = self.resync_iter(&mut must_exit).await {
|
||||||
warn!("Error in block resync loop: {}", e);
|
warn!("Error in block resync loop: {}", e);
|
||||||
select! {
|
select! {
|
||||||
_ = tokio::time::sleep(Duration::from_secs(1)).fuse() => (),
|
_ = tokio::time::sleep(Duration::from_secs(1)).fuse() => {},
|
||||||
_ = must_exit.changed().fuse() => (),
|
_ = must_exit.changed().fuse() => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -304,15 +304,15 @@ impl BlockManager {
|
||||||
} else {
|
} else {
|
||||||
let delay = tokio::time::sleep(Duration::from_millis(time_msec - now));
|
let delay = tokio::time::sleep(Duration::from_millis(time_msec - now));
|
||||||
select! {
|
select! {
|
||||||
_ = delay.fuse() => (),
|
_ = delay.fuse() => {},
|
||||||
_ = self.resync_notify.notified().fuse() => (),
|
_ = self.resync_notify.notified().fuse() => {},
|
||||||
_ = must_exit.changed().fuse() => (),
|
_ = must_exit.changed().fuse() => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
select! {
|
select! {
|
||||||
_ = self.resync_notify.notified().fuse() => (),
|
_ = self.resync_notify.notified().fuse() => {},
|
||||||
_ = must_exit.changed().fuse() => (),
|
_ = must_exit.changed().fuse() => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -342,7 +342,7 @@ impl BlockManager {
|
||||||
|
|
||||||
let mut who = self.replication.write_nodes(&hash);
|
let mut who = self.replication.write_nodes(&hash);
|
||||||
if who.len() < self.replication.write_quorum() {
|
if who.len() < self.replication.write_quorum() {
|
||||||
return Err(Error::Message(format!("Not trying to offload block because we don't have a quorum of nodes to write to")));
|
return Err(Error::Message("Not trying to offload block because we don't have a quorum of nodes to write to".to_string()));
|
||||||
}
|
}
|
||||||
who.retain(|id| *id != self.system.id);
|
who.retain(|id| *id != self.system.id);
|
||||||
|
|
||||||
|
@ -362,14 +362,14 @@ impl BlockManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::Message(format!(
|
return Err(Error::Message(
|
||||||
"Unexpected response to NeedBlockQuery RPC"
|
"Unexpected response to NeedBlockQuery RPC".to_string(),
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if need_nodes.len() > 0 {
|
if !need_nodes.is_empty() {
|
||||||
trace!(
|
trace!(
|
||||||
"Block {:?} needed by {} nodes, sending",
|
"Block {:?} needed by {} nodes, sending",
|
||||||
hash,
|
hash,
|
||||||
|
@ -478,7 +478,7 @@ impl BlockManager {
|
||||||
|
|
||||||
fn repair_aux_read_dir_rec<'a>(
|
fn repair_aux_read_dir_rec<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
path: &'a PathBuf,
|
path: &'a Path,
|
||||||
must_exit: &'a watch::Receiver<bool>,
|
must_exit: &'a watch::Receiver<bool>,
|
||||||
) -> BoxFuture<'a, Result<(), Error>> {
|
) -> BoxFuture<'a, Result<(), Error>> {
|
||||||
// Lists all blocks on disk and adds them to the resync queue.
|
// Lists all blocks on disk and adds them to the resync queue.
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use garage_util::data::*;
|
use garage_util::data::*;
|
||||||
|
|
||||||
use garage_table::crdt::CRDT;
|
use garage_table::crdt::Crdt;
|
||||||
use garage_table::*;
|
use garage_table::*;
|
||||||
|
|
||||||
use crate::block::*;
|
use crate::block::*;
|
||||||
|
@ -14,18 +14,18 @@ pub struct BlockRef {
|
||||||
pub block: Hash,
|
pub block: Hash,
|
||||||
|
|
||||||
/// Id of the Version for the object containing this block, used as sorting key
|
/// Id of the Version for the object containing this block, used as sorting key
|
||||||
pub version: UUID,
|
pub version: Uuid,
|
||||||
|
|
||||||
// Keep track of deleted status
|
// Keep track of deleted status
|
||||||
/// Is the Version that contains this block deleted
|
/// Is the Version that contains this block deleted
|
||||||
pub deleted: crdt::Bool,
|
pub deleted: crdt::Bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entry<Hash, UUID> for BlockRef {
|
impl Entry<Hash, Uuid> for BlockRef {
|
||||||
fn partition_key(&self) -> &Hash {
|
fn partition_key(&self) -> &Hash {
|
||||||
&self.block
|
&self.block
|
||||||
}
|
}
|
||||||
fn sort_key(&self) -> &UUID {
|
fn sort_key(&self) -> &Uuid {
|
||||||
&self.version
|
&self.version
|
||||||
}
|
}
|
||||||
fn is_tombstone(&self) -> bool {
|
fn is_tombstone(&self) -> bool {
|
||||||
|
@ -33,7 +33,7 @@ impl Entry<Hash, UUID> for BlockRef {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CRDT for BlockRef {
|
impl Crdt for BlockRef {
|
||||||
fn merge(&mut self, other: &Self) {
|
fn merge(&mut self, other: &Self) {
|
||||||
self.deleted.merge(&other.deleted);
|
self.deleted.merge(&other.deleted);
|
||||||
}
|
}
|
||||||
|
@ -45,12 +45,12 @@ pub struct BlockRefTable {
|
||||||
|
|
||||||
impl TableSchema for BlockRefTable {
|
impl TableSchema for BlockRefTable {
|
||||||
type P = Hash;
|
type P = Hash;
|
||||||
type S = UUID;
|
type S = Uuid;
|
||||||
type E = BlockRef;
|
type E = BlockRef;
|
||||||
type Filter = DeletedFilter;
|
type Filter = DeletedFilter;
|
||||||
|
|
||||||
fn updated(&self, old: Option<Self::E>, new: Option<Self::E>) {
|
fn updated(&self, old: Option<Self::E>, new: Option<Self::E>) {
|
||||||
let block = &old.as_ref().or(new.as_ref()).unwrap().block;
|
let block = &old.as_ref().or_else(|| new.as_ref()).unwrap().block;
|
||||||
let was_before = old.as_ref().map(|x| !x.deleted.get()).unwrap_or(false);
|
let was_before = old.as_ref().map(|x| !x.deleted.get()).unwrap_or(false);
|
||||||
let is_after = new.as_ref().map(|x| !x.deleted.get()).unwrap_or(false);
|
let is_after = new.as_ref().map(|x| !x.deleted.get()).unwrap_or(false);
|
||||||
if is_after && !was_before {
|
if is_after && !was_before {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use garage_table::crdt::CRDT;
|
use garage_table::crdt::Crdt;
|
||||||
use garage_table::*;
|
use garage_table::*;
|
||||||
|
|
||||||
use crate::key_table::PermissionSet;
|
use crate::key_table::PermissionSet;
|
||||||
|
@ -15,7 +15,7 @@ pub struct Bucket {
|
||||||
/// Name of the bucket
|
/// Name of the bucket
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// State, and configuration if not deleted, of the bucket
|
/// State, and configuration if not deleted, of the bucket
|
||||||
pub state: crdt::LWW<BucketState>,
|
pub state: crdt::Lww<BucketState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State of a bucket
|
/// State of a bucket
|
||||||
|
@ -27,7 +27,7 @@ pub enum BucketState {
|
||||||
Present(BucketParams),
|
Present(BucketParams),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CRDT for BucketState {
|
impl Crdt for BucketState {
|
||||||
fn merge(&mut self, o: &Self) {
|
fn merge(&mut self, o: &Self) {
|
||||||
match o {
|
match o {
|
||||||
BucketState::Deleted => *self = BucketState::Deleted,
|
BucketState::Deleted => *self = BucketState::Deleted,
|
||||||
|
@ -44,34 +44,40 @@ impl CRDT for BucketState {
|
||||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct BucketParams {
|
pub struct BucketParams {
|
||||||
/// Map of key with access to the bucket, and what kind of access they give
|
/// Map of key with access to the bucket, and what kind of access they give
|
||||||
pub authorized_keys: crdt::LWWMap<String, PermissionSet>,
|
pub authorized_keys: crdt::LwwMap<String, PermissionSet>,
|
||||||
/// Is the bucket served as http
|
/// Is the bucket served as http
|
||||||
pub website: crdt::LWW<bool>,
|
pub website: crdt::Lww<bool>,
|
||||||
}
|
|
||||||
|
|
||||||
impl CRDT for BucketParams {
|
|
||||||
fn merge(&mut self, o: &Self) {
|
|
||||||
self.authorized_keys.merge(&o.authorized_keys);
|
|
||||||
self.website.merge(&o.website);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BucketParams {
|
impl BucketParams {
|
||||||
/// Create an empty BucketParams with no authorized keys and no website accesss
|
/// Create an empty BucketParams with no authorized keys and no website accesss
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
BucketParams {
|
BucketParams {
|
||||||
authorized_keys: crdt::LWWMap::new(),
|
authorized_keys: crdt::LwwMap::new(),
|
||||||
website: crdt::LWW::new(false),
|
website: crdt::Lww::new(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Crdt for BucketParams {
|
||||||
|
fn merge(&mut self, o: &Self) {
|
||||||
|
self.authorized_keys.merge(&o.authorized_keys);
|
||||||
|
self.website.merge(&o.website);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BucketParams {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Bucket {
|
impl Bucket {
|
||||||
/// Initializes a new instance of the Bucket struct
|
/// Initializes a new instance of the Bucket struct
|
||||||
pub fn new(name: String) -> Self {
|
pub fn new(name: String) -> Self {
|
||||||
Bucket {
|
Bucket {
|
||||||
name,
|
name,
|
||||||
state: crdt::LWW::new(BucketState::Present(BucketParams::new())),
|
state: crdt::Lww::new(BucketState::Present(BucketParams::new())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +105,7 @@ impl Entry<EmptyKey, String> for Bucket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CRDT for Bucket {
|
impl Crdt for Bucket {
|
||||||
fn merge(&mut self, other: &Self) {
|
fn merge(&mut self, other: &Self) {
|
||||||
self.state.merge(&other.state);
|
self.state.merge(&other.state);
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,7 @@ impl Garage {
|
||||||
BlockRefTable {
|
BlockRefTable {
|
||||||
block_manager: block_manager.clone(),
|
block_manager: block_manager.clone(),
|
||||||
},
|
},
|
||||||
data_rep_param.clone(),
|
data_rep_param,
|
||||||
system.clone(),
|
system.clone(),
|
||||||
&db,
|
&db,
|
||||||
"block_ref".to_string(),
|
"block_ref".to_string(),
|
||||||
|
@ -121,7 +121,7 @@ impl Garage {
|
||||||
background: background.clone(),
|
background: background.clone(),
|
||||||
version_table: version_table.clone(),
|
version_table: version_table.clone(),
|
||||||
},
|
},
|
||||||
meta_rep_param.clone(),
|
meta_rep_param,
|
||||||
system.clone(),
|
system.clone(),
|
||||||
&db,
|
&db,
|
||||||
"object".to_string(),
|
"object".to_string(),
|
||||||
|
@ -141,7 +141,7 @@ impl Garage {
|
||||||
info!("Initialize key_table_table...");
|
info!("Initialize key_table_table...");
|
||||||
let key_table = Table::new(
|
let key_table = Table::new(
|
||||||
KeyTable,
|
KeyTable,
|
||||||
control_rep_param.clone(),
|
control_rep_param,
|
||||||
system.clone(),
|
system.clone(),
|
||||||
&db,
|
&db,
|
||||||
"key".to_string(),
|
"key".to_string(),
|
||||||
|
@ -152,9 +152,9 @@ impl Garage {
|
||||||
let garage = Arc::new(Self {
|
let garage = Arc::new(Self {
|
||||||
config,
|
config,
|
||||||
db,
|
db,
|
||||||
system: system.clone(),
|
|
||||||
block_manager,
|
|
||||||
background,
|
background,
|
||||||
|
system,
|
||||||
|
block_manager,
|
||||||
bucket_table,
|
bucket_table,
|
||||||
key_table,
|
key_table,
|
||||||
object_table,
|
object_table,
|
||||||
|
|
|
@ -13,14 +13,14 @@ pub struct Key {
|
||||||
pub secret_key: String,
|
pub secret_key: String,
|
||||||
|
|
||||||
/// Name for the key
|
/// Name for the key
|
||||||
pub name: crdt::LWW<String>,
|
pub name: crdt::Lww<String>,
|
||||||
|
|
||||||
/// Is the key deleted
|
/// Is the key deleted
|
||||||
pub deleted: crdt::Bool,
|
pub deleted: crdt::Bool,
|
||||||
|
|
||||||
/// Buckets in which the key is authorized. Empty if `Key` is deleted
|
/// Buckets in which the key is authorized. Empty if `Key` is deleted
|
||||||
// CRDT interaction: deleted implies authorized_buckets is empty
|
// CRDT interaction: deleted implies authorized_buckets is empty
|
||||||
pub authorized_buckets: crdt::LWWMap<String, PermissionSet>,
|
pub authorized_buckets: crdt::LwwMap<String, PermissionSet>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Key {
|
impl Key {
|
||||||
|
@ -31,9 +31,9 @@ impl Key {
|
||||||
Self {
|
Self {
|
||||||
key_id,
|
key_id,
|
||||||
secret_key,
|
secret_key,
|
||||||
name: crdt::LWW::new(name),
|
name: crdt::Lww::new(name),
|
||||||
deleted: crdt::Bool::new(false),
|
deleted: crdt::Bool::new(false),
|
||||||
authorized_buckets: crdt::LWWMap::new(),
|
authorized_buckets: crdt::LwwMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,9 +42,9 @@ impl Key {
|
||||||
Self {
|
Self {
|
||||||
key_id: key_id.to_string(),
|
key_id: key_id.to_string(),
|
||||||
secret_key: secret_key.to_string(),
|
secret_key: secret_key.to_string(),
|
||||||
name: crdt::LWW::new(name.to_string()),
|
name: crdt::Lww::new(name.to_string()),
|
||||||
deleted: crdt::Bool::new(false),
|
deleted: crdt::Bool::new(false),
|
||||||
authorized_buckets: crdt::LWWMap::new(),
|
authorized_buckets: crdt::LwwMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,9 +53,9 @@ impl Key {
|
||||||
Self {
|
Self {
|
||||||
key_id,
|
key_id,
|
||||||
secret_key: "".into(),
|
secret_key: "".into(),
|
||||||
name: crdt::LWW::new("".to_string()),
|
name: crdt::Lww::new("".to_string()),
|
||||||
deleted: crdt::Bool::new(true),
|
deleted: crdt::Bool::new(true),
|
||||||
authorized_buckets: crdt::LWWMap::new(),
|
authorized_buckets: crdt::LwwMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ pub struct PermissionSet {
|
||||||
pub allow_write: bool,
|
pub allow_write: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AutoCRDT for PermissionSet {
|
impl AutoCrdt for PermissionSet {
|
||||||
const WARN_IF_DIFFERENT: bool = true;
|
const WARN_IF_DIFFERENT: bool = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ impl Entry<EmptyKey, String> for Key {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CRDT for Key {
|
impl Crdt for Key {
|
||||||
fn merge(&mut self, other: &Self) {
|
fn merge(&mut self, other: &Self) {
|
||||||
self.name.merge(&other.name);
|
self.name.merge(&other.name);
|
||||||
self.deleted.merge(&other.deleted);
|
self.deleted.merge(&other.deleted);
|
||||||
|
|
|
@ -40,6 +40,7 @@ impl Object {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a version if it wasn't already present
|
/// Adds a version if it wasn't already present
|
||||||
|
#[allow(clippy::result_unit_err)]
|
||||||
pub fn add_version(&mut self, new: ObjectVersion) -> Result<(), ()> {
|
pub fn add_version(&mut self, new: ObjectVersion) -> Result<(), ()> {
|
||||||
match self
|
match self
|
||||||
.versions
|
.versions
|
||||||
|
@ -63,7 +64,7 @@ impl Object {
|
||||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct ObjectVersion {
|
pub struct ObjectVersion {
|
||||||
/// Id of the version
|
/// Id of the version
|
||||||
pub uuid: UUID,
|
pub uuid: Uuid,
|
||||||
/// Timestamp of when the object was created
|
/// Timestamp of when the object was created
|
||||||
pub timestamp: u64,
|
pub timestamp: u64,
|
||||||
/// State of the version
|
/// State of the version
|
||||||
|
@ -81,7 +82,7 @@ pub enum ObjectVersionState {
|
||||||
Aborted,
|
Aborted,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CRDT for ObjectVersionState {
|
impl Crdt for ObjectVersionState {
|
||||||
fn merge(&mut self, other: &Self) {
|
fn merge(&mut self, other: &Self) {
|
||||||
use ObjectVersionState::*;
|
use ObjectVersionState::*;
|
||||||
match other {
|
match other {
|
||||||
|
@ -114,7 +115,7 @@ pub enum ObjectVersionData {
|
||||||
FirstBlock(ObjectVersionMeta, Hash),
|
FirstBlock(ObjectVersionMeta, Hash),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AutoCRDT for ObjectVersionData {
|
impl AutoCrdt for ObjectVersionData {
|
||||||
const WARN_IF_DIFFERENT: bool = true;
|
const WARN_IF_DIFFERENT: bool = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,24 +140,18 @@ pub struct ObjectVersionHeaders {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectVersion {
|
impl ObjectVersion {
|
||||||
fn cmp_key(&self) -> (u64, UUID) {
|
fn cmp_key(&self) -> (u64, Uuid) {
|
||||||
(self.timestamp, self.uuid)
|
(self.timestamp, self.uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is the object version currently being uploaded
|
/// Is the object version currently being uploaded
|
||||||
pub fn is_uploading(&self) -> bool {
|
pub fn is_uploading(&self) -> bool {
|
||||||
match self.state {
|
matches!(self.state, ObjectVersionState::Uploading(_))
|
||||||
ObjectVersionState::Uploading(_) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is the object version completely received
|
/// Is the object version completely received
|
||||||
pub fn is_complete(&self) -> bool {
|
pub fn is_complete(&self) -> bool {
|
||||||
match self.state {
|
matches!(self.state, ObjectVersionState::Complete(_))
|
||||||
ObjectVersionState::Complete(_) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is the object version available (received and not a tombstone)
|
/// Is the object version available (received and not a tombstone)
|
||||||
|
@ -183,7 +178,7 @@ impl Entry<String, String> for Object {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CRDT for Object {
|
impl Crdt for Object {
|
||||||
fn merge(&mut self, other: &Self) {
|
fn merge(&mut self, other: &Self) {
|
||||||
// Merge versions from other into here
|
// Merge versions from other into here
|
||||||
for other_v in other.versions.iter() {
|
for other_v in other.versions.iter() {
|
||||||
|
@ -207,8 +202,7 @@ impl CRDT for Object {
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.rev()
|
.rev()
|
||||||
.filter(|(_, v)| v.is_complete())
|
.find(|(_, v)| v.is_complete())
|
||||||
.next()
|
|
||||||
.map(|(vi, _)| vi);
|
.map(|(vi, _)| vi);
|
||||||
|
|
||||||
if let Some(last_vi) = last_complete {
|
if let Some(last_vi) = last_complete {
|
||||||
|
|
|
@ -14,7 +14,7 @@ use crate::block_ref_table::*;
|
||||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct Version {
|
pub struct Version {
|
||||||
/// UUID of the version, used as partition key
|
/// UUID of the version, used as partition key
|
||||||
pub uuid: UUID,
|
pub uuid: Uuid,
|
||||||
|
|
||||||
// Actual data: the blocks for this version
|
// Actual data: the blocks for this version
|
||||||
// In the case of a multipart upload, also store the etags
|
// In the case of a multipart upload, also store the etags
|
||||||
|
@ -35,7 +35,7 @@ pub struct Version {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Version {
|
impl Version {
|
||||||
pub fn new(uuid: UUID, bucket: String, key: String, deleted: bool) -> Self {
|
pub fn new(uuid: Uuid, bucket: String, key: String, deleted: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
uuid,
|
uuid,
|
||||||
deleted: deleted.into(),
|
deleted: deleted.into(),
|
||||||
|
@ -78,7 +78,7 @@ pub struct VersionBlock {
|
||||||
pub size: u64,
|
pub size: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AutoCRDT for VersionBlock {
|
impl AutoCrdt for VersionBlock {
|
||||||
const WARN_IF_DIFFERENT: bool = true;
|
const WARN_IF_DIFFERENT: bool = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ impl Entry<Hash, EmptyKey> for Version {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CRDT for Version {
|
impl Crdt for Version {
|
||||||
fn merge(&mut self, other: &Self) {
|
fn merge(&mut self, other: &Self) {
|
||||||
self.deleted.merge(&other.deleted);
|
self.deleted.merge(&other.deleted);
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::collections::HashMap;
|
||||||
use std::fmt::Write as FmtWrite;
|
use std::fmt::Write as FmtWrite;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::net::{IpAddr, SocketAddr};
|
use std::net::{IpAddr, SocketAddr};
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
@ -56,7 +56,7 @@ impl RpcMessage for Message {}
|
||||||
/// A ping, containing informations about status and config
|
/// A ping, containing informations about status and config
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct PingMessage {
|
pub struct PingMessage {
|
||||||
id: UUID,
|
id: Uuid,
|
||||||
rpc_port: u16,
|
rpc_port: u16,
|
||||||
|
|
||||||
status_hash: Hash,
|
status_hash: Hash,
|
||||||
|
@ -69,7 +69,7 @@ pub struct PingMessage {
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct AdvertisedNode {
|
pub struct AdvertisedNode {
|
||||||
/// Id of the node this advertisement relates to
|
/// Id of the node this advertisement relates to
|
||||||
pub id: UUID,
|
pub id: Uuid,
|
||||||
/// IP and port of the node
|
/// IP and port of the node
|
||||||
pub addr: SocketAddr,
|
pub addr: SocketAddr,
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ pub struct AdvertisedNode {
|
||||||
/// This node's membership manager
|
/// This node's membership manager
|
||||||
pub struct System {
|
pub struct System {
|
||||||
/// The id of this node
|
/// The id of this node
|
||||||
pub id: UUID,
|
pub id: Uuid,
|
||||||
|
|
||||||
persist_config: Persister<NetworkConfig>,
|
persist_config: Persister<NetworkConfig>,
|
||||||
persist_status: Persister<Vec<AdvertisedNode>>,
|
persist_status: Persister<Vec<AdvertisedNode>>,
|
||||||
|
@ -114,7 +114,7 @@ struct Updaters {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Status {
|
pub struct Status {
|
||||||
/// Mapping of each node id to its known status
|
/// Mapping of each node id to its known status
|
||||||
pub nodes: HashMap<UUID, Arc<StatusEntry>>,
|
pub nodes: HashMap<Uuid, Arc<StatusEntry>>,
|
||||||
/// Hash of `nodes`, used to detect when nodes have different views of the cluster
|
/// Hash of `nodes`, used to detect when nodes have different views of the cluster
|
||||||
pub hash: Hash,
|
pub hash: Hash,
|
||||||
}
|
}
|
||||||
|
@ -198,15 +198,15 @@ impl Status {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen_node_id(metadata_dir: &PathBuf) -> Result<UUID, Error> {
|
fn gen_node_id(metadata_dir: &Path) -> Result<Uuid, Error> {
|
||||||
let mut id_file = metadata_dir.clone();
|
let mut id_file = metadata_dir.to_path_buf();
|
||||||
id_file.push("node_id");
|
id_file.push("node_id");
|
||||||
if id_file.as_path().exists() {
|
if id_file.as_path().exists() {
|
||||||
let mut f = std::fs::File::open(id_file.as_path())?;
|
let mut f = std::fs::File::open(id_file.as_path())?;
|
||||||
let mut d = vec![];
|
let mut d = vec![];
|
||||||
f.read_to_end(&mut d)?;
|
f.read_to_end(&mut d)?;
|
||||||
if d.len() != 32 {
|
if d.len() != 32 {
|
||||||
return Err(Error::Message(format!("Corrupt node_id file")));
|
return Err(Error::Message("Corrupt node_id file".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut id = [0u8; 32];
|
let mut id = [0u8; 32];
|
||||||
|
@ -256,7 +256,7 @@ impl System {
|
||||||
let state_info = StateInfo {
|
let state_info = StateInfo {
|
||||||
hostname: gethostname::gethostname()
|
hostname: gethostname::gethostname()
|
||||||
.into_string()
|
.into_string()
|
||||||
.unwrap_or("<invalid utf-8>".to_string()),
|
.unwrap_or_else(|_| "<invalid utf-8>".to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let ring = Ring::new(net_config);
|
let ring = Ring::new(net_config);
|
||||||
|
@ -296,12 +296,12 @@ impl System {
|
||||||
match msg {
|
match msg {
|
||||||
Message::Ping(ping) => self2.handle_ping(&addr, &ping).await,
|
Message::Ping(ping) => self2.handle_ping(&addr, &ping).await,
|
||||||
|
|
||||||
Message::PullStatus => self2.handle_pull_status(),
|
Message::PullStatus => Ok(self2.handle_pull_status()),
|
||||||
Message::PullConfig => self2.handle_pull_config(),
|
Message::PullConfig => Ok(self2.handle_pull_config()),
|
||||||
Message::AdvertiseNodesUp(adv) => self2.handle_advertise_nodes_up(&adv).await,
|
Message::AdvertiseNodesUp(adv) => self2.handle_advertise_nodes_up(&adv).await,
|
||||||
Message::AdvertiseConfig(adv) => self2.handle_advertise_config(&adv).await,
|
Message::AdvertiseConfig(adv) => self2.handle_advertise_config(&adv).await,
|
||||||
|
|
||||||
_ => Err(Error::BadRPC(format!("Unexpected RPC message"))),
|
_ => Err(Error::BadRpc("Unexpected RPC message".to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -358,18 +358,18 @@ impl System {
|
||||||
) {
|
) {
|
||||||
let self2 = self.clone();
|
let self2 = self.clone();
|
||||||
self.background
|
self.background
|
||||||
.spawn_worker(format!("discovery loop"), |stop_signal| {
|
.spawn_worker("discovery loop".to_string(), |stop_signal| {
|
||||||
self2.discovery_loop(peers, consul_host, consul_service_name, stop_signal)
|
self2.discovery_loop(peers, consul_host, consul_service_name, stop_signal)
|
||||||
});
|
});
|
||||||
|
|
||||||
let self2 = self.clone();
|
let self2 = self.clone();
|
||||||
self.background
|
self.background
|
||||||
.spawn_worker(format!("ping loop"), |stop_signal| {
|
.spawn_worker("ping loop".to_string(), |stop_signal| {
|
||||||
self2.ping_loop(stop_signal)
|
self2.ping_loop(stop_signal)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn ping_nodes(self: Arc<Self>, peers: Vec<(SocketAddr, Option<UUID>)>) {
|
async fn ping_nodes(self: Arc<Self>, peers: Vec<(SocketAddr, Option<Uuid>)>) {
|
||||||
let ping_msg = self.make_ping();
|
let ping_msg = self.make_ping();
|
||||||
let ping_resps = join_all(peers.iter().map(|(addr, id_option)| {
|
let ping_resps = join_all(peers.iter().map(|(addr, id_option)| {
|
||||||
let sys = self.clone();
|
let sys = self.clone();
|
||||||
|
@ -424,7 +424,6 @@ impl System {
|
||||||
warn!("Node {:?} seems to be down.", id);
|
warn!("Node {:?} seems to be down.", id);
|
||||||
if !ring.config.members.contains_key(id) {
|
if !ring.config.members.contains_key(id) {
|
||||||
info!("Removing node {:?} from status (not in config and not responding to pings anymore)", id);
|
info!("Removing node {:?} from status (not in config and not responding to pings anymore)", id);
|
||||||
drop(st);
|
|
||||||
status.nodes.remove(&id);
|
status.nodes.remove(&id);
|
||||||
has_changes = true;
|
has_changes = true;
|
||||||
}
|
}
|
||||||
|
@ -438,7 +437,7 @@ impl System {
|
||||||
self.update_status(&update_locked, status).await;
|
self.update_status(&update_locked, status).await;
|
||||||
drop(update_locked);
|
drop(update_locked);
|
||||||
|
|
||||||
if to_advertise.len() > 0 {
|
if !to_advertise.is_empty() {
|
||||||
self.broadcast(Message::AdvertiseNodesUp(to_advertise), PING_TIMEOUT)
|
self.broadcast(Message::AdvertiseNodesUp(to_advertise), PING_TIMEOUT)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
@ -474,15 +473,13 @@ impl System {
|
||||||
Ok(self.make_ping())
|
Ok(self.make_ping())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_pull_status(&self) -> Result<Message, Error> {
|
fn handle_pull_status(&self) -> Message {
|
||||||
Ok(Message::AdvertiseNodesUp(
|
Message::AdvertiseNodesUp(self.status.borrow().to_serializable_membership(self))
|
||||||
self.status.borrow().to_serializable_membership(self),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_pull_config(&self) -> Result<Message, Error> {
|
fn handle_pull_config(&self) -> Message {
|
||||||
let ring = self.ring.borrow().clone();
|
let ring = self.ring.borrow().clone();
|
||||||
Ok(Message::AdvertiseConfig(ring.config.clone()))
|
Message::AdvertiseConfig(ring.config.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_advertise_nodes_up(
|
async fn handle_advertise_nodes_up(
|
||||||
|
@ -530,7 +527,7 @@ impl System {
|
||||||
self.update_status(&update_lock, status).await;
|
self.update_status(&update_lock, status).await;
|
||||||
drop(update_lock);
|
drop(update_lock);
|
||||||
|
|
||||||
if to_ping.len() > 0 {
|
if !to_ping.is_empty() {
|
||||||
self.background
|
self.background
|
||||||
.spawn_cancellable(self.clone().ping_nodes(to_ping).map(Ok));
|
.spawn_cancellable(self.clone().ping_nodes(to_ping).map(Ok));
|
||||||
}
|
}
|
||||||
|
@ -576,8 +573,8 @@ impl System {
|
||||||
self.clone().ping_nodes(ping_addrs).await;
|
self.clone().ping_nodes(ping_addrs).await;
|
||||||
|
|
||||||
select! {
|
select! {
|
||||||
_ = restart_at.fuse() => (),
|
_ = restart_at.fuse() => {},
|
||||||
_ = stop_signal.changed().fuse() => (),
|
_ = stop_signal.changed().fuse() => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -595,7 +592,7 @@ impl System {
|
||||||
};
|
};
|
||||||
|
|
||||||
while !*stop_signal.borrow() {
|
while !*stop_signal.borrow() {
|
||||||
let not_configured = self.ring.borrow().config.members.len() == 0;
|
let not_configured = self.ring.borrow().config.members.is_empty();
|
||||||
let no_peers = self.status.borrow().nodes.len() < 3;
|
let no_peers = self.status.borrow().nodes.len() < 3;
|
||||||
let bad_peers = self
|
let bad_peers = self
|
||||||
.status
|
.status
|
||||||
|
@ -613,11 +610,8 @@ impl System {
|
||||||
.map(|ip| (*ip, None))
|
.map(|ip| (*ip, None))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
match self.persist_status.load_async().await {
|
if let Ok(peers) = self.persist_status.load_async().await {
|
||||||
Ok(peers) => {
|
ping_list.extend(peers.iter().map(|x| (x.addr, Some(x.id))));
|
||||||
ping_list.extend(peers.iter().map(|x| (x.addr, Some(x.id))));
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((consul_host, consul_service_name)) = &consul_config {
|
if let Some((consul_host, consul_service_name)) = &consul_config {
|
||||||
|
@ -636,15 +630,17 @@ impl System {
|
||||||
|
|
||||||
let restart_at = tokio::time::sleep(DISCOVERY_INTERVAL);
|
let restart_at = tokio::time::sleep(DISCOVERY_INTERVAL);
|
||||||
select! {
|
select! {
|
||||||
_ = restart_at.fuse() => (),
|
_ = restart_at.fuse() => {},
|
||||||
_ = stop_signal.changed().fuse() => (),
|
_ = stop_signal.changed().fuse() => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for some reason fixing this is causing compilation error, see https://github.com/rust-lang/rust-clippy/issues/7052
|
||||||
|
#[allow(clippy::manual_async_fn)]
|
||||||
fn pull_status(
|
fn pull_status(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
peer: UUID,
|
peer: Uuid,
|
||||||
) -> impl futures::future::Future<Output = ()> + Send + 'static {
|
) -> impl futures::future::Future<Output = ()> + Send + 'static {
|
||||||
async move {
|
async move {
|
||||||
let resp = self
|
let resp = self
|
||||||
|
@ -657,7 +653,7 @@ impl System {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn pull_config(self: Arc<Self>, peer: UUID) {
|
async fn pull_config(self: Arc<Self>, peer: Uuid) {
|
||||||
let resp = self
|
let resp = self
|
||||||
.rpc_client
|
.rpc_client
|
||||||
.call(peer, Message::PullConfig, PING_TIMEOUT)
|
.call(peer, Message::PullConfig, PING_TIMEOUT)
|
||||||
|
@ -672,18 +668,15 @@ impl System {
|
||||||
let mut list = status.to_serializable_membership(&self);
|
let mut list = status.to_serializable_membership(&self);
|
||||||
|
|
||||||
// Combine with old peer list to make sure no peer is lost
|
// Combine with old peer list to make sure no peer is lost
|
||||||
match self.persist_status.load_async().await {
|
if let Ok(old_list) = self.persist_status.load_async().await {
|
||||||
Ok(old_list) => {
|
for pp in old_list {
|
||||||
for pp in old_list {
|
if !list.iter().any(|np| pp.id == np.id) {
|
||||||
if !list.iter().any(|np| pp.id == np.id) {
|
list.push(pp);
|
||||||
list.push(pp);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if list.len() > 0 {
|
if !list.is_empty() {
|
||||||
info!("Persisting new peer list ({} peers)", list.len());
|
info!("Persisting new peer list ({} peers)", list.len());
|
||||||
self.persist_status
|
self.persist_status
|
||||||
.save_async(&list)
|
.save_async(&list)
|
||||||
|
|
|
@ -32,7 +32,7 @@ pub const MAX_REPLICATION: usize = 3;
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct NetworkConfig {
|
pub struct NetworkConfig {
|
||||||
/// Map of each node's id to it's configuration
|
/// Map of each node's id to it's configuration
|
||||||
pub members: HashMap<UUID, NetworkConfigEntry>,
|
pub members: HashMap<Uuid, NetworkConfigEntry>,
|
||||||
/// Version of this config
|
/// Version of this config
|
||||||
pub version: u64,
|
pub version: u64,
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ pub struct RingEntry {
|
||||||
/// The prefix of the Hash of object which should use this entry
|
/// The prefix of the Hash of object which should use this entry
|
||||||
pub location: Hash,
|
pub location: Hash,
|
||||||
/// The nodes in which a matching object should get stored
|
/// The nodes in which a matching object should get stored
|
||||||
pub nodes: [UUID; MAX_REPLICATION],
|
pub nodes: [Uuid; MAX_REPLICATION],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ring {
|
impl Ring {
|
||||||
|
@ -92,7 +92,7 @@ impl Ring {
|
||||||
let n_datacenters = datacenters.len();
|
let n_datacenters = datacenters.len();
|
||||||
|
|
||||||
// Prepare ring
|
// Prepare ring
|
||||||
let mut partitions: Vec<Vec<(&UUID, &NetworkConfigEntry)>> = partitions_idx
|
let mut partitions: Vec<Vec<(&Uuid, &NetworkConfigEntry)>> = partitions_idx
|
||||||
.iter()
|
.iter()
|
||||||
.map(|_i| Vec::new())
|
.map(|_i| Vec::new())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
@ -141,8 +141,7 @@ impl Ring {
|
||||||
if i_round >= node_info.capacity {
|
if i_round >= node_info.capacity {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for pos2 in *pos..q.len() {
|
for (pos2, &qv) in q.iter().enumerate().skip(*pos) {
|
||||||
let qv = q[pos2];
|
|
||||||
if partitions[qv].len() != rep {
|
if partitions[qv].len() != rep {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -181,7 +180,7 @@ impl Ring {
|
||||||
let top = (i as u16) << (16 - PARTITION_BITS);
|
let top = (i as u16) << (16 - PARTITION_BITS);
|
||||||
let mut hash = [0u8; 32];
|
let mut hash = [0u8; 32];
|
||||||
hash[0..2].copy_from_slice(&u16::to_be_bytes(top)[..]);
|
hash[0..2].copy_from_slice(&u16::to_be_bytes(top)[..]);
|
||||||
let nodes = nodes.iter().map(|(id, _info)| **id).collect::<Vec<UUID>>();
|
let nodes = nodes.iter().map(|(id, _info)| **id).collect::<Vec<Uuid>>();
|
||||||
RingEntry {
|
RingEntry {
|
||||||
location: hash.into(),
|
location: hash.into(),
|
||||||
nodes: nodes.try_into().unwrap(),
|
nodes: nodes.try_into().unwrap(),
|
||||||
|
@ -205,7 +204,7 @@ impl Ring {
|
||||||
for (i, entry) in self.ring.iter().enumerate() {
|
for (i, entry) in self.ring.iter().enumerate() {
|
||||||
ret.push((i as u16, entry.location));
|
ret.push((i as u16, entry.location));
|
||||||
}
|
}
|
||||||
if ret.len() > 0 {
|
if !ret.is_empty() {
|
||||||
assert_eq!(ret[0].1, [0u8; 32].into());
|
assert_eq!(ret[0].1, [0u8; 32].into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +213,7 @@ impl Ring {
|
||||||
|
|
||||||
// TODO rename this function as it no longer walk the ring
|
// TODO rename this function as it no longer walk the ring
|
||||||
/// Walk the ring to find the n servers in which data should be replicated
|
/// Walk the ring to find the n servers in which data should be replicated
|
||||||
pub fn walk_ring(&self, from: &Hash, n: usize) -> Vec<UUID> {
|
pub fn walk_ring(&self, from: &Hash, n: usize) -> Vec<Uuid> {
|
||||||
if self.ring.len() != 1 << PARTITION_BITS {
|
if self.ring.len() != 1 << PARTITION_BITS {
|
||||||
warn!("Ring not yet ready, read/writes will be lost!");
|
warn!("Ring not yet ready, read/writes will be lost!");
|
||||||
return vec![];
|
return vec![];
|
||||||
|
@ -234,6 +233,6 @@ impl Ring {
|
||||||
assert_eq!(partition_top & PARTITION_MASK_U16, top & PARTITION_MASK_U16);
|
assert_eq!(partition_top & PARTITION_MASK_U16, top & PARTITION_MASK_U16);
|
||||||
|
|
||||||
assert!(n <= partition.nodes.len());
|
assert!(n <= partition.nodes.len());
|
||||||
partition.nodes[..n].iter().cloned().collect::<Vec<_>>()
|
partition.nodes[..n].to_vec()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ use tokio::sync::{watch, Semaphore};
|
||||||
use garage_util::background::BackgroundRunner;
|
use garage_util::background::BackgroundRunner;
|
||||||
use garage_util::config::TlsConfig;
|
use garage_util::config::TlsConfig;
|
||||||
use garage_util::data::*;
|
use garage_util::data::*;
|
||||||
use garage_util::error::{Error, RPCError};
|
use garage_util::error::{Error, RpcError};
|
||||||
|
|
||||||
use crate::membership::Status;
|
use crate::membership::Status;
|
||||||
use crate::rpc_server::RpcMessage;
|
use crate::rpc_server::RpcMessage;
|
||||||
|
@ -70,7 +70,7 @@ pub struct RpcClient<M: RpcMessage> {
|
||||||
status: watch::Receiver<Arc<Status>>,
|
status: watch::Receiver<Arc<Status>>,
|
||||||
background: Arc<BackgroundRunner>,
|
background: Arc<BackgroundRunner>,
|
||||||
|
|
||||||
local_handler: ArcSwapOption<(UUID, LocalHandlerFn<M>)>,
|
local_handler: ArcSwapOption<(Uuid, LocalHandlerFn<M>)>,
|
||||||
|
|
||||||
rpc_addr_client: RpcAddrClient<M>,
|
rpc_addr_client: RpcAddrClient<M>,
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ impl<M: RpcMessage + 'static> RpcClient<M> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the local handler, to process RPC to this node without network usage
|
/// Set the local handler, to process RPC to this node without network usage
|
||||||
pub fn set_local_handler<F, Fut>(&self, my_id: UUID, handler: F)
|
pub fn set_local_handler<F, Fut>(&self, my_id: Uuid, handler: F)
|
||||||
where
|
where
|
||||||
F: Fn(Arc<M>) -> Fut + Send + Sync + 'static,
|
F: Fn(Arc<M>) -> Fut + Send + Sync + 'static,
|
||||||
Fut: Future<Output = Result<M, Error>> + Send + 'static,
|
Fut: Future<Output = Result<M, Error>> + Send + 'static,
|
||||||
|
@ -110,12 +110,12 @@ impl<M: RpcMessage + 'static> RpcClient<M> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make a RPC call
|
/// Make a RPC call
|
||||||
pub async fn call(&self, to: UUID, msg: M, timeout: Duration) -> Result<M, Error> {
|
pub async fn call(&self, to: Uuid, msg: M, timeout: Duration) -> Result<M, Error> {
|
||||||
self.call_arc(to, Arc::new(msg), timeout).await
|
self.call_arc(to, Arc::new(msg), timeout).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make a RPC call from a message stored in an Arc
|
/// Make a RPC call from a message stored in an Arc
|
||||||
pub async fn call_arc(&self, to: UUID, msg: Arc<M>, timeout: Duration) -> Result<M, Error> {
|
pub async fn call_arc(&self, to: Uuid, msg: Arc<M>, timeout: Duration) -> Result<M, Error> {
|
||||||
if let Some(lh) = self.local_handler.load_full() {
|
if let Some(lh) = self.local_handler.load_full() {
|
||||||
let (my_id, local_handler) = lh.as_ref();
|
let (my_id, local_handler) = lh.as_ref();
|
||||||
if to.borrow() == my_id {
|
if to.borrow() == my_id {
|
||||||
|
@ -128,7 +128,7 @@ impl<M: RpcMessage + 'static> RpcClient<M> {
|
||||||
if node_status.is_up() {
|
if node_status.is_up() {
|
||||||
node_status
|
node_status
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::from(RPCError::NodeDown(to)));
|
return Err(Error::from(RpcError::NodeDown(to)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
@ -152,7 +152,7 @@ impl<M: RpcMessage + 'static> RpcClient<M> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make a RPC call to multiple servers, returning a Vec containing each result
|
/// Make a RPC call to multiple servers, returning a Vec containing each result
|
||||||
pub async fn call_many(&self, to: &[UUID], msg: M, timeout: Duration) -> Vec<Result<M, Error>> {
|
pub async fn call_many(&self, to: &[Uuid], msg: M, timeout: Duration) -> Vec<Result<M, Error>> {
|
||||||
let msg = Arc::new(msg);
|
let msg = Arc::new(msg);
|
||||||
let mut resp_stream = to
|
let mut resp_stream = to
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -170,7 +170,7 @@ impl<M: RpcMessage + 'static> RpcClient<M> {
|
||||||
/// strategy could not be respected due to too many errors
|
/// strategy could not be respected due to too many errors
|
||||||
pub async fn try_call_many(
|
pub async fn try_call_many(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
to: &[UUID],
|
to: &[Uuid],
|
||||||
msg: M,
|
msg: M,
|
||||||
strategy: RequestStrategy,
|
strategy: RequestStrategy,
|
||||||
) -> Result<Vec<M>, Error> {
|
) -> Result<Vec<M>, Error> {
|
||||||
|
@ -222,7 +222,7 @@ impl<M: RpcMessage + 'static> RpcClient<M> {
|
||||||
Ok(results)
|
Ok(results)
|
||||||
} else {
|
} else {
|
||||||
let errors = errors.iter().map(|e| format!("{}", e)).collect::<Vec<_>>();
|
let errors = errors.iter().map(|e| format!("{}", e)).collect::<Vec<_>>();
|
||||||
Err(Error::from(RPCError::TooManyErrors(errors)))
|
Err(Error::from(RpcError::TooManyErrors(errors)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,7 +240,7 @@ impl<M: RpcMessage> RpcAddrClient<M> {
|
||||||
pub fn new(http_client: Arc<RpcHttpClient>, path: String) -> Self {
|
pub fn new(http_client: Arc<RpcHttpClient>, path: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
phantom: PhantomData::default(),
|
phantom: PhantomData::default(),
|
||||||
http_client: http_client,
|
http_client,
|
||||||
path,
|
path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -251,7 +251,7 @@ impl<M: RpcMessage> RpcAddrClient<M> {
|
||||||
to_addr: &SocketAddr,
|
to_addr: &SocketAddr,
|
||||||
msg: MB,
|
msg: MB,
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
) -> Result<Result<M, Error>, RPCError>
|
) -> Result<Result<M, Error>, RpcError>
|
||||||
where
|
where
|
||||||
MB: Borrow<M>,
|
MB: Borrow<M>,
|
||||||
{
|
{
|
||||||
|
@ -268,8 +268,8 @@ pub struct RpcHttpClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ClientMethod {
|
enum ClientMethod {
|
||||||
HTTP(Client<HttpConnector, hyper::Body>),
|
Http(Client<HttpConnector, hyper::Body>),
|
||||||
HTTPS(Client<tls_util::HttpsConnectorFixedDnsname<HttpConnector>, hyper::Body>),
|
Https(Client<tls_util::HttpsConnectorFixedDnsname<HttpConnector>, hyper::Body>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RpcHttpClient {
|
impl RpcHttpClient {
|
||||||
|
@ -294,9 +294,9 @@ impl RpcHttpClient {
|
||||||
let connector =
|
let connector =
|
||||||
tls_util::HttpsConnectorFixedDnsname::<HttpConnector>::new(config, "garage");
|
tls_util::HttpsConnectorFixedDnsname::<HttpConnector>::new(config, "garage");
|
||||||
|
|
||||||
ClientMethod::HTTPS(Client::builder().build(connector))
|
ClientMethod::Https(Client::builder().build(connector))
|
||||||
} else {
|
} else {
|
||||||
ClientMethod::HTTP(Client::new())
|
ClientMethod::Http(Client::new())
|
||||||
};
|
};
|
||||||
Ok(RpcHttpClient {
|
Ok(RpcHttpClient {
|
||||||
method,
|
method,
|
||||||
|
@ -311,14 +311,14 @@ impl RpcHttpClient {
|
||||||
to_addr: &SocketAddr,
|
to_addr: &SocketAddr,
|
||||||
msg: MB,
|
msg: MB,
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
) -> Result<Result<M, Error>, RPCError>
|
) -> Result<Result<M, Error>, RpcError>
|
||||||
where
|
where
|
||||||
MB: Borrow<M>,
|
MB: Borrow<M>,
|
||||||
M: RpcMessage,
|
M: RpcMessage,
|
||||||
{
|
{
|
||||||
let uri = match self.method {
|
let uri = match self.method {
|
||||||
ClientMethod::HTTP(_) => format!("http://{}/{}", to_addr, path),
|
ClientMethod::Http(_) => format!("http://{}/{}", to_addr, path),
|
||||||
ClientMethod::HTTPS(_) => format!("https://{}/{}", to_addr, path),
|
ClientMethod::Https(_) => format!("https://{}/{}", to_addr, path),
|
||||||
};
|
};
|
||||||
|
|
||||||
let req = Request::builder()
|
let req = Request::builder()
|
||||||
|
@ -327,8 +327,8 @@ impl RpcHttpClient {
|
||||||
.body(Body::from(rmp_to_vec_all_named(msg.borrow())?))?;
|
.body(Body::from(rmp_to_vec_all_named(msg.borrow())?))?;
|
||||||
|
|
||||||
let resp_fut = match &self.method {
|
let resp_fut = match &self.method {
|
||||||
ClientMethod::HTTP(client) => client.request(req).fuse(),
|
ClientMethod::Http(client) => client.request(req).fuse(),
|
||||||
ClientMethod::HTTPS(client) => client.request(req).fuse(),
|
ClientMethod::Https(client) => client.request(req).fuse(),
|
||||||
};
|
};
|
||||||
|
|
||||||
trace!("({}) Acquiring request_limiter slot...", path);
|
trace!("({}) Acquiring request_limiter slot...", path);
|
||||||
|
|
|
@ -57,7 +57,7 @@ where
|
||||||
trace!(
|
trace!(
|
||||||
"Request message: {}",
|
"Request message: {}",
|
||||||
serde_json::to_string(&msg)
|
serde_json::to_string(&msg)
|
||||||
.unwrap_or("<json error>".into())
|
.unwrap_or_else(|_| "<json error>".into())
|
||||||
.chars()
|
.chars()
|
||||||
.take(100)
|
.take(100)
|
||||||
.collect::<String>()
|
.collect::<String>()
|
||||||
|
@ -77,7 +77,7 @@ where
|
||||||
let rep_bytes = rmp_to_vec_all_named::<Result<M, String>>(&Err(err_str))?;
|
let rep_bytes = rmp_to_vec_all_named::<Result<M, String>>(&Err(err_str))?;
|
||||||
let mut err_response = Response::new(Body::from(rep_bytes));
|
let mut err_response = Response::new(Body::from(rep_bytes));
|
||||||
*err_response.status_mut() = match e {
|
*err_response.status_mut() = match e {
|
||||||
Error::BadRPC(_) => StatusCode::BAD_REQUEST,
|
Error::BadRpc(_) => StatusCode::BAD_REQUEST,
|
||||||
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
};
|
};
|
||||||
warn!(
|
warn!(
|
||||||
|
@ -123,7 +123,7 @@ impl RpcServer {
|
||||||
req: Request<Body>,
|
req: Request<Body>,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
) -> Result<Response<Body>, Error> {
|
) -> Result<Response<Body>, Error> {
|
||||||
if req.method() != &Method::POST {
|
if req.method() != Method::POST {
|
||||||
let mut bad_request = Response::default();
|
let mut bad_request = Response::default();
|
||||||
*bad_request.status_mut() = StatusCode::BAD_REQUEST;
|
*bad_request.status_mut() = StatusCode::BAD_REQUEST;
|
||||||
return Ok(bad_request);
|
return Ok(bad_request);
|
||||||
|
@ -201,7 +201,7 @@ impl RpcServer {
|
||||||
.get_ref()
|
.get_ref()
|
||||||
.0
|
.0
|
||||||
.peer_addr()
|
.peer_addr()
|
||||||
.unwrap_or(([0, 0, 0, 0], 0).into());
|
.unwrap_or_else(|_| ([0, 0, 0, 0], 0).into());
|
||||||
let self_arc = self_arc.clone();
|
let self_arc = self_arc.clone();
|
||||||
async move {
|
async move {
|
||||||
Ok::<_, Error>(service_fn(move |req: Request<Body>| {
|
Ok::<_, Error>(service_fn(move |req: Request<Body>| {
|
||||||
|
|
|
@ -27,7 +27,7 @@ impl From<bool> for Bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CRDT for Bool {
|
impl Crdt for Bool {
|
||||||
fn merge(&mut self, other: &Self) {
|
fn merge(&mut self, other: &Self) {
|
||||||
self.0 = self.0 || other.0;
|
self.0 = self.0 || other.0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ use garage_util::data::*;
|
||||||
/// Moreover, the relationship `≥` defined by `a ≥ b ⇔ ∃c. a = b ⊔ c` must be a partial order.
|
/// Moreover, the relationship `≥` defined by `a ≥ b ⇔ ∃c. a = b ⊔ c` must be a partial order.
|
||||||
/// This implies a few properties such as: if `a ⊔ b ≠ a`, then there is no `c` such that `(a ⊔ b) ⊔ c = a`,
|
/// This implies a few properties such as: if `a ⊔ b ≠ a`, then there is no `c` such that `(a ⊔ b) ⊔ c = a`,
|
||||||
/// as this would imply a cycle in the partial order.
|
/// as this would imply a cycle in the partial order.
|
||||||
pub trait CRDT {
|
pub trait Crdt {
|
||||||
/// Merge the two datastructures according to the CRDT rules.
|
/// Merge the two datastructures according to the CRDT rules.
|
||||||
/// `self` is modified to contain the merged CRDT value. `other` is not modified.
|
/// `self` is modified to contain the merged CRDT value. `other` is not modified.
|
||||||
///
|
///
|
||||||
|
@ -31,16 +31,16 @@ pub trait CRDT {
|
||||||
/// All types that implement `Ord` (a total order) can also implement a trivial CRDT
|
/// All types that implement `Ord` (a total order) can also implement a trivial CRDT
|
||||||
/// defined by the merge rule: `a ⊔ b = max(a, b)`. Implement this trait for your type
|
/// defined by the merge rule: `a ⊔ b = max(a, b)`. Implement this trait for your type
|
||||||
/// to enable this behavior.
|
/// to enable this behavior.
|
||||||
pub trait AutoCRDT: Ord + Clone + std::fmt::Debug {
|
pub trait AutoCrdt: Ord + Clone + std::fmt::Debug {
|
||||||
/// WARN_IF_DIFFERENT: emit a warning when values differ. Set this to true if
|
/// WARN_IF_DIFFERENT: emit a warning when values differ. Set this to true if
|
||||||
/// different values in your application should never happen. Set this to false
|
/// different values in your application should never happen. Set this to false
|
||||||
/// if you are actually relying on the semantics of `a ⊔ b = max(a, b)`.
|
/// if you are actually relying on the semantics of `a ⊔ b = max(a, b)`.
|
||||||
const WARN_IF_DIFFERENT: bool;
|
const WARN_IF_DIFFERENT: bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> CRDT for T
|
impl<T> Crdt for T
|
||||||
where
|
where
|
||||||
T: AutoCRDT,
|
T: AutoCrdt,
|
||||||
{
|
{
|
||||||
fn merge(&mut self, other: &Self) {
|
fn merge(&mut self, other: &Self) {
|
||||||
if Self::WARN_IF_DIFFERENT && self != other {
|
if Self::WARN_IF_DIFFERENT && self != other {
|
||||||
|
@ -52,22 +52,20 @@ where
|
||||||
*self = other.clone();
|
*self = other.clone();
|
||||||
}
|
}
|
||||||
warn!("Making an arbitrary choice: {:?}", self);
|
warn!("Making an arbitrary choice: {:?}", self);
|
||||||
} else {
|
} else if other > self {
|
||||||
if other > self {
|
*self = other.clone();
|
||||||
*self = other.clone();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AutoCRDT for String {
|
impl AutoCrdt for String {
|
||||||
const WARN_IF_DIFFERENT: bool = true;
|
const WARN_IF_DIFFERENT: bool = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AutoCRDT for bool {
|
impl AutoCrdt for bool {
|
||||||
const WARN_IF_DIFFERENT: bool = true;
|
const WARN_IF_DIFFERENT: bool = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AutoCRDT for FixedBytes32 {
|
impl AutoCrdt for FixedBytes32 {
|
||||||
const WARN_IF_DIFFERENT: bool = true;
|
const WARN_IF_DIFFERENT: bool = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,14 +36,14 @@ use crate::crdt::crdt::*;
|
||||||
/// This scheme is used by AWS S3 or Soundcloud and often without knowing
|
/// This scheme is used by AWS S3 or Soundcloud and often without knowing
|
||||||
/// in enterprise when reconciliating databases with ad-hoc scripts.
|
/// in enterprise when reconciliating databases with ad-hoc scripts.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct LWW<T> {
|
pub struct Lww<T> {
|
||||||
ts: u64,
|
ts: u64,
|
||||||
v: T,
|
v: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> LWW<T>
|
impl<T> Lww<T>
|
||||||
where
|
where
|
||||||
T: CRDT,
|
T: Crdt,
|
||||||
{
|
{
|
||||||
/// Creates a new CRDT
|
/// Creates a new CRDT
|
||||||
///
|
///
|
||||||
|
@ -99,9 +99,9 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> CRDT for LWW<T>
|
impl<T> Crdt for Lww<T>
|
||||||
where
|
where
|
||||||
T: Clone + CRDT,
|
T: Clone + Crdt,
|
||||||
{
|
{
|
||||||
fn merge(&mut self, other: &Self) {
|
fn merge(&mut self, other: &Self) {
|
||||||
if other.ts > self.ts {
|
if other.ts > self.ts {
|
||||||
|
|
|
@ -22,14 +22,14 @@ use crate::crdt::crdt::*;
|
||||||
/// the serialization cost `O(n)` would still have to be paid at each modification, so we are
|
/// the serialization cost `O(n)` would still have to be paid at each modification, so we are
|
||||||
/// actually not losing anything here.
|
/// actually not losing anything here.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct LWWMap<K, V> {
|
pub struct LwwMap<K, V> {
|
||||||
vals: Vec<(K, u64, V)>,
|
vals: Vec<(K, u64, V)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, V> LWWMap<K, V>
|
impl<K, V> LwwMap<K, V>
|
||||||
where
|
where
|
||||||
K: Ord,
|
K: Ord,
|
||||||
V: CRDT,
|
V: Crdt,
|
||||||
{
|
{
|
||||||
/// Create a new empty map CRDT
|
/// Create a new empty map CRDT
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
@ -94,7 +94,7 @@ where
|
||||||
/// put_my_crdt_value(a);
|
/// put_my_crdt_value(a);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn take_and_clear(&mut self) -> Self {
|
pub fn take_and_clear(&mut self) -> Self {
|
||||||
let vals = std::mem::replace(&mut self.vals, vec![]);
|
let vals = std::mem::take(&mut self.vals);
|
||||||
Self { vals }
|
Self { vals }
|
||||||
}
|
}
|
||||||
/// Removes all values from the map
|
/// Removes all values from the map
|
||||||
|
@ -113,16 +113,22 @@ where
|
||||||
pub fn items(&self) -> &[(K, u64, V)] {
|
pub fn items(&self) -> &[(K, u64, V)] {
|
||||||
&self.vals[..]
|
&self.vals[..]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of items in the map
|
/// Returns the number of items in the map
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.vals.len()
|
self.vals.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the map is empty
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.len() == 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, V> CRDT for LWWMap<K, V>
|
impl<K, V> Crdt for LwwMap<K, V>
|
||||||
where
|
where
|
||||||
K: Clone + Ord,
|
K: Clone + Ord,
|
||||||
V: Clone + CRDT,
|
V: Clone + Crdt,
|
||||||
{
|
{
|
||||||
fn merge(&mut self, other: &Self) {
|
fn merge(&mut self, other: &Self) {
|
||||||
for (k, ts2, v2) in other.vals.iter() {
|
for (k, ts2, v2) in other.vals.iter() {
|
||||||
|
@ -143,3 +149,13 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<K, V> Default for LwwMap<K, V>
|
||||||
|
where
|
||||||
|
K: Ord,
|
||||||
|
V: Crdt,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub struct Map<K, V> {
|
||||||
impl<K, V> Map<K, V>
|
impl<K, V> Map<K, V>
|
||||||
where
|
where
|
||||||
K: Clone + Ord,
|
K: Clone + Ord,
|
||||||
V: Clone + CRDT,
|
V: Clone + Crdt,
|
||||||
{
|
{
|
||||||
/// Create a new empty map CRDT
|
/// Create a new empty map CRDT
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
@ -62,12 +62,17 @@ where
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.vals.len()
|
self.vals.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the map is empty
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.len() == 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, V> CRDT for Map<K, V>
|
impl<K, V> Crdt for Map<K, V>
|
||||||
where
|
where
|
||||||
K: Clone + Ord,
|
K: Clone + Ord,
|
||||||
V: Clone + CRDT,
|
V: Clone + Crdt,
|
||||||
{
|
{
|
||||||
fn merge(&mut self, other: &Self) {
|
fn merge(&mut self, other: &Self) {
|
||||||
for (k, v2) in other.vals.iter() {
|
for (k, v2) in other.vals.iter() {
|
||||||
|
@ -82,3 +87,13 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<K, V> Default for Map<K, V>
|
||||||
|
where
|
||||||
|
K: Clone + Ord,
|
||||||
|
V: Clone + Crdt,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
//! Learn more about CRDT [on Wikipedia](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type)
|
//! Learn more about CRDT [on Wikipedia](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type)
|
||||||
|
|
||||||
mod bool;
|
mod bool;
|
||||||
|
#[allow(clippy::module_inception)]
|
||||||
mod crdt;
|
mod crdt;
|
||||||
mod lww;
|
mod lww;
|
||||||
mod lww_map;
|
mod lww_map;
|
||||||
|
|
|
@ -11,7 +11,7 @@ use garage_util::error::*;
|
||||||
|
|
||||||
use garage_rpc::membership::System;
|
use garage_rpc::membership::System;
|
||||||
|
|
||||||
use crate::crdt::CRDT;
|
use crate::crdt::Crdt;
|
||||||
use crate::replication::*;
|
use crate::replication::*;
|
||||||
use crate::schema::*;
|
use crate::schema::*;
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ where
|
||||||
|
|
||||||
if Some(&new_entry) != old_entry.as_ref() {
|
if Some(&new_entry) != old_entry.as_ref() {
|
||||||
let new_bytes = rmp_to_vec_all_named(&new_entry)
|
let new_bytes = rmp_to_vec_all_named(&new_entry)
|
||||||
.map_err(Error::RMPEncode)
|
.map_err(Error::RmpEncode)
|
||||||
.map_err(sled::transaction::ConflictableTransactionError::Abort)?;
|
.map_err(sled::transaction::ConflictableTransactionError::Abort)?;
|
||||||
let new_bytes_hash = blake2sum(&new_bytes[..]);
|
let new_bytes_hash = blake2sum(&new_bytes[..]);
|
||||||
mkl_todo.insert(tree_key.clone(), new_bytes_hash.as_slice())?;
|
mkl_todo.insert(tree_key.clone(), new_bytes_hash.as_slice())?;
|
||||||
|
|
|
@ -24,23 +24,23 @@ use crate::schema::*;
|
||||||
const TABLE_GC_BATCH_SIZE: usize = 1024;
|
const TABLE_GC_BATCH_SIZE: usize = 1024;
|
||||||
const TABLE_GC_RPC_TIMEOUT: Duration = Duration::from_secs(30);
|
const TABLE_GC_RPC_TIMEOUT: Duration = Duration::from_secs(30);
|
||||||
|
|
||||||
pub struct TableGC<F: TableSchema, R: TableReplication> {
|
pub struct TableGc<F: TableSchema, R: TableReplication> {
|
||||||
system: Arc<System>,
|
system: Arc<System>,
|
||||||
data: Arc<TableData<F, R>>,
|
data: Arc<TableData<F, R>>,
|
||||||
|
|
||||||
rpc_client: Arc<RpcClient<GcRPC>>,
|
rpc_client: Arc<RpcClient<GcRpc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
enum GcRPC {
|
enum GcRpc {
|
||||||
Update(Vec<ByteBuf>),
|
Update(Vec<ByteBuf>),
|
||||||
DeleteIfEqualHash(Vec<(ByteBuf, Hash)>),
|
DeleteIfEqualHash(Vec<(ByteBuf, Hash)>),
|
||||||
Ok,
|
Ok,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RpcMessage for GcRPC {}
|
impl RpcMessage for GcRpc {}
|
||||||
|
|
||||||
impl<F, R> TableGC<F, R>
|
impl<F, R> TableGc<F, R>
|
||||||
where
|
where
|
||||||
F: TableSchema + 'static,
|
F: TableSchema + 'static,
|
||||||
R: TableReplication + 'static,
|
R: TableReplication + 'static,
|
||||||
|
@ -51,7 +51,7 @@ where
|
||||||
rpc_server: &mut RpcServer,
|
rpc_server: &mut RpcServer,
|
||||||
) -> Arc<Self> {
|
) -> Arc<Self> {
|
||||||
let rpc_path = format!("table_{}/gc", data.name);
|
let rpc_path = format!("table_{}/gc", data.name);
|
||||||
let rpc_client = system.rpc_client::<GcRPC>(&rpc_path);
|
let rpc_client = system.rpc_client::<GcRpc>(&rpc_path);
|
||||||
|
|
||||||
let gc = Arc::new(Self {
|
let gc = Arc::new(Self {
|
||||||
system: system.clone(),
|
system: system.clone(),
|
||||||
|
@ -85,8 +85,8 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
select! {
|
select! {
|
||||||
_ = tokio::time::sleep(Duration::from_secs(10)).fuse() => (),
|
_ = tokio::time::sleep(Duration::from_secs(10)).fuse() => {},
|
||||||
_ = must_exit.changed().fuse() => (),
|
_ = must_exit.changed().fuse() => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,7 +120,7 @@ where
|
||||||
self.todo_remove_if_equal(&k[..], vhash)?;
|
self.todo_remove_if_equal(&k[..], vhash)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if entries.len() == 0 {
|
if entries.is_empty() {
|
||||||
// Nothing to do in this iteration
|
// Nothing to do in this iteration
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
@ -168,7 +168,7 @@ where
|
||||||
|
|
||||||
async fn try_send_and_delete(
|
async fn try_send_and_delete(
|
||||||
&self,
|
&self,
|
||||||
nodes: Vec<UUID>,
|
nodes: Vec<Uuid>,
|
||||||
items: Vec<(ByteBuf, Hash, ByteBuf)>,
|
items: Vec<(ByteBuf, Hash, ByteBuf)>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let n_items = items.len();
|
let n_items = items.len();
|
||||||
|
@ -183,7 +183,7 @@ where
|
||||||
self.rpc_client
|
self.rpc_client
|
||||||
.try_call_many(
|
.try_call_many(
|
||||||
&nodes[..],
|
&nodes[..],
|
||||||
GcRPC::Update(updates),
|
GcRpc::Update(updates),
|
||||||
RequestStrategy::with_quorum(nodes.len()).with_timeout(TABLE_GC_RPC_TIMEOUT),
|
RequestStrategy::with_quorum(nodes.len()).with_timeout(TABLE_GC_RPC_TIMEOUT),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -196,7 +196,7 @@ where
|
||||||
self.rpc_client
|
self.rpc_client
|
||||||
.try_call_many(
|
.try_call_many(
|
||||||
&nodes[..],
|
&nodes[..],
|
||||||
GcRPC::DeleteIfEqualHash(deletes.clone()),
|
GcRpc::DeleteIfEqualHash(deletes.clone()),
|
||||||
RequestStrategy::with_quorum(nodes.len()).with_timeout(TABLE_GC_RPC_TIMEOUT),
|
RequestStrategy::with_quorum(nodes.len()).with_timeout(TABLE_GC_RPC_TIMEOUT),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -221,7 +221,7 @@ where
|
||||||
|
|
||||||
fn register_handler(self: &Arc<Self>, rpc_server: &mut RpcServer, path: String) {
|
fn register_handler(self: &Arc<Self>, rpc_server: &mut RpcServer, path: String) {
|
||||||
let self2 = self.clone();
|
let self2 = self.clone();
|
||||||
rpc_server.add_handler::<GcRPC, _, _>(path, move |msg, _addr| {
|
rpc_server.add_handler::<GcRpc, _, _>(path, move |msg, _addr| {
|
||||||
let self2 = self2.clone();
|
let self2 = self2.clone();
|
||||||
async move { self2.handle_rpc(&msg).await }
|
async move { self2.handle_rpc(&msg).await }
|
||||||
});
|
});
|
||||||
|
@ -234,20 +234,20 @@ where
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_rpc(self: &Arc<Self>, message: &GcRPC) -> Result<GcRPC, Error> {
|
async fn handle_rpc(self: &Arc<Self>, message: &GcRpc) -> Result<GcRpc, Error> {
|
||||||
match message {
|
match message {
|
||||||
GcRPC::Update(items) => {
|
GcRpc::Update(items) => {
|
||||||
self.data.update_many(items)?;
|
self.data.update_many(items)?;
|
||||||
Ok(GcRPC::Ok)
|
Ok(GcRpc::Ok)
|
||||||
}
|
}
|
||||||
GcRPC::DeleteIfEqualHash(items) => {
|
GcRpc::DeleteIfEqualHash(items) => {
|
||||||
for (key, vhash) in items.iter() {
|
for (key, vhash) in items.iter() {
|
||||||
self.data.delete_if_equal_hash(&key[..], *vhash)?;
|
self.data.delete_if_equal_hash(&key[..], *vhash)?;
|
||||||
self.todo_remove_if_equal(&key[..], *vhash)?;
|
self.todo_remove_if_equal(&key[..], *vhash)?;
|
||||||
}
|
}
|
||||||
Ok(GcRPC::Ok)
|
Ok(GcRpc::Ok)
|
||||||
}
|
}
|
||||||
_ => Err(Error::Message(format!("Unexpected GC RPC"))),
|
_ => Err(Error::Message("Unexpected GC RPC".to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#![recursion_limit = "1024"]
|
#![recursion_limit = "1024"]
|
||||||
|
#![allow(clippy::comparison_chain)]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
|
@ -111,8 +111,8 @@ where
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
select! {
|
select! {
|
||||||
_ = self.data.merkle_todo_notify.notified().fuse() => (),
|
_ = self.data.merkle_todo_notify.notified().fuse() => {},
|
||||||
_ = must_exit.changed().fuse() => (),
|
_ = must_exit.changed().fuse() => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,10 +121,10 @@ where
|
||||||
fn update_item(&self, k: &[u8], vhash_by: &[u8]) -> Result<(), Error> {
|
fn update_item(&self, k: &[u8], vhash_by: &[u8]) -> Result<(), Error> {
|
||||||
let khash = blake2sum(k);
|
let khash = blake2sum(k);
|
||||||
|
|
||||||
let new_vhash = if vhash_by.len() == 0 {
|
let new_vhash = if vhash_by.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(Hash::try_from(&vhash_by[..]).unwrap())
|
Some(Hash::try_from(vhash_by).unwrap())
|
||||||
};
|
};
|
||||||
|
|
||||||
let key = MerkleNodeKey {
|
let key = MerkleNodeKey {
|
||||||
|
@ -168,14 +168,7 @@ where
|
||||||
// This update is an Option<_>, so that it is None if the update is a no-op
|
// This update is an Option<_>, so that it is None if the update is a no-op
|
||||||
// and we can thus skip recalculating and re-storing everything
|
// and we can thus skip recalculating and re-storing everything
|
||||||
let mutate = match self.read_node_txn(tx, &key)? {
|
let mutate = match self.read_node_txn(tx, &key)? {
|
||||||
MerkleNode::Empty => {
|
MerkleNode::Empty => new_vhash.map(|vhv| MerkleNode::Leaf(k.to_vec(), vhv)),
|
||||||
if let Some(vhv) = new_vhash {
|
|
||||||
Some(MerkleNode::Leaf(k.to_vec(), vhv))
|
|
||||||
} else {
|
|
||||||
// Nothing to do, keep empty node
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MerkleNode::Intermediate(mut children) => {
|
MerkleNode::Intermediate(mut children) => {
|
||||||
let key2 = key.next_key(khash);
|
let key2 = key.next_key(khash);
|
||||||
if let Some(subhash) = self.update_item_rec(tx, k, khash, &key2, new_vhash)? {
|
if let Some(subhash) = self.update_item_rec(tx, k, khash, &key2, new_vhash)? {
|
||||||
|
@ -186,7 +179,7 @@ where
|
||||||
intermediate_set_child(&mut children, key2.prefix[i], subhash);
|
intermediate_set_child(&mut children, key2.prefix[i], subhash);
|
||||||
}
|
}
|
||||||
|
|
||||||
if children.len() == 0 {
|
if children.is_empty() {
|
||||||
// should not happen
|
// should not happen
|
||||||
warn!(
|
warn!(
|
||||||
"({}) Replacing intermediate node with empty node, should not happen.",
|
"({}) Replacing intermediate node with empty node, should not happen.",
|
||||||
|
|
|
@ -19,14 +19,14 @@ pub struct TableFullReplication {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableReplication for TableFullReplication {
|
impl TableReplication for TableFullReplication {
|
||||||
fn read_nodes(&self, _hash: &Hash) -> Vec<UUID> {
|
fn read_nodes(&self, _hash: &Hash) -> Vec<Uuid> {
|
||||||
vec![self.system.id]
|
vec![self.system.id]
|
||||||
}
|
}
|
||||||
fn read_quorum(&self) -> usize {
|
fn read_quorum(&self) -> usize {
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_nodes(&self, _hash: &Hash) -> Vec<UUID> {
|
fn write_nodes(&self, _hash: &Hash) -> Vec<Uuid> {
|
||||||
let ring = self.system.ring.borrow();
|
let ring = self.system.ring.borrow();
|
||||||
ring.config.members.keys().cloned().collect::<Vec<_>>()
|
ring.config.members.keys().cloned().collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,12 @@ pub trait TableReplication: Send + Sync {
|
||||||
// To understand various replication methods
|
// To understand various replication methods
|
||||||
|
|
||||||
/// Which nodes to send read requests to
|
/// Which nodes to send read requests to
|
||||||
fn read_nodes(&self, hash: &Hash) -> Vec<UUID>;
|
fn read_nodes(&self, hash: &Hash) -> Vec<Uuid>;
|
||||||
/// Responses needed to consider a read succesfull
|
/// Responses needed to consider a read succesfull
|
||||||
fn read_quorum(&self) -> usize;
|
fn read_quorum(&self) -> usize;
|
||||||
|
|
||||||
/// Which nodes to send writes to
|
/// Which nodes to send writes to
|
||||||
fn write_nodes(&self, hash: &Hash) -> Vec<UUID>;
|
fn write_nodes(&self, hash: &Hash) -> Vec<Uuid>;
|
||||||
/// Responses needed to consider a write succesfull
|
/// Responses needed to consider a write succesfull
|
||||||
fn write_quorum(&self) -> usize;
|
fn write_quorum(&self) -> usize;
|
||||||
fn max_write_errors(&self) -> usize;
|
fn max_write_errors(&self) -> usize;
|
||||||
|
|
|
@ -25,7 +25,7 @@ pub struct TableShardedReplication {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableReplication for TableShardedReplication {
|
impl TableReplication for TableShardedReplication {
|
||||||
fn read_nodes(&self, hash: &Hash) -> Vec<UUID> {
|
fn read_nodes(&self, hash: &Hash) -> Vec<Uuid> {
|
||||||
let ring = self.system.ring.borrow().clone();
|
let ring = self.system.ring.borrow().clone();
|
||||||
ring.walk_ring(&hash, self.replication_factor)
|
ring.walk_ring(&hash, self.replication_factor)
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ impl TableReplication for TableShardedReplication {
|
||||||
self.read_quorum
|
self.read_quorum
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_nodes(&self, hash: &Hash) -> Vec<UUID> {
|
fn write_nodes(&self, hash: &Hash) -> Vec<Uuid> {
|
||||||
let ring = self.system.ring.borrow();
|
let ring = self.system.ring.borrow();
|
||||||
ring.walk_ring(&hash, self.replication_factor)
|
ring.walk_ring(&hash, self.replication_factor)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use garage_util::data::*;
|
use garage_util::data::*;
|
||||||
|
|
||||||
use crate::crdt::CRDT;
|
use crate::crdt::Crdt;
|
||||||
|
|
||||||
/// Trait for field used to partition data
|
/// Trait for field used to partition data
|
||||||
pub trait PartitionKey {
|
pub trait PartitionKey {
|
||||||
|
@ -18,7 +18,7 @@ impl PartitionKey for String {
|
||||||
|
|
||||||
impl PartitionKey for Hash {
|
impl PartitionKey for Hash {
|
||||||
fn hash(&self) -> Hash {
|
fn hash(&self) -> Hash {
|
||||||
self.clone()
|
*self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ impl SortKey for Hash {
|
||||||
|
|
||||||
/// Trait for an entry in a table. It must be sortable and partitionnable.
|
/// Trait for an entry in a table. It must be sortable and partitionnable.
|
||||||
pub trait Entry<P: PartitionKey, S: SortKey>:
|
pub trait Entry<P: PartitionKey, S: SortKey>:
|
||||||
CRDT + PartialEq + Clone + Serialize + for<'de> Deserialize<'de> + Send + Sync
|
Crdt + PartialEq + Clone + Serialize + for<'de> Deserialize<'de> + Send + Sync
|
||||||
{
|
{
|
||||||
/// Get the key used to partition
|
/// Get the key used to partition
|
||||||
fn partition_key(&self) -> &P;
|
fn partition_key(&self) -> &P;
|
||||||
|
|
|
@ -34,11 +34,11 @@ pub struct TableSyncer<F: TableSchema, R: TableReplication> {
|
||||||
merkle: Arc<MerkleUpdater<F, R>>,
|
merkle: Arc<MerkleUpdater<F, R>>,
|
||||||
|
|
||||||
todo: Mutex<SyncTodo>,
|
todo: Mutex<SyncTodo>,
|
||||||
rpc_client: Arc<RpcClient<SyncRPC>>,
|
rpc_client: Arc<RpcClient<SyncRpc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub(crate) enum SyncRPC {
|
pub(crate) enum SyncRpc {
|
||||||
RootCkHash(Partition, Hash),
|
RootCkHash(Partition, Hash),
|
||||||
RootCkDifferent(bool),
|
RootCkDifferent(bool),
|
||||||
GetNode(MerkleNodeKey),
|
GetNode(MerkleNodeKey),
|
||||||
|
@ -47,7 +47,7 @@ pub(crate) enum SyncRPC {
|
||||||
Ok,
|
Ok,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RpcMessage for SyncRPC {}
|
impl RpcMessage for SyncRpc {}
|
||||||
|
|
||||||
struct SyncTodo {
|
struct SyncTodo {
|
||||||
todo: Vec<TodoPartition>,
|
todo: Vec<TodoPartition>,
|
||||||
|
@ -75,7 +75,7 @@ where
|
||||||
rpc_server: &mut RpcServer,
|
rpc_server: &mut RpcServer,
|
||||||
) -> Arc<Self> {
|
) -> Arc<Self> {
|
||||||
let rpc_path = format!("table_{}/sync", data.name);
|
let rpc_path = format!("table_{}/sync", data.name);
|
||||||
let rpc_client = system.rpc_client::<SyncRPC>(&rpc_path);
|
let rpc_client = system.rpc_client::<SyncRpc>(&rpc_path);
|
||||||
|
|
||||||
let todo = SyncTodo { todo: vec![] };
|
let todo = SyncTodo { todo: vec![] };
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ where
|
||||||
|
|
||||||
fn register_handler(self: &Arc<Self>, rpc_server: &mut RpcServer, path: String) {
|
fn register_handler(self: &Arc<Self>, rpc_server: &mut RpcServer, path: String) {
|
||||||
let self2 = self.clone();
|
let self2 = self.clone();
|
||||||
rpc_server.add_handler::<SyncRPC, _, _>(path, move |msg, _addr| {
|
rpc_server.add_handler::<SyncRpc, _, _>(path, move |msg, _addr| {
|
||||||
let self2 = self2.clone();
|
let self2 = self2.clone();
|
||||||
async move { self2.handle_rpc(&msg).await }
|
async move { self2.handle_rpc(&msg).await }
|
||||||
});
|
});
|
||||||
|
@ -150,14 +150,12 @@ where
|
||||||
if let Some(busy) = busy_opt {
|
if let Some(busy) = busy_opt {
|
||||||
if busy {
|
if busy {
|
||||||
nothing_to_do_since = None;
|
nothing_to_do_since = None;
|
||||||
} else {
|
} else if nothing_to_do_since.is_none() {
|
||||||
if nothing_to_do_since.is_none() {
|
nothing_to_do_since = Some(Instant::now());
|
||||||
nothing_to_do_since = Some(Instant::now());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = must_exit.changed().fuse() => (),
|
_ = must_exit.changed().fuse() => {},
|
||||||
_ = tokio::time::sleep(Duration::from_secs(1)).fuse() => {
|
_ = tokio::time::sleep(Duration::from_secs(1)).fuse() => {
|
||||||
if nothing_to_do_since.map(|t| Instant::now() - t >= ANTI_ENTROPY_INTERVAL).unwrap_or(false) {
|
if nothing_to_do_since.map(|t| Instant::now() - t >= ANTI_ENTROPY_INTERVAL).unwrap_or(false) {
|
||||||
nothing_to_do_since = None;
|
nothing_to_do_since = None;
|
||||||
|
@ -277,7 +275,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if items.len() > 0 {
|
if !items.is_empty() {
|
||||||
let nodes = self
|
let nodes = self
|
||||||
.data
|
.data
|
||||||
.replication
|
.replication
|
||||||
|
@ -292,9 +290,10 @@ where
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if nodes.len() < self.data.replication.write_quorum() {
|
if nodes.len() < self.data.replication.write_quorum() {
|
||||||
return Err(Error::Message(format!(
|
return Err(Error::Message(
|
||||||
"Not offloading as we don't have a quorum of nodes to write to."
|
"Not offloading as we don't have a quorum of nodes to write to."
|
||||||
)));
|
.to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
counter += 1;
|
counter += 1;
|
||||||
|
@ -317,15 +316,15 @@ where
|
||||||
|
|
||||||
async fn offload_items(
|
async fn offload_items(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
items: &Vec<(Vec<u8>, Arc<ByteBuf>)>,
|
items: &[(Vec<u8>, Arc<ByteBuf>)],
|
||||||
nodes: &[UUID],
|
nodes: &[Uuid],
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let values = items.iter().map(|(_k, v)| v.clone()).collect::<Vec<_>>();
|
let values = items.iter().map(|(_k, v)| v.clone()).collect::<Vec<_>>();
|
||||||
|
|
||||||
self.rpc_client
|
self.rpc_client
|
||||||
.try_call_many(
|
.try_call_many(
|
||||||
&nodes[..],
|
nodes,
|
||||||
SyncRPC::Items(values),
|
SyncRpc::Items(values),
|
||||||
RequestStrategy::with_quorum(nodes.len()).with_timeout(TABLE_SYNC_RPC_TIMEOUT),
|
RequestStrategy::with_quorum(nodes.len()).with_timeout(TABLE_SYNC_RPC_TIMEOUT),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -363,7 +362,7 @@ where
|
||||||
async fn do_sync_with(
|
async fn do_sync_with(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
partition: TodoPartition,
|
partition: TodoPartition,
|
||||||
who: UUID,
|
who: Uuid,
|
||||||
must_exit: watch::Receiver<bool>,
|
must_exit: watch::Receiver<bool>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let (root_ck_key, root_ck) = self.get_root_ck(partition.partition)?;
|
let (root_ck_key, root_ck) = self.get_root_ck(partition.partition)?;
|
||||||
|
@ -382,20 +381,20 @@ where
|
||||||
.rpc_client
|
.rpc_client
|
||||||
.call(
|
.call(
|
||||||
who,
|
who,
|
||||||
SyncRPC::RootCkHash(partition.partition, root_ck_hash),
|
SyncRpc::RootCkHash(partition.partition, root_ck_hash),
|
||||||
TABLE_SYNC_RPC_TIMEOUT,
|
TABLE_SYNC_RPC_TIMEOUT,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut todo = match root_resp {
|
let mut todo = match root_resp {
|
||||||
SyncRPC::RootCkDifferent(false) => {
|
SyncRpc::RootCkDifferent(false) => {
|
||||||
debug!(
|
debug!(
|
||||||
"({}) Sync {:?} with {:?}: no difference",
|
"({}) Sync {:?} with {:?}: no difference",
|
||||||
self.data.name, partition, who
|
self.data.name, partition, who
|
||||||
);
|
);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
SyncRPC::RootCkDifferent(true) => VecDeque::from(vec![root_ck_key]),
|
SyncRpc::RootCkDifferent(true) => VecDeque::from(vec![root_ck_key]),
|
||||||
x => {
|
x => {
|
||||||
return Err(Error::Message(format!(
|
return Err(Error::Message(format!(
|
||||||
"Invalid respone to RootCkHash RPC: {}",
|
"Invalid respone to RootCkHash RPC: {}",
|
||||||
|
@ -432,10 +431,10 @@ where
|
||||||
// and compare it with local node
|
// and compare it with local node
|
||||||
let remote_node = match self
|
let remote_node = match self
|
||||||
.rpc_client
|
.rpc_client
|
||||||
.call(who, SyncRPC::GetNode(key.clone()), TABLE_SYNC_RPC_TIMEOUT)
|
.call(who, SyncRpc::GetNode(key.clone()), TABLE_SYNC_RPC_TIMEOUT)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
SyncRPC::Node(_, node) => node,
|
SyncRpc::Node(_, node) => node,
|
||||||
x => {
|
x => {
|
||||||
return Err(Error::Message(format!(
|
return Err(Error::Message(format!(
|
||||||
"Invalid respone to GetNode RPC: {}",
|
"Invalid respone to GetNode RPC: {}",
|
||||||
|
@ -467,7 +466,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
if todo_items.len() >= 256 {
|
if todo_items.len() >= 256 {
|
||||||
self.send_items(who, std::mem::replace(&mut todo_items, vec![]))
|
self.send_items(who, std::mem::take(&mut todo_items))
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -479,7 +478,7 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_items(&self, who: UUID, item_value_list: Vec<Vec<u8>>) -> Result<(), Error> {
|
async fn send_items(&self, who: Uuid, item_value_list: Vec<Vec<u8>>) -> Result<(), Error> {
|
||||||
info!(
|
info!(
|
||||||
"({}) Sending {} items to {:?}",
|
"({}) Sending {} items to {:?}",
|
||||||
self.data.name,
|
self.data.name,
|
||||||
|
@ -494,9 +493,9 @@ where
|
||||||
|
|
||||||
let rpc_resp = self
|
let rpc_resp = self
|
||||||
.rpc_client
|
.rpc_client
|
||||||
.call(who, SyncRPC::Items(values), TABLE_SYNC_RPC_TIMEOUT)
|
.call(who, SyncRpc::Items(values), TABLE_SYNC_RPC_TIMEOUT)
|
||||||
.await?;
|
.await?;
|
||||||
if let SyncRPC::Ok = rpc_resp {
|
if let SyncRpc::Ok = rpc_resp {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(Error::Message(format!(
|
Err(Error::Message(format!(
|
||||||
|
@ -508,22 +507,22 @@ where
|
||||||
|
|
||||||
// ======= SYNCHRONIZATION PROCEDURE -- RECEIVER SIDE ======
|
// ======= SYNCHRONIZATION PROCEDURE -- RECEIVER SIDE ======
|
||||||
|
|
||||||
async fn handle_rpc(self: &Arc<Self>, message: &SyncRPC) -> Result<SyncRPC, Error> {
|
async fn handle_rpc(self: &Arc<Self>, message: &SyncRpc) -> Result<SyncRpc, Error> {
|
||||||
match message {
|
match message {
|
||||||
SyncRPC::RootCkHash(range, h) => {
|
SyncRpc::RootCkHash(range, h) => {
|
||||||
let (_root_ck_key, root_ck) = self.get_root_ck(*range)?;
|
let (_root_ck_key, root_ck) = self.get_root_ck(*range)?;
|
||||||
let hash = hash_of::<MerkleNode>(&root_ck)?;
|
let hash = hash_of::<MerkleNode>(&root_ck)?;
|
||||||
Ok(SyncRPC::RootCkDifferent(hash != *h))
|
Ok(SyncRpc::RootCkDifferent(hash != *h))
|
||||||
}
|
}
|
||||||
SyncRPC::GetNode(k) => {
|
SyncRpc::GetNode(k) => {
|
||||||
let node = self.merkle.read_node(&k)?;
|
let node = self.merkle.read_node(&k)?;
|
||||||
Ok(SyncRPC::Node(k.clone(), node))
|
Ok(SyncRpc::Node(k.clone(), node))
|
||||||
}
|
}
|
||||||
SyncRPC::Items(items) => {
|
SyncRpc::Items(items) => {
|
||||||
self.data.update_many(items)?;
|
self.data.update_many(items)?;
|
||||||
Ok(SyncRPC::Ok)
|
Ok(SyncRpc::Ok)
|
||||||
}
|
}
|
||||||
_ => Err(Error::Message(format!("Unexpected sync RPC"))),
|
_ => Err(Error::Message("Unexpected sync RPC".to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use garage_rpc::membership::System;
|
||||||
use garage_rpc::rpc_client::*;
|
use garage_rpc::rpc_client::*;
|
||||||
use garage_rpc::rpc_server::*;
|
use garage_rpc::rpc_server::*;
|
||||||
|
|
||||||
use crate::crdt::CRDT;
|
use crate::crdt::Crdt;
|
||||||
use crate::data::*;
|
use crate::data::*;
|
||||||
use crate::gc::*;
|
use crate::gc::*;
|
||||||
use crate::merkle::*;
|
use crate::merkle::*;
|
||||||
|
@ -28,11 +28,11 @@ pub struct Table<F: TableSchema, R: TableReplication> {
|
||||||
pub data: Arc<TableData<F, R>>,
|
pub data: Arc<TableData<F, R>>,
|
||||||
pub merkle_updater: Arc<MerkleUpdater<F, R>>,
|
pub merkle_updater: Arc<MerkleUpdater<F, R>>,
|
||||||
pub syncer: Arc<TableSyncer<F, R>>,
|
pub syncer: Arc<TableSyncer<F, R>>,
|
||||||
rpc_client: Arc<RpcClient<TableRPC<F>>>,
|
rpc_client: Arc<RpcClient<TableRpc<F>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub(crate) enum TableRPC<F: TableSchema> {
|
pub(crate) enum TableRpc<F: TableSchema> {
|
||||||
Ok,
|
Ok,
|
||||||
|
|
||||||
ReadEntry(F::P, F::S),
|
ReadEntry(F::P, F::S),
|
||||||
|
@ -44,7 +44,7 @@ pub(crate) enum TableRPC<F: TableSchema> {
|
||||||
Update(Vec<Arc<ByteBuf>>),
|
Update(Vec<Arc<ByteBuf>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F: TableSchema> RpcMessage for TableRPC<F> {}
|
impl<F: TableSchema> RpcMessage for TableRpc<F> {}
|
||||||
|
|
||||||
impl<F, R> Table<F, R>
|
impl<F, R> Table<F, R>
|
||||||
where
|
where
|
||||||
|
@ -62,7 +62,7 @@ where
|
||||||
rpc_server: &mut RpcServer,
|
rpc_server: &mut RpcServer,
|
||||||
) -> Arc<Self> {
|
) -> Arc<Self> {
|
||||||
let rpc_path = format!("table_{}", name);
|
let rpc_path = format!("table_{}", name);
|
||||||
let rpc_client = system.rpc_client::<TableRPC<F>>(&rpc_path);
|
let rpc_client = system.rpc_client::<TableRpc<F>>(&rpc_path);
|
||||||
|
|
||||||
let data = TableData::new(system.clone(), name, instance, replication, db);
|
let data = TableData::new(system.clone(), name, instance, replication, db);
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ where
|
||||||
merkle_updater.clone(),
|
merkle_updater.clone(),
|
||||||
rpc_server,
|
rpc_server,
|
||||||
);
|
);
|
||||||
TableGC::launch(system.clone(), data.clone(), rpc_server);
|
TableGc::launch(system.clone(), data.clone(), rpc_server);
|
||||||
|
|
||||||
let table = Arc::new(Self {
|
let table = Arc::new(Self {
|
||||||
system,
|
system,
|
||||||
|
@ -95,7 +95,7 @@ where
|
||||||
//eprintln!("insert who: {:?}", who);
|
//eprintln!("insert who: {:?}", who);
|
||||||
|
|
||||||
let e_enc = Arc::new(ByteBuf::from(rmp_to_vec_all_named(e)?));
|
let e_enc = Arc::new(ByteBuf::from(rmp_to_vec_all_named(e)?));
|
||||||
let rpc = TableRPC::<F>::Update(vec![e_enc]);
|
let rpc = TableRpc::<F>::Update(vec![e_enc]);
|
||||||
|
|
||||||
self.rpc_client
|
self.rpc_client
|
||||||
.try_call_many(
|
.try_call_many(
|
||||||
|
@ -109,22 +109,19 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn insert_many(&self, entries: &[F::E]) -> Result<(), Error> {
|
pub async fn insert_many(&self, entries: &[F::E]) -> Result<(), Error> {
|
||||||
let mut call_list = HashMap::new();
|
let mut call_list: HashMap<_, Vec<_>> = HashMap::new();
|
||||||
|
|
||||||
for entry in entries.iter() {
|
for entry in entries.iter() {
|
||||||
let hash = entry.partition_key().hash();
|
let hash = entry.partition_key().hash();
|
||||||
let who = self.data.replication.write_nodes(&hash);
|
let who = self.data.replication.write_nodes(&hash);
|
||||||
let e_enc = Arc::new(ByteBuf::from(rmp_to_vec_all_named(entry)?));
|
let e_enc = Arc::new(ByteBuf::from(rmp_to_vec_all_named(entry)?));
|
||||||
for node in who {
|
for node in who {
|
||||||
if !call_list.contains_key(&node) {
|
call_list.entry(node).or_default().push(e_enc.clone());
|
||||||
call_list.insert(node, vec![]);
|
|
||||||
}
|
|
||||||
call_list.get_mut(&node).unwrap().push(e_enc.clone());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let call_futures = call_list.drain().map(|(node, entries)| async move {
|
let call_futures = call_list.drain().map(|(node, entries)| async move {
|
||||||
let rpc = TableRPC::<F>::Update(entries);
|
let rpc = TableRpc::<F>::Update(entries);
|
||||||
|
|
||||||
let resp = self.rpc_client.call(node, rpc, TABLE_RPC_TIMEOUT).await?;
|
let resp = self.rpc_client.call(node, rpc, TABLE_RPC_TIMEOUT).await?;
|
||||||
Ok::<_, Error>((node, resp))
|
Ok::<_, Error>((node, resp))
|
||||||
|
@ -153,7 +150,7 @@ where
|
||||||
let who = self.data.replication.read_nodes(&hash);
|
let who = self.data.replication.read_nodes(&hash);
|
||||||
//eprintln!("get who: {:?}", who);
|
//eprintln!("get who: {:?}", who);
|
||||||
|
|
||||||
let rpc = TableRPC::<F>::ReadEntry(partition_key.clone(), sort_key.clone());
|
let rpc = TableRpc::<F>::ReadEntry(partition_key.clone(), sort_key.clone());
|
||||||
let resps = self
|
let resps = self
|
||||||
.rpc_client
|
.rpc_client
|
||||||
.try_call_many(
|
.try_call_many(
|
||||||
|
@ -168,7 +165,7 @@ where
|
||||||
let mut ret = None;
|
let mut ret = None;
|
||||||
let mut not_all_same = false;
|
let mut not_all_same = false;
|
||||||
for resp in resps {
|
for resp in resps {
|
||||||
if let TableRPC::ReadEntryResponse(value) = resp {
|
if let TableRpc::ReadEntryResponse(value) = resp {
|
||||||
if let Some(v_bytes) = value {
|
if let Some(v_bytes) = value {
|
||||||
let v = self.data.decode_entry(v_bytes.as_slice())?;
|
let v = self.data.decode_entry(v_bytes.as_slice())?;
|
||||||
ret = match ret {
|
ret = match ret {
|
||||||
|
@ -183,7 +180,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::Message(format!("Invalid return value to read")));
|
return Err(Error::Message("Invalid return value to read".to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(ret_entry) = &ret {
|
if let Some(ret_entry) = &ret {
|
||||||
|
@ -208,7 +205,7 @@ where
|
||||||
let hash = partition_key.hash();
|
let hash = partition_key.hash();
|
||||||
let who = self.data.replication.read_nodes(&hash);
|
let who = self.data.replication.read_nodes(&hash);
|
||||||
|
|
||||||
let rpc = TableRPC::<F>::ReadRange(partition_key.clone(), begin_sort_key, filter, limit);
|
let rpc = TableRpc::<F>::ReadRange(partition_key.clone(), begin_sort_key, filter, limit);
|
||||||
|
|
||||||
let resps = self
|
let resps = self
|
||||||
.rpc_client
|
.rpc_client
|
||||||
|
@ -224,7 +221,7 @@ where
|
||||||
let mut ret = BTreeMap::new();
|
let mut ret = BTreeMap::new();
|
||||||
let mut to_repair = BTreeMap::new();
|
let mut to_repair = BTreeMap::new();
|
||||||
for resp in resps {
|
for resp in resps {
|
||||||
if let TableRPC::Update(entries) = resp {
|
if let TableRpc::Update(entries) = resp {
|
||||||
for entry_bytes in entries.iter() {
|
for entry_bytes in entries.iter() {
|
||||||
let entry = self.data.decode_entry(entry_bytes.as_slice())?;
|
let entry = self.data.decode_entry(entry_bytes.as_slice())?;
|
||||||
let entry_key = self.data.tree_key(entry.partition_key(), entry.sort_key());
|
let entry_key = self.data.tree_key(entry.partition_key(), entry.sort_key());
|
||||||
|
@ -264,12 +261,12 @@ where
|
||||||
|
|
||||||
// =============== UTILITY FUNCTION FOR CLIENT OPERATIONS ===============
|
// =============== UTILITY FUNCTION FOR CLIENT OPERATIONS ===============
|
||||||
|
|
||||||
async fn repair_on_read(&self, who: &[UUID], what: F::E) -> Result<(), Error> {
|
async fn repair_on_read(&self, who: &[Uuid], what: F::E) -> Result<(), Error> {
|
||||||
let what_enc = Arc::new(ByteBuf::from(rmp_to_vec_all_named(&what)?));
|
let what_enc = Arc::new(ByteBuf::from(rmp_to_vec_all_named(&what)?));
|
||||||
self.rpc_client
|
self.rpc_client
|
||||||
.try_call_many(
|
.try_call_many(
|
||||||
&who[..],
|
who,
|
||||||
TableRPC::<F>::Update(vec![what_enc]),
|
TableRpc::<F>::Update(vec![what_enc]),
|
||||||
RequestStrategy::with_quorum(who.len()).with_timeout(TABLE_RPC_TIMEOUT),
|
RequestStrategy::with_quorum(who.len()).with_timeout(TABLE_RPC_TIMEOUT),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -280,7 +277,7 @@ where
|
||||||
|
|
||||||
fn register_handler(self: Arc<Self>, rpc_server: &mut RpcServer, path: String) {
|
fn register_handler(self: Arc<Self>, rpc_server: &mut RpcServer, path: String) {
|
||||||
let self2 = self.clone();
|
let self2 = self.clone();
|
||||||
rpc_server.add_handler::<TableRPC<F>, _, _>(path, move |msg, _addr| {
|
rpc_server.add_handler::<TableRpc<F>, _, _>(path, move |msg, _addr| {
|
||||||
let self2 = self2.clone();
|
let self2 = self2.clone();
|
||||||
async move { self2.handle(&msg).await }
|
async move { self2.handle(&msg).await }
|
||||||
});
|
});
|
||||||
|
@ -293,21 +290,21 @@ where
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle(self: &Arc<Self>, msg: &TableRPC<F>) -> Result<TableRPC<F>, Error> {
|
async fn handle(self: &Arc<Self>, msg: &TableRpc<F>) -> Result<TableRpc<F>, Error> {
|
||||||
match msg {
|
match msg {
|
||||||
TableRPC::ReadEntry(key, sort_key) => {
|
TableRpc::ReadEntry(key, sort_key) => {
|
||||||
let value = self.data.read_entry(key, sort_key)?;
|
let value = self.data.read_entry(key, sort_key)?;
|
||||||
Ok(TableRPC::ReadEntryResponse(value))
|
Ok(TableRpc::ReadEntryResponse(value))
|
||||||
}
|
}
|
||||||
TableRPC::ReadRange(key, begin_sort_key, filter, limit) => {
|
TableRpc::ReadRange(key, begin_sort_key, filter, limit) => {
|
||||||
let values = self.data.read_range(key, begin_sort_key, filter, *limit)?;
|
let values = self.data.read_range(key, begin_sort_key, filter, *limit)?;
|
||||||
Ok(TableRPC::Update(values))
|
Ok(TableRpc::Update(values))
|
||||||
}
|
}
|
||||||
TableRPC::Update(pairs) => {
|
TableRpc::Update(pairs) => {
|
||||||
self.data.update_many(pairs)?;
|
self.data.update_many(pairs)?;
|
||||||
Ok(TableRPC::Ok)
|
Ok(TableRpc::Ok)
|
||||||
}
|
}
|
||||||
_ => Err(Error::BadRPC(format!("Unexpected table RPC"))),
|
_ => Err(Error::BadRpc("Unexpected table RPC".to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ impl FixedBytes32 {
|
||||||
&mut self.0[..]
|
&mut self.0[..]
|
||||||
}
|
}
|
||||||
/// Copy to a slice
|
/// Copy to a slice
|
||||||
pub fn to_vec(&self) -> Vec<u8> {
|
pub fn to_vec(self) -> Vec<u8> {
|
||||||
self.0.to_vec()
|
self.0.to_vec()
|
||||||
}
|
}
|
||||||
/// Try building a FixedBytes32 from a slice
|
/// Try building a FixedBytes32 from a slice
|
||||||
|
@ -88,7 +88,7 @@ impl FixedBytes32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A 32 bytes UUID
|
/// A 32 bytes UUID
|
||||||
pub type UUID = FixedBytes32;
|
pub type Uuid = FixedBytes32;
|
||||||
/// A 256 bit cryptographic hash, can be sha256 or blake2 depending on provenance
|
/// A 256 bit cryptographic hash, can be sha256 or blake2 depending on provenance
|
||||||
pub type Hash = FixedBytes32;
|
pub type Hash = FixedBytes32;
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ pub fn fasthash(data: &[u8]) -> FastHash {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a random 32 bytes UUID
|
/// Generate a random 32 bytes UUID
|
||||||
pub fn gen_uuid() -> UUID {
|
pub fn gen_uuid() -> Uuid {
|
||||||
rand::thread_rng().gen::<[u8; 32]>().into()
|
rand::thread_rng().gen::<[u8; 32]>().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,24 +7,24 @@ use crate::data::*;
|
||||||
|
|
||||||
/// RPC related errors
|
/// RPC related errors
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum RPCError {
|
pub enum RpcError {
|
||||||
#[error(display = "Node is down: {:?}.", _0)]
|
#[error(display = "Node is down: {:?}.", _0)]
|
||||||
NodeDown(UUID),
|
NodeDown(Uuid),
|
||||||
|
|
||||||
#[error(display = "Timeout: {}", _0)]
|
#[error(display = "Timeout: {}", _0)]
|
||||||
Timeout(#[error(source)] tokio::time::error::Elapsed),
|
Timeout(#[error(source)] tokio::time::error::Elapsed),
|
||||||
|
|
||||||
#[error(display = "HTTP error: {}", _0)]
|
#[error(display = "HTTP error: {}", _0)]
|
||||||
HTTP(#[error(source)] http::Error),
|
Http(#[error(source)] http::Error),
|
||||||
|
|
||||||
#[error(display = "Hyper error: {}", _0)]
|
#[error(display = "Hyper error: {}", _0)]
|
||||||
Hyper(#[error(source)] hyper::Error),
|
Hyper(#[error(source)] hyper::Error),
|
||||||
|
|
||||||
#[error(display = "Messagepack encode error: {}", _0)]
|
#[error(display = "Messagepack encode error: {}", _0)]
|
||||||
RMPEncode(#[error(source)] rmp_serde::encode::Error),
|
RmpEncode(#[error(source)] rmp_serde::encode::Error),
|
||||||
|
|
||||||
#[error(display = "Messagepack decode error: {}", _0)]
|
#[error(display = "Messagepack decode error: {}", _0)]
|
||||||
RMPDecode(#[error(source)] rmp_serde::decode::Error),
|
RmpDecode(#[error(source)] rmp_serde::decode::Error),
|
||||||
|
|
||||||
#[error(display = "Too many errors: {:?}", _0)]
|
#[error(display = "Too many errors: {:?}", _0)]
|
||||||
TooManyErrors(Vec<String>),
|
TooManyErrors(Vec<String>),
|
||||||
|
@ -40,26 +40,26 @@ pub enum Error {
|
||||||
Hyper(#[error(source)] hyper::Error),
|
Hyper(#[error(source)] hyper::Error),
|
||||||
|
|
||||||
#[error(display = "HTTP error: {}", _0)]
|
#[error(display = "HTTP error: {}", _0)]
|
||||||
HTTP(#[error(source)] http::Error),
|
Http(#[error(source)] http::Error),
|
||||||
|
|
||||||
#[error(display = "Invalid HTTP header value: {}", _0)]
|
#[error(display = "Invalid HTTP header value: {}", _0)]
|
||||||
HTTPHeader(#[error(source)] http::header::ToStrError),
|
HttpHeader(#[error(source)] http::header::ToStrError),
|
||||||
|
|
||||||
#[error(display = "TLS error: {}", _0)]
|
#[error(display = "TLS error: {}", _0)]
|
||||||
TLS(#[error(source)] rustls::TLSError),
|
Tls(#[error(source)] rustls::TLSError),
|
||||||
|
|
||||||
#[error(display = "PKI error: {}", _0)]
|
#[error(display = "PKI error: {}", _0)]
|
||||||
PKI(#[error(source)] webpki::Error),
|
Pki(#[error(source)] webpki::Error),
|
||||||
|
|
||||||
#[error(display = "Sled error: {}", _0)]
|
#[error(display = "Sled error: {}", _0)]
|
||||||
Sled(#[error(source)] sled::Error),
|
Sled(#[error(source)] sled::Error),
|
||||||
|
|
||||||
#[error(display = "Messagepack encode error: {}", _0)]
|
#[error(display = "Messagepack encode error: {}", _0)]
|
||||||
RMPEncode(#[error(source)] rmp_serde::encode::Error),
|
RmpEncode(#[error(source)] rmp_serde::encode::Error),
|
||||||
#[error(display = "Messagepack decode error: {}", _0)]
|
#[error(display = "Messagepack decode error: {}", _0)]
|
||||||
RMPDecode(#[error(source)] rmp_serde::decode::Error),
|
RmpDecode(#[error(source)] rmp_serde::decode::Error),
|
||||||
#[error(display = "JSON error: {}", _0)]
|
#[error(display = "JSON error: {}", _0)]
|
||||||
JSON(#[error(source)] serde_json::error::Error),
|
Json(#[error(source)] serde_json::error::Error),
|
||||||
#[error(display = "TOML decode error: {}", _0)]
|
#[error(display = "TOML decode error: {}", _0)]
|
||||||
TomlDecode(#[error(source)] toml::de::Error),
|
TomlDecode(#[error(source)] toml::de::Error),
|
||||||
|
|
||||||
|
@ -67,13 +67,13 @@ pub enum Error {
|
||||||
TokioJoin(#[error(source)] tokio::task::JoinError),
|
TokioJoin(#[error(source)] tokio::task::JoinError),
|
||||||
|
|
||||||
#[error(display = "RPC call error: {}", _0)]
|
#[error(display = "RPC call error: {}", _0)]
|
||||||
RPC(#[error(source)] RPCError),
|
Rpc(#[error(source)] RpcError),
|
||||||
|
|
||||||
#[error(display = "Remote error: {} (status code {})", _0, _1)]
|
#[error(display = "Remote error: {} (status code {})", _0, _1)]
|
||||||
RemoteError(String, StatusCode),
|
RemoteError(String, StatusCode),
|
||||||
|
|
||||||
#[error(display = "Bad RPC: {}", _0)]
|
#[error(display = "Bad RPC: {}", _0)]
|
||||||
BadRPC(String),
|
BadRpc(String),
|
||||||
|
|
||||||
#[error(display = "Corrupt data: does not match hash {:?}", _0)]
|
#[error(display = "Corrupt data: does not match hash {:?}", _0)]
|
||||||
CorruptData(Hash),
|
CorruptData(Hash),
|
||||||
|
@ -93,12 +93,12 @@ impl From<sled::transaction::TransactionError<Error>> for Error {
|
||||||
|
|
||||||
impl<T> From<tokio::sync::watch::error::SendError<T>> for Error {
|
impl<T> From<tokio::sync::watch::error::SendError<T>> for Error {
|
||||||
fn from(_e: tokio::sync::watch::error::SendError<T>) -> Error {
|
fn from(_e: tokio::sync::watch::error::SendError<T>) -> Error {
|
||||||
Error::Message(format!("Watch send error"))
|
Error::Message("Watch send error".to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<tokio::sync::mpsc::error::SendError<T>> for Error {
|
impl<T> From<tokio::sync::mpsc::error::SendError<T>> for Error {
|
||||||
fn from(_e: tokio::sync::mpsc::error::SendError<T>) -> Error {
|
fn from(_e: tokio::sync::mpsc::error::SendError<T>) -> Error {
|
||||||
Error::Message(format!("MPSC send error"))
|
Error::Message("MPSC send error".to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
|
||||||
|
@ -18,8 +18,8 @@ impl<T> Persister<T>
|
||||||
where
|
where
|
||||||
T: Serialize + for<'de> Deserialize<'de>,
|
T: Serialize + for<'de> Deserialize<'de>,
|
||||||
{
|
{
|
||||||
pub fn new(base_dir: &PathBuf, file_name: &str) -> Self {
|
pub fn new(base_dir: &Path, file_name: &str) -> Self {
|
||||||
let mut path = base_dir.clone();
|
let mut path = base_dir.to_path_buf();
|
||||||
path.push(file_name);
|
path.push(file_name);
|
||||||
Self {
|
Self {
|
||||||
path,
|
path,
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub enum Error {
|
||||||
|
|
||||||
/// The request contained an invalid UTF-8 sequence in its path or in other parameters
|
/// The request contained an invalid UTF-8 sequence in its path or in other parameters
|
||||||
#[error(display = "Invalid UTF-8: {}", _0)]
|
#[error(display = "Invalid UTF-8: {}", _0)]
|
||||||
InvalidUTF8(#[error(source)] std::str::Utf8Error),
|
InvalidUtf8(#[error(source)] std::str::Utf8Error),
|
||||||
|
|
||||||
/// The client send a header with invalid value
|
/// The client send a header with invalid value
|
||||||
#[error(display = "Invalid header value: {}", _0)]
|
#[error(display = "Invalid header value: {}", _0)]
|
||||||
|
@ -38,7 +38,7 @@ impl Error {
|
||||||
match self {
|
match self {
|
||||||
Error::NotFound => StatusCode::NOT_FOUND,
|
Error::NotFound => StatusCode::NOT_FOUND,
|
||||||
Error::ApiError(e) => e.http_status_code(),
|
Error::ApiError(e) => e.http_status_code(),
|
||||||
Error::InternalError(GarageError::RPC(_)) => StatusCode::SERVICE_UNAVAILABLE,
|
Error::InternalError(GarageError::Rpc(_)) => StatusCode::SERVICE_UNAVAILABLE,
|
||||||
Error::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
Error::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
_ => StatusCode::BAD_REQUEST,
|
_ => StatusCode::BAD_REQUEST,
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ async fn serve_file(garage: Arc<Garage>, req: Request<Body>) -> Result<Response<
|
||||||
let authority = req
|
let authority = req
|
||||||
.headers()
|
.headers()
|
||||||
.get(HOST)
|
.get(HOST)
|
||||||
.ok_or(Error::BadRequest(format!("HOST header required")))?
|
.ok_or_else(|| Error::BadRequest("HOST header required".to_owned()))?
|
||||||
.to_str()?;
|
.to_str()?;
|
||||||
|
|
||||||
// Get bucket
|
// Get bucket
|
||||||
|
@ -99,10 +99,10 @@ async fn serve_file(garage: Arc<Garage>, req: Request<Body>) -> Result<Response<
|
||||||
|
|
||||||
info!("Selected bucket: \"{}\", selected key: \"{}\"", bucket, key);
|
info!("Selected bucket: \"{}\", selected key: \"{}\"", bucket, key);
|
||||||
|
|
||||||
let res = match req.method() {
|
let res = match *req.method() {
|
||||||
&Method::HEAD => handle_head(garage, &req, &bucket, &key).await?,
|
Method::HEAD => handle_head(garage, &req, &bucket, &key).await?,
|
||||||
&Method::GET => handle_get(garage, &req, bucket, &key).await?,
|
Method::GET => handle_get(garage, &req, bucket, &key).await?,
|
||||||
_ => return Err(Error::BadRequest(format!("HTTP method not supported"))),
|
_ => return Err(Error::BadRequest("HTTP method not supported".to_string())),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
|
@ -118,7 +118,7 @@ fn authority_to_host(authority: &str) -> Result<&str, Error> {
|
||||||
let mut iter = authority.chars().enumerate();
|
let mut iter = authority.chars().enumerate();
|
||||||
let (_, first_char) = iter
|
let (_, first_char) = iter
|
||||||
.next()
|
.next()
|
||||||
.ok_or(Error::BadRequest(format!("Authority is empty")))?;
|
.ok_or_else(|| Error::BadRequest("Authority is empty".to_string()))?;
|
||||||
|
|
||||||
let split = match first_char {
|
let split = match first_char {
|
||||||
'[' => {
|
'[' => {
|
||||||
|
@ -133,7 +133,7 @@ fn authority_to_host(authority: &str) -> Result<&str, Error> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => iter.skip_while(|(_, c)| c != &':').next(),
|
_ => iter.find(|(_, c)| *c == ':'),
|
||||||
};
|
};
|
||||||
|
|
||||||
match split {
|
match split {
|
||||||
|
@ -158,7 +158,7 @@ fn host_to_bucket<'a>(host: &'a str, root: &str) -> &'a str {
|
||||||
}
|
}
|
||||||
|
|
||||||
let len_diff = host.len() - root.len();
|
let len_diff = host.len() - root.len();
|
||||||
let missing_starting_dot = root.chars().next() != Some('.');
|
let missing_starting_dot = !root.starts_with('.');
|
||||||
let cursor = if missing_starting_dot {
|
let cursor = if missing_starting_dot {
|
||||||
len_diff - 1
|
len_diff - 1
|
||||||
} else {
|
} else {
|
||||||
|
@ -175,10 +175,10 @@ fn host_to_bucket<'a>(host: &'a str, root: &str) -> &'a str {
|
||||||
fn path_to_key<'a>(path: &'a str, index: &str) -> Result<Cow<'a, str>, Error> {
|
fn path_to_key<'a>(path: &'a str, index: &str) -> Result<Cow<'a, str>, Error> {
|
||||||
let path_utf8 = percent_encoding::percent_decode_str(&path).decode_utf8()?;
|
let path_utf8 = percent_encoding::percent_decode_str(&path).decode_utf8()?;
|
||||||
|
|
||||||
if path_utf8.chars().next() != Some('/') {
|
if !path_utf8.starts_with('/') {
|
||||||
return Err(Error::BadRequest(format!(
|
return Err(Error::BadRequest(
|
||||||
"Path must start with a / (slash)"
|
"Path must start with a / (slash)".to_string(),
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
match path_utf8.chars().last() {
|
match path_utf8.chars().last() {
|
||||||
|
|
Loading…
Reference in a new issue