Compare commits

...

4 Commits

Author SHA1 Message Date
Alex Auvolat 3e7f766d95 CLI: default rpc_host 2021-10-26 11:36:30 +02:00
Alex Auvolat 43e13a501d Use published netapp crate instead of git repo 2021-10-26 10:36:57 +02:00
Alex Auvolat ada7899b24 Fix clippy lints (fix #121) 2021-10-26 10:20:05 +02:00
Alex Auvolat b2c51844a1 Add download link on homepage 2021-10-25 15:55:30 +02:00
25 changed files with 166 additions and 104 deletions
+1 -1
View File
@@ -36,7 +36,7 @@ steps:
path: /etc/nix
commands:
- nix-shell --arg release false --run "cargo fmt -- --check"
- nix-shell --arg release false --run "cargo clippy -- --allow clippy::needless_borrow --allow clippy::needless-return --deny warnings"
- nix-shell --arg release false --run "cargo clippy -- --deny warnings"
- name: build
image: nixpkgs/nix:nixos-21.05
Generated
+2 -1
View File
@@ -874,7 +874,8 @@ dependencies = [
[[package]]
name = "netapp"
version = "0.3.0"
source = "git+https://git.deuxfleurs.fr/lx/netapp#c20d36892bcccae580603249706ba60d54a46d7f"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d202ff23744499a8e5d89a3199ca7b9a8aef9980a23b1b09d6508a8db2a118d2"
dependencies = [
"arc-swap",
"async-trait",
+7 -11
View File
@@ -626,7 +626,7 @@ in
hex = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; };
sodiumoxide = rustPackages."registry+https://github.com/rust-lang/crates.io-index".kuska-sodiumoxide."0.2.5-0" { inherit profileName; };
log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.14" { inherit profileName; };
netapp = rustPackages."git+https://git.deuxfleurs.fr/lx/netapp".netapp."0.3.0" { inherit profileName; };
netapp = rustPackages."registry+https://github.com/rust-lang/crates.io-index".netapp."0.3.0" { inherit profileName; };
pretty_env_logger = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pretty_env_logger."0.4.0" { inherit profileName; };
rand = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.4" { inherit profileName; };
rmp_serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" { inherit profileName; };
@@ -687,7 +687,7 @@ in
garage_util = rustPackages."unknown".garage_util."0.4.0" { inherit profileName; };
hex = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; };
log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.14" { inherit profileName; };
netapp = rustPackages."git+https://git.deuxfleurs.fr/lx/netapp".netapp."0.3.0" { inherit profileName; };
netapp = rustPackages."registry+https://github.com/rust-lang/crates.io-index".netapp."0.3.0" { inherit profileName; };
rand = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.4" { inherit profileName; };
rmp_serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" { inherit profileName; };
serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.130" { inherit profileName; };
@@ -714,7 +714,7 @@ in
hyper = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.13" { inherit profileName; };
sodiumoxide = rustPackages."registry+https://github.com/rust-lang/crates.io-index".kuska-sodiumoxide."0.2.5-0" { inherit profileName; };
log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.14" { inherit profileName; };
netapp = rustPackages."git+https://git.deuxfleurs.fr/lx/netapp".netapp."0.3.0" { inherit profileName; };
netapp = rustPackages."registry+https://github.com/rust-lang/crates.io-index".netapp."0.3.0" { inherit profileName; };
rand = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.4" { inherit profileName; };
rmp_serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" { inherit profileName; };
serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.130" { inherit profileName; };
@@ -761,7 +761,7 @@ in
http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.5" { inherit profileName; };
hyper = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.13" { inherit profileName; };
log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.14" { inherit profileName; };
netapp = rustPackages."git+https://git.deuxfleurs.fr/lx/netapp".netapp."0.3.0" { inherit profileName; };
netapp = rustPackages."registry+https://github.com/rust-lang/crates.io-index".netapp."0.3.0" { inherit profileName; };
rand = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.4" { inherit profileName; };
rmp_serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" { inherit profileName; };
serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.130" { inherit profileName; };
@@ -1228,15 +1228,11 @@ in
};
});
"git+https://git.deuxfleurs.fr/lx/netapp".netapp."0.3.0" = overridableMkRustCrate (profileName: rec {
"registry+https://github.com/rust-lang/crates.io-index".netapp."0.3.0" = overridableMkRustCrate (profileName: rec {
name = "netapp";
version = "0.3.0";
registry = "git+https://git.deuxfleurs.fr/lx/netapp";
src = fetchCrateGit {
url = https://git.deuxfleurs.fr/lx/netapp;
name = "netapp";
version = "0.3.0";
rev = "9b64c27da68f7ac9049e02e26da918e871a63f07";};
registry = "registry+https://github.com/rust-lang/crates.io-index";
src = fetchCratesIo { inherit name version; sha256 = "d202ff23744499a8e5d89a3199ca7b9a8aef9980a23b1b09d6508a8db2a118d2"; };
features = builtins.concatLists [
[ "default" ]
];
+2 -1
View File
@@ -5,7 +5,8 @@
</p>
<p align="center" style="text-align:center;">
[ <a href="https://git.deuxfleurs.fr/Deuxfleurs/garage">Git repository</a>
[ <a href="https://garagehq.deuxfleurs.fr/_releases.html">Download</a>
| <a href="https://git.deuxfleurs.fr/Deuxfleurs/garage">Git repository</a>
| <a href="https://matrix.to/#/%23garage:deuxfleurs.fr">Matrix channel</a>
| <a href="https://drone.deuxfleurs.fr/Deuxfleurs/garage">Drone CI</a>
]
+65 -21
View File
@@ -1,6 +1,8 @@
# Migrating from 0.3 to 0.4
**Migrating from 0.3 to 0.4 is unsupported. This document is only intended to document the process internally for the Deuxfleurs cluster where we have to do it. Do not try it yourself, you will lose your data and we will not help you.**
**Migrating from 0.3 to 0.4 is unsupported. This document is only intended to
document the process internally for the Deuxfleurs cluster where we have to do
it. Do not try it yourself, you will lose your data and we will not help you.**
**Migrating from 0.2 to 0.4 will break everything for sure. Never try it.**
@@ -9,53 +11,95 @@ The Sled database is still the same, and the data directory as well.
The following has changed, all in the meta directory:
- `node_id` in 0.3 contains the identifier of the current node. In 0.4, this file does nothing and should be deleted. It is replaced by `node_key` (the secret key) and `node_key.pub` (the associated public key). A node's identifier on the ring is its public key.
- `node_id` in 0.3 contains the identifier of the current node. In 0.4, this
file does nothing and should be deleted. It is replaced by `node_key` (the
secret key) and `node_key.pub` (the associated public key). A node's
identifier on the ring is its public key.
- `peer_info` in 0.3 contains the list of peers saved automatically by Garage. The format has changed and it is now stored in `peer_list` (`peer_info` should be deleted).
- `peer_info` in 0.3 contains the list of peers saved automatically by Garage.
The format has changed and it is now stored in `peer_list` (`peer_info`
should be deleted).
When migrating, all node identifiers will change. This also means that the affectation of data partitions on the ring will change, and lots of data will have to be rebalanced.
When migrating, all node identifiers will change. This also means that the
affectation of data partitions on the ring will change, and lots of data will
have to be rebalanced.
- If your cluster has only 3 nodes, all nodes store everything, therefore nothing has to be rebalanced.
- If your cluster has only 4 nodes, for any partition there will always be at least 2 nodes that stored data before that still store it after. Therefore the migration should in theory be transparent and Garage should continue to work during the rebalance.
- If your cluster has only 4 nodes, for any partition there will always be at
least 2 nodes that stored data before that still store it after. Therefore
the migration should in theory be transparent and Garage should continue to
work during the rebalance.
- If your cluster has 5 or more nodes, data will disappear during the migration. Do not migrate (fortunately we don't have this scenario at Deuxfleurs), or if you do, make Garage unavailable until things stabilize (disable web and api access).
- If your cluster has 5 or more nodes, data will disappear during the
migration. Do not migrate (fortunately we don't have this scenario at
Deuxfleurs), or if you do, make Garage unavailable until things stabilize
(disable web and api access).
The migration steps are as follows:
1. Prepare a new configuration file for 0.4. For each node, point to the same meta and data directories as Garage 0.3. Basically, the things that change are the following:
1. Prepare a new configuration file for 0.4. For each node, point to the same
meta and data directories as Garage 0.3. Basically, the things that change
are the following:
- No more `rpc_tls` section
- You have to generate a shared `rpc_secret` and put it in all config files
- `bootstrap_nodes` has a different syntax as it has to contain node keys. Leave it empty and use `garage node-id` and `garage node connect` instead (new features of 0.4)
- `bootstrap_peers` has a different syntax as it has to contain node keys.
Leave it empty and use `garage node-id` and `garage node connect` instead (new features of 0.4)
- put the publicly accessible RPC address of your node in `rpc_public_addr` if possible (its optional but recommended)
- If you are using Consul, change the `consul_service_name` to NOT be the name advertised by Nomad. Now Garage is responsible for advertising its own service itself.
- If you are using Consul, change the `consul_service_name` to NOT be the name advertised by Nomad.
Now Garage is responsible for advertising its own service itself.
2. Disable api and web access for some time, do `garage repair --all --yes tables` and `garage repair --all --yes blocks`, check the logs and check that all data seems to be synced correctly between nodes.
2. Disable api and web access for some time (Garage does not support disabling
these endpoints but you can change the port number or stop your reverse
proxy for instance).
3. Save somewhere the output of `garage status`. We will need this to remember how to reconfigure nodes in 0.4.
3. Do `garage repair -a --yes tables` and `garage repair -a --yes blocks`,
check the logs and check that all data seems to be synced correctly between
nodes.
4. Turn off Garage 0.3
4. Save somewhere the output of `garage status`. We will need this to remember
how to reconfigure nodes in 0.4.
5. Backup metadata folders if you can (i.e. if you have space to do it somewhere). Backuping data folders could also be usefull but that's much harder to do. If your filesystem supports snapshots, this could be a good time to use them.
5. Turn off Garage 0.3
6. Turn on Garage 0.4
6. Backup metadata folders if you can (i.e. if you have space to do it
somewhere). Backuping data folders could also be usefull but that's much
harder to do. If your filesystem supports snapshots, this could be a good
time to use them.
7. At this point, running `garage status` should indicate that all nodes of the previous cluster are "unavailable". The nodes have new identifiers that should appear in healthy nodes once they can talk to one another (use `garage node connect` if necessary`). They should have NO ROLE ASSIGNED at the moment.
7. Turn on Garage 0.4
8. Prepare a script with several `garage node configure` commands that replace each of the v0.3 node ID with the corresponding v0.4 node ID, with the same zone/tag/capacity. For example if your node `drosera` had identifier `c24e` before and now has identifier `789a`, and it was configured with capacity `2` in zone `dc1`, put the following command in your script:
8. At this point, running `garage status` should indicate that all nodes of the
previous cluster are "unavailable". The nodes have new identifiers that
should appear in healthy nodes once they can talk to one another (use
`garage node connect` if necessary`). They should have NO ROLE ASSIGNED at
the moment.
9. Prepare a script with several `garage node configure` commands that replace
each of the v0.3 node ID with the corresponding v0.4 node ID, with the same
zone/tag/capacity. For example if your node `drosera` had identifier `c24e`
before and now has identifier `789a`, and it was configured with capacity
`2` in zone `dc1`, put the following command in your script:
```bash
garage node configure 789a -z dc1 -c 2 -t drosera --replace c24e
```
9. Run your reconfiguration script. Check that the new output of `garage status` contains the correct node IDs with the correct values for capacity and zone. Old nodes should no longer be mentioned.
10. Run your reconfiguration script. Check that the new output of `garage
status` contains the correct node IDs with the correct values for capacity
and zone. Old nodes should no longer be mentioned.
10. If your cluster has 4 nodes or less, and you are feeling adventurous, you can reenable Web and API access now. Things will probably work.
11. If your cluster has 4 nodes or less, and you are feeling adventurous, you
can reenable Web and API access now. Things will probably work.
11. Garage might already be resyncing stuff. Issue a `garage repair --all --yes tables` and `garage repair --all --yes blocks` to force it to do so.
12. Garage might already be resyncing stuff. Issue a `garage repair -a --yes
tables` and `garage repair -a --yes blocks` to force it to do so.
12. Wait for resyncing activity to stop in the logs. Do steps 11 and 12 two or three times, until you see that when you issue the repair commands, nothing gets resynced any longer.
13. Wait for resyncing activity to stop in the logs. Do steps 12 and 13 two or
three times, until you see that when you issue the repair commands, nothing
gets resynced any longer.
13. Your upgraded cluster should be in a working state. Re-enable API and Web access and check that everything went well.
14. Your upgraded cluster should be in a working state. Re-enable API and Web
access and check that everything went well.
+16 -19
View File
@@ -39,7 +39,7 @@ pub async fn run_api_server(
}
});
let server = Server::bind(&addr).serve(service);
let server = Server::bind(addr).serve(service);
let graceful = server.with_graceful_shutdown(shutdown_signal);
info!("API server listening on http://{}", addr);
@@ -88,8 +88,8 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
let (bucket, key) = parse_bucket_key(&path)?;
let allowed = match req.method() {
&Method::HEAD | &Method::GET => api_key.allow_read(&bucket),
_ => api_key.allow_write(&bucket),
&Method::HEAD | &Method::GET => api_key.allow_read(bucket),
_ => api_key.allow_write(bucket),
};
if !allowed {
return Err(Error::Forbidden(
@@ -109,11 +109,11 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
match *req.method() {
Method::HEAD => {
// HeadObject query
Ok(handle_head(garage, &req, &bucket, &key).await?)
Ok(handle_head(garage, &req, bucket, key).await?)
}
Method::GET => {
// GetObject query
Ok(handle_get(garage, &req, &bucket, &key).await?)
Ok(handle_get(garage, &req, bucket, key).await?)
}
Method::PUT => {
if params.contains_key(&"partnumber".to_string())
@@ -125,8 +125,8 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
Ok(handle_put_part(
garage,
req,
&bucket,
&key,
bucket,
key,
part_number,
upload_id,
content_sha256,
@@ -136,46 +136,43 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
// CopyObject query
let copy_source = req.headers().get("x-amz-copy-source").unwrap().to_str()?;
let copy_source =
percent_encoding::percent_decode_str(&copy_source).decode_utf8()?;
percent_encoding::percent_decode_str(copy_source).decode_utf8()?;
let (source_bucket, source_key) = parse_bucket_key(&copy_source)?;
if !api_key.allow_read(&source_bucket) {
if !api_key.allow_read(source_bucket) {
return Err(Error::Forbidden(format!(
"Reading from bucket {} not allowed for this key",
source_bucket
)));
}
let source_key = source_key.ok_or_bad_request("No source key specified")?;
Ok(
handle_copy(garage, &req, &bucket, &key, &source_bucket, &source_key)
.await?,
)
Ok(handle_copy(garage, &req, bucket, key, source_bucket, source_key).await?)
} else {
// PutObject query
Ok(handle_put(garage, req, &bucket, &key, content_sha256).await?)
Ok(handle_put(garage, req, bucket, key, content_sha256).await?)
}
}
Method::DELETE => {
if params.contains_key(&"uploadid".to_string()) {
// AbortMultipartUpload query
let upload_id = params.get("uploadid").unwrap();
Ok(handle_abort_multipart_upload(garage, &bucket, &key, upload_id).await?)
Ok(handle_abort_multipart_upload(garage, bucket, key, upload_id).await?)
} else {
// DeleteObject query
Ok(handle_delete(garage, &bucket, &key).await?)
Ok(handle_delete(garage, bucket, key).await?)
}
}
Method::POST => {
if params.contains_key(&"uploads".to_string()) {
// CreateMultipartUpload call
Ok(handle_create_multipart_upload(garage, &req, &bucket, &key).await?)
Ok(handle_create_multipart_upload(garage, &req, bucket, key).await?)
} else if params.contains_key(&"uploadid".to_string()) {
// CompleteMultipartUpload call
let upload_id = params.get("uploadid").unwrap();
Ok(handle_complete_multipart_upload(
garage,
req,
&bucket,
&key,
bucket,
key,
upload_id,
content_sha256,
)
+3 -2
View File
@@ -55,7 +55,8 @@ async fn handle_delete_internal(
);
garage.object_table.insert(&object).await?;
return Ok((deleted_version, version_uuid));
Ok((deleted_version, version_uuid))
}
pub async fn handle_delete(
@@ -82,7 +83,7 @@ pub async fn handle_delete_objects(
let body = hyper::body::to_bytes(req.into_body()).await?;
verify_signed_content(content_sha256, &body[..])?;
let cmd_xml = roxmltree::Document::parse(&std::str::from_utf8(&body)?)?;
let cmd_xml = roxmltree::Document::parse(std::str::from_utf8(&body)?)?;
let cmd = parse_delete_objects_xml(&cmd_xml).ok_or_bad_request("Invalid delete XML query")?;
let mut ret_deleted = Vec::new();
+5 -5
View File
@@ -106,12 +106,12 @@ pub async fn handle_head(
_ => unreachable!(),
};
if let Some(cached) = try_answer_cached(&version, version_meta, req) {
if let Some(cached) = try_answer_cached(version, version_meta, req) {
return Ok(cached);
}
let body: Body = Body::empty();
let response = object_headers(&version, version_meta)
let response = object_headers(version, version_meta)
.header("Content-Length", format!("{}", version_meta.size))
.status(StatusCode::OK)
.body(body)
@@ -149,7 +149,7 @@ pub async fn handle_get(
ObjectVersionData::FirstBlock(meta, _) => meta,
};
if let Some(cached) = try_answer_cached(&last_v, last_v_meta, req) {
if let Some(cached) = try_answer_cached(last_v, last_v_meta, req) {
return Ok(cached);
}
@@ -179,7 +179,7 @@ pub async fn handle_get(
.await;
}
let resp_builder = object_headers(&last_v, last_v_meta)
let resp_builder = object_headers(last_v, last_v_meta)
.header("Content-Length", format!("{}", last_v_meta.size))
.status(StatusCode::OK);
@@ -190,7 +190,7 @@ pub async fn handle_get(
Ok(resp_builder.body(body)?)
}
ObjectVersionData::FirstBlock(_, first_block_hash) => {
let read_first_block = garage.block_manager.rpc_get_block(&first_block_hash);
let read_first_block = garage.block_manager.rpc_get_block(first_block_hash);
let get_next_blocks = garage.version_table.get(&last_v.uuid, &EmptyKey);
let (first_block, version) = futures::try_join!(read_first_block, get_next_blocks)?;
+5 -5
View File
@@ -193,8 +193,8 @@ async fn read_and_put_blocks(
let mut next_offset = first_block.len();
let mut put_curr_version_block = put_block_meta(
&garage,
&version,
garage,
version,
part_number,
0,
first_block_hash,
@@ -213,8 +213,8 @@ async fn read_and_put_blocks(
let block_hash = blake2sum(&block[..]);
let block_len = block.len();
put_curr_version_block = put_block_meta(
&garage,
&version,
garage,
version,
part_number,
next_offset as u64,
block_hash,
@@ -437,7 +437,7 @@ pub async fn handle_complete_multipart_upload(
let body = hyper::body::to_bytes(req.into_body()).await?;
verify_signed_content(content_sha256, &body[..])?;
let body_xml = roxmltree::Document::parse(&std::str::from_utf8(&body)?)?;
let body_xml = roxmltree::Document::parse(std::str::from_utf8(&body)?)?;
let body_list_of_parts = parse_complete_multpart_upload_body(&body_xml)
.ok_or_bad_request("Invalid CompleteMultipartUpload XML")?;
debug!(
+2 -2
View File
@@ -70,7 +70,7 @@ pub async fn check_signature(
let canonical_request = canonical_request(
request.method(),
&request.uri().path().to_string(),
&canonical_query_string(&request.uri()),
&canonical_query_string(request.uri()),
&headers,
&authorization.signed_headers,
&authorization.content_sha256,
@@ -252,7 +252,7 @@ fn canonical_request(
method.as_str(),
url_path,
canonical_query_string,
&canonical_header_string(&headers, signed_headers),
&canonical_header_string(headers, signed_headers),
"",
signed_headers,
content_sha256,
+2 -1
View File
@@ -41,4 +41,5 @@ futures = "0.3"
futures-util = "0.3"
tokio = { version = "1.0", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "time", "macros", "sync", "signal", "fs"] }
netapp = { version = "0.3.0", git = "https://git.deuxfleurs.fr/lx/netapp" }
#netapp = { version = "0.3.0", git = "https://git.deuxfleurs.fr/lx/netapp" }
netapp = "0.3.0"
+5 -3
View File
@@ -1,5 +1,7 @@
use std::path::PathBuf;
use log::warn;
use garage_util::error::*;
pub const READ_KEY_ERROR: &str = "Unable to read node key. It will be generated by your garage node the first time is it launched. Ensure that your garage node is currently running. (The node key is supposed to be stored in your metadata directory.)";
@@ -22,11 +24,11 @@ pub fn node_id_command(config_file: PathBuf, quiet: bool) -> Result<(), Error> {
println!("{}", idstr);
if !quiet {
eprintln!("WARNING: I don't know the public address to reach this node.");
eprintln!("In all of the instructions below, replace 127.0.0.1:3901 by the appropriate address and port.");
warn!("WARNING: I don't know the public address to reach this node.");
warn!("In all of the instructions below, replace 127.0.0.1:{} by the appropriate address and port.", config.rpc_bind_addr.port());
}
format!("{}@127.0.0.1:3901", idstr)
format!("{}@127.0.0.1:{}", idstr, config.rpc_bind_addr.port())
};
if !quiet {
+1 -1
View File
@@ -54,7 +54,7 @@ pub fn format_table(data: Vec<String>) {
out.push_str(col);
(0..col_len - col.chars().count() + 2).for_each(|_| out.push(' '));
}
out.push_str(&row[row.len() - 1]);
out.push_str(row[row.len() - 1]);
out.push('\n');
}
+20 -5
View File
@@ -9,6 +9,7 @@ mod cli;
mod repair;
mod server;
use std::net::SocketAddr;
use std::path::PathBuf;
use structopt::StructOpt;
@@ -46,6 +47,9 @@ struct Opt {
#[tokio::main]
async fn main() {
if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "garage=info")
}
pretty_env_logger::init();
sodiumoxide::init().expect("Unable to init sodiumoxide");
@@ -99,12 +103,23 @@ async fn cli_command(opt: Opt) -> Result<(), Error> {
let (id, addr) = if let Some(h) = opt.rpc_host {
let (id, addrs) = parse_and_resolve_peer_addr(&h).ok_or_else(|| format!("Invalid RPC remote node identifier: {}. Expected format is <pubkey>@<IP or hostname>:<port>.", h))?;
(id, addrs[0])
} else if let Some(a) = config.as_ref().map(|c| c.rpc_public_addr).flatten() {
let node_id = garage_rpc::system::read_node_id(&config.unwrap().metadata_dir)
.err_context(READ_KEY_ERROR)?;
(node_id, a)
} else {
return Err(Error::Message("No RPC host provided".into()));
let node_id = garage_rpc::system::read_node_id(&config.as_ref().unwrap().metadata_dir)
.err_context(READ_KEY_ERROR)?;
if let Some(a) = config.as_ref().map(|c| c.rpc_public_addr).flatten() {
(node_id, a)
} else {
let default_addr = SocketAddr::new(
"127.0.0.1".parse().unwrap(),
config.as_ref().unwrap().rpc_bind_addr.port(),
);
warn!(
"Trying to contact Garage node at default address {}",
default_addr
);
warn!("If this doesn't work, consider adding rpc_public_addr in your config file or specifying the -h command line parameter.");
(node_id, default_addr)
}
};
// Connect to target host
+2 -1
View File
@@ -33,4 +33,5 @@ futures = "0.3"
futures-util = "0.3"
tokio = { version = "1.0", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "time", "macros", "sync", "signal", "fs"] }
netapp = { version = "0.3.0", git = "https://git.deuxfleurs.fr/lx/netapp" }
#netapp = { version = "0.3.0", git = "https://git.deuxfleurs.fr/lx/netapp" }
netapp = "0.3.0"
+6 -6
View File
@@ -129,7 +129,7 @@ impl BlockManager {
/// Ask nodes that might have a block for it
pub async fn rpc_get_block(&self, hash: &Hash) -> Result<Vec<u8>, Error> {
let who = self.replication.read_nodes(&hash);
let who = self.replication.read_nodes(hash);
let resps = self
.system
.rpc
@@ -224,7 +224,7 @@ impl BlockManager {
})?;
let old_rc = old_rc.map(u64_from_be_bytes).unwrap_or(0);
if old_rc == 0 {
self.put_to_resync(&hash, BLOCK_RW_TIMEOUT)?;
self.put_to_resync(hash, BLOCK_RW_TIMEOUT)?;
}
Ok(())
}
@@ -240,7 +240,7 @@ impl BlockManager {
}
})?;
if new_rc.is_none() {
self.put_to_resync(&hash, BLOCK_GC_TIMEOUT)?;
self.put_to_resync(hash, BLOCK_GC_TIMEOUT)?;
}
Ok(())
}
@@ -406,7 +406,7 @@ impl BlockManager {
if exists && !needed {
trace!("Offloading block {:?}", hash);
let mut who = self.replication.write_nodes(&hash);
let mut who = self.replication.write_nodes(hash);
if who.len() < self.replication.write_quorum() {
return Err(Error::Message("Not trying to offload block because we don't have a quorum of nodes to write to".to_string()));
}
@@ -478,7 +478,7 @@ impl BlockManager {
// TODO find a way to not do this if they are sending it to us
// Let's suppose this isn't an issue for now with the BLOCK_RW_TIMEOUT delay
// between the RC being incremented and this part being called.
let block_data = self.rpc_get_block(&hash).await?;
let block_data = self.rpc_get_block(hash).await?;
self.write_block(hash, &block_data[..]).await?;
}
@@ -600,7 +600,7 @@ impl BlockManagerLocked {
let mut path2 = path.clone();
path2.set_extension(".corrupted");
fs::rename(path, path2).await?;
mgr.put_to_resync(&hash, Duration::from_millis(0))?;
mgr.put_to_resync(hash, Duration::from_millis(0))?;
Ok(())
}
+2 -1
View File
@@ -33,6 +33,7 @@ futures-util = "0.3"
tokio = { version = "1.0", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "time", "macros", "sync", "signal", "fs"] }
tokio-stream = { version = "0.1", features = ["net"] }
netapp = { version = "0.3.0", git = "https://git.deuxfleurs.fr/lx/netapp" }
#netapp = { version = "0.3.0", git = "https://git.deuxfleurs.fr/lx/netapp" }
netapp = "0.3.0"
hyper = { version = "0.14", features = ["client", "http1", "runtime", "tcp"] }
+1 -1
View File
@@ -543,7 +543,7 @@ impl EndpointHandler<SystemRpc> for System {
SystemRpc::Connect(node) => self.handle_connect(node).await,
SystemRpc::PullConfig => Ok(self.handle_pull_config()),
SystemRpc::AdvertiseStatus(adv) => self.handle_advertise_status(from.into(), adv).await,
SystemRpc::AdvertiseConfig(adv) => self.clone().handle_advertise_config(&adv).await,
SystemRpc::AdvertiseConfig(adv) => self.clone().handle_advertise_config(adv).await,
SystemRpc::GetKnownNodes => Ok(self.handle_get_known_nodes()),
_ => Err(Error::BadRpc("Unexpected RPC message".to_string())),
}
+3 -3
View File
@@ -103,7 +103,7 @@ where
}
/// Get a reference to the value assigned to a key
pub fn get(&self, k: &K) -> Option<&V> {
match self.vals.binary_search_by(|(k2, _, _)| k2.cmp(&k)) {
match self.vals.binary_search_by(|(k2, _, _)| k2.cmp(k)) {
Ok(i) => Some(&self.vals[i].2),
Err(_) => None,
}
@@ -132,14 +132,14 @@ where
{
fn merge(&mut self, other: &Self) {
for (k, ts2, v2) in other.vals.iter() {
match self.vals.binary_search_by(|(k2, _, _)| k2.cmp(&k)) {
match self.vals.binary_search_by(|(k2, _, _)| k2.cmp(k)) {
Ok(i) => {
let (_, ts1, _v1) = &self.vals[i];
if ts2 > ts1 {
self.vals[i].1 = *ts2;
self.vals[i].2 = v2.clone();
} else if ts1 == ts2 {
self.vals[i].2.merge(&v2);
self.vals[i].2.merge(v2);
}
}
Err(i) => {
+3 -3
View File
@@ -49,7 +49,7 @@ where
/// Get a reference to the value assigned to a key
pub fn get(&self, k: &K) -> Option<&V> {
match self.vals.binary_search_by(|(k2, _)| k2.cmp(&k)) {
match self.vals.binary_search_by(|(k2, _)| k2.cmp(k)) {
Ok(i) => Some(&self.vals[i].1),
Err(_) => None,
}
@@ -76,9 +76,9 @@ where
{
fn merge(&mut self, other: &Self) {
for (k, v2) in other.vals.iter() {
match self.vals.binary_search_by(|(k2, _)| k2.cmp(&k)) {
match self.vals.binary_search_by(|(k2, _)| k2.cmp(k)) {
Ok(i) => {
self.vals[i].1.merge(&v2);
self.vals[i].1.merge(v2);
}
Err(i) => {
self.vals.insert(i, (k.clone(), v2.clone()));
+2 -2
View File
@@ -167,7 +167,7 @@ where
// Calculate an update to apply to this node
// 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
let mutate = match self.read_node_txn(tx, &key)? {
let mutate = match self.read_node_txn(tx, key)? {
MerkleNode::Empty => new_vhash.map(|vhv| MerkleNode::Leaf(k.to_vec(), vhv)),
MerkleNode::Intermediate(mut children) => {
let key2 = key.next_key(khash);
@@ -270,7 +270,7 @@ where
};
if let Some(new_node) = mutate {
let hash = self.put_node_txn(tx, &key, &new_node)?;
let hash = self.put_node_txn(tx, key, &new_node)?;
Ok(Some(hash))
} else {
Ok(None)
+2 -2
View File
@@ -27,7 +27,7 @@ pub struct TableShardedReplication {
impl TableReplication for TableShardedReplication {
fn read_nodes(&self, hash: &Hash) -> Vec<Uuid> {
let ring = self.system.ring.borrow();
ring.get_nodes(&hash, self.replication_factor)
ring.get_nodes(hash, self.replication_factor)
}
fn read_quorum(&self) -> usize {
self.read_quorum
@@ -35,7 +35,7 @@ impl TableReplication for TableShardedReplication {
fn write_nodes(&self, hash: &Hash) -> Vec<Uuid> {
let ring = self.system.ring.borrow();
ring.get_nodes(&hash, self.replication_factor)
ring.get_nodes(hash, self.replication_factor)
}
fn write_quorum(&self) -> usize {
self.write_quorum
+2 -2
View File
@@ -266,7 +266,7 @@ where
let nodes = self
.data
.replication
.write_nodes(&begin)
.write_nodes(begin)
.into_iter()
.collect::<Vec<_>>();
if nodes.contains(&self.system.id) {
@@ -530,7 +530,7 @@ where
Ok(SyncRpc::RootCkDifferent(hash != *h))
}
SyncRpc::GetNode(k) => {
let node = self.merkle.read_node(&k)?;
let node = self.merkle.read_node(k)?;
Ok(SyncRpc::Node(k.clone(), node))
}
SyncRpc::Items(items) => {
+3 -1
View File
@@ -32,6 +32,8 @@ toml = "0.5"
futures = "0.3"
tokio = { version = "1.0", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "time", "macros", "sync", "signal", "fs"] }
netapp = { version = "0.3.0", git = "https://git.deuxfleurs.fr/lx/netapp" }
#netapp = { version = "0.3.0", git = "https://git.deuxfleurs.fr/lx/netapp" }
netapp = "0.3.0"
http = "0.2"
hyper = "0.14"
+4 -4
View File
@@ -36,7 +36,7 @@ pub async fn run_web_server(
}
});
let server = Server::bind(&addr).serve(service);
let server = Server::bind(addr).serve(service);
let graceful = server.with_graceful_shutdown(shutdown_signal);
info!("Web server listening on http://{}", addr);
@@ -95,12 +95,12 @@ async fn serve_file(garage: Arc<Garage>, req: Request<Body>) -> Result<Response<
// Get path
let path = req.uri().path().to_string();
let index = &garage.config.s3_web.index;
let key = path_to_key(&path, &index)?;
let key = path_to_key(&path, index)?;
info!("Selected bucket: \"{}\", selected key: \"{}\"", bucket, key);
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?,
_ => return Err(Error::BadRequest("HTTP method not supported".to_string())),
};
@@ -173,7 +173,7 @@ fn host_to_bucket<'a>(host: &'a str, root: &str) -> &'a str {
/// When a path ends with "/", we append the index name to match traditional web server behavior
/// which is also AWS S3 behavior.
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.starts_with('/') {
return Err(Error::BadRequest(