mirror of
https://github.com/deuxfleurs-org/garage.git
synced 2026-08-14 16:13:11 +00:00
Compare commits
6 Commits
v0.9.0-rc1
...
v0.9.0-rc2
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c431b0c03 | |||
| 1c13135f25 | |||
| 2448eb7713 | |||
| 6790e24f5a | |||
| 9ccc1d6f4a | |||
| 920dec393a |
+13
-9
@@ -42,12 +42,6 @@ steps:
|
||||
- nix-build --no-build-output --attr clippy.amd64 --argstr git_version ${DRONE_TAG:-$DRONE_COMMIT}
|
||||
- nix-shell --attr integration --run ./script/test-smoke.sh || (cat /tmp/garage.log; false)
|
||||
|
||||
- name: upgrade tests
|
||||
image: nixpkgs/nix:nixos-22.05
|
||||
commands:
|
||||
- nix-build --no-build-output --attr clippy.amd64 --argstr git_version ${DRONE_TAG:-$DRONE_COMMIT}
|
||||
- nix-shell --attr integration --run "./script/test-upgrade.sh v0.8.4 x86_64-unknown-linux-musl" || (cat /tmp/garage.log; false)
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- custom
|
||||
@@ -71,11 +65,16 @@ steps:
|
||||
- nix-build --no-build-output --attr pkgs.amd64.release --argstr git_version ${DRONE_TAG:-$DRONE_COMMIT}
|
||||
- nix-shell --attr rust --run "./script/not-dynamic.sh result-bin/bin/garage"
|
||||
|
||||
- name: integration
|
||||
- name: integration tests
|
||||
image: nixpkgs/nix:nixos-22.05
|
||||
commands:
|
||||
- nix-shell --attr integration --run ./script/test-smoke.sh || (cat /tmp/garage.log; false)
|
||||
|
||||
- name: upgrade tests
|
||||
image: nixpkgs/nix:nixos-22.05
|
||||
commands:
|
||||
- nix-shell --attr integration --run "./script/test-upgrade.sh v0.8.4 x86_64-unknown-linux-musl" || (cat /tmp/garage.log; false)
|
||||
|
||||
- name: push static binary
|
||||
image: nixpkgs/nix:nixos-22.05
|
||||
environment:
|
||||
@@ -122,11 +121,16 @@ steps:
|
||||
- nix-build --no-build-output --attr pkgs.i386.release --argstr git_version ${DRONE_TAG:-$DRONE_COMMIT}
|
||||
- nix-shell --attr rust --run "./script/not-dynamic.sh result-bin/bin/garage"
|
||||
|
||||
- name: integration
|
||||
- name: integration tests
|
||||
image: nixpkgs/nix:nixos-22.05
|
||||
commands:
|
||||
- nix-shell --attr integration --run ./script/test-smoke.sh || (cat /tmp/garage.log; false)
|
||||
|
||||
- name: upgrade tests
|
||||
image: nixpkgs/nix:nixos-22.05
|
||||
commands:
|
||||
- nix-shell --attr integration --run "./script/test-upgrade.sh v0.8.4 i686-unknown-linux-musl" || (cat /tmp/garage.log; false)
|
||||
|
||||
- name: push static binary
|
||||
image: nixpkgs/nix:nixos-22.05
|
||||
environment:
|
||||
@@ -291,6 +295,6 @@ trigger:
|
||||
|
||||
---
|
||||
kind: signature
|
||||
hmac: dcf68ed6536e3fd9b359377e4c6c1d321fa9cc0f6fe5645d69c8f1d604569b4c
|
||||
hmac: 0c4b57eb4b27b7c6a6ff21ab87f0767fe3eb90f5d95d5cbcdccf794e9d2a5d86
|
||||
|
||||
...
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
+++
|
||||
title = "Migrating from 0.8 to 0.9"
|
||||
weight = 14
|
||||
+++
|
||||
|
||||
**This guide explains how to migrate to 0.9 if you have an existing 0.8 cluster.
|
||||
We don't recommend trying to migrate to 0.9 directly from 0.7 or older.**
|
||||
|
||||
**We make no guarantee that this migration will work perfectly:
|
||||
back up all your data before attempting it!**
|
||||
|
||||
The following are **breaking changes** in Garage v0.9 that require your attention when migrating:
|
||||
|
||||
- LMDB is now the default metadata db engine and Sled is deprecated. If you were using Sled, make sure to specify `db_engine = "sled"` in your configuration file, or take the time to [convert your database](https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#db-engine-since-v0-8-0).
|
||||
|
||||
- Capacity values are now in actual byte units. The translation from the old layout will assign 1 capacity = 1Gb by default, which might be wrong for your cluster. This does not cause any data to be moved around, but you might want to re-assign correct capacity values post-migration.
|
||||
|
||||
- Multipart uploads that were started in Garage v0.8 will not be visible in Garage v0.9 and will have to be restarted from scratch.
|
||||
|
||||
- Changes to the admin API: some `v0/` endpoints have been replaced by `v1/` counterparts with updated/uniformized syntax. All other endpoints have also moved to `v1/` by default, without syntax changes, but are still available under `v0/` for compatibility.
|
||||
|
||||
|
||||
## Simple migration procedure (takes cluster offline for a while)
|
||||
|
||||
The migration steps are as follows:
|
||||
|
||||
1. Disable API and web access. You may do this by stopping your reverse proxy or by commenting out
|
||||
the `api_bind_addr` values in your `config.toml` file and restarting Garage.
|
||||
2. Do `garage repair --all-nodes --yes tables` and `garage repair --all-nodes --yes blocks`,
|
||||
check the logs and check that all data seems to be synced correctly between
|
||||
nodes. If you have time, do additional checks (`versions`, `block_refs`, etc.)
|
||||
3. Check that the block resync queue and Merkle queue are empty:
|
||||
run `garage stats -a` to query them or inspect metrics in the Grafana dashboard.
|
||||
4. Turn off Garage v0.8
|
||||
5. **Backup the metadata folder of all your nodes!** For instance, use the following command
|
||||
if your metadata directory is `/var/lib/garage/meta`: `cd /var/lib/garage ; tar -acf meta-v0.8.tar.zst meta/`
|
||||
6. Install Garage v0.9
|
||||
7. Update your configuration file if necessary.
|
||||
8. Turn on Garage v0.9
|
||||
9. Do `garage repair --all-nodes --yes tables` and `garage repair --all-nodes --yes blocks`.
|
||||
Wait for a full table sync to run.
|
||||
10. Your upgraded cluster should be in a working state. Re-enable API and Web
|
||||
access and check that everything went well.
|
||||
11. Monitor your cluster in the next hours to see if it works well under your production load, report any issue.
|
||||
12. You might want to assign correct capacity values to all your nodes. Doing so might cause data to be moved
|
||||
in your cluster, which should also be monitored carefully.
|
||||
|
||||
## Minimal downtime migration procedure
|
||||
|
||||
The migration to Garage v0.9 can be done with almost no downtime,
|
||||
by restarting all nodes at once in the new version.
|
||||
|
||||
The migration steps are as follows:
|
||||
|
||||
1. Do `garage repair --all-nodes --yes tables` and `garage repair --all-nodes --yes blocks`,
|
||||
check the logs and check that all data seems to be synced correctly between
|
||||
nodes. If you have time, do additional checks (`versions`, `block_refs`, etc.)
|
||||
|
||||
2. Turn off each node individually; back up its metadata folder (see above); turn it back on again.
|
||||
This will allow you to take a backup of all nodes without impacting global cluster availability.
|
||||
You can do all nodes of a single zone at once as this does not impact the availability of Garage.
|
||||
|
||||
3. Prepare your binaries and configuration files for Garage v0.9
|
||||
|
||||
4. Shut down all v0.8 nodes simultaneously, and restart them all simultaneously in v0.9.
|
||||
Use your favorite deployment tool (Ansible, Kubernetes, Nomad) to achieve this as fast as possible.
|
||||
Garage v0.9 should be in a working state as soon as it starts.
|
||||
|
||||
5. Proceed with repair and monitoring as described in steps 9-12 above.
|
||||
@@ -25,7 +25,8 @@ use crate::admin::bucket::*;
|
||||
use crate::admin::cluster::*;
|
||||
use crate::admin::error::*;
|
||||
use crate::admin::key::*;
|
||||
use crate::admin::router::{Authorization, Endpoint};
|
||||
use crate::admin::router_v0;
|
||||
use crate::admin::router_v1::{Authorization, Endpoint};
|
||||
use crate::helpers::host_to_bucket;
|
||||
|
||||
pub struct AdminApiServer {
|
||||
@@ -229,7 +230,12 @@ impl ApiHandler for AdminApiServer {
|
||||
type Error = Error;
|
||||
|
||||
fn parse_endpoint(&self, req: &Request<Body>) -> Result<Endpoint, Error> {
|
||||
Endpoint::from_request(req)
|
||||
if req.uri().path().starts_with("/v0/") {
|
||||
let endpoint_v0 = router_v0::Endpoint::from_request(req)?;
|
||||
Endpoint::from_v0(endpoint_v0)
|
||||
} else {
|
||||
Endpoint::from_request(req)
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod api_server;
|
||||
mod error;
|
||||
mod router;
|
||||
mod router_v0;
|
||||
mod router_v1;
|
||||
|
||||
mod bucket;
|
||||
mod cluster;
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use hyper::{Method, Request};
|
||||
|
||||
use crate::admin::error::*;
|
||||
use crate::router_macros::*;
|
||||
|
||||
pub enum Authorization {
|
||||
None,
|
||||
MetricsToken,
|
||||
AdminToken,
|
||||
}
|
||||
|
||||
router_match! {@func
|
||||
|
||||
/// List of all Admin API endpoints.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Endpoint {
|
||||
Options,
|
||||
CheckDomain,
|
||||
Health,
|
||||
Metrics,
|
||||
GetClusterStatus,
|
||||
GetClusterHealth,
|
||||
ConnectClusterNodes,
|
||||
// Layout
|
||||
GetClusterLayout,
|
||||
UpdateClusterLayout,
|
||||
ApplyClusterLayout,
|
||||
RevertClusterLayout,
|
||||
// Keys
|
||||
ListKeys,
|
||||
CreateKey,
|
||||
ImportKey,
|
||||
GetKeyInfo {
|
||||
id: Option<String>,
|
||||
search: Option<String>,
|
||||
show_secret_key: Option<String>,
|
||||
},
|
||||
DeleteKey {
|
||||
id: String,
|
||||
},
|
||||
UpdateKey {
|
||||
id: String,
|
||||
},
|
||||
// Buckets
|
||||
ListBuckets,
|
||||
CreateBucket,
|
||||
GetBucketInfo {
|
||||
id: Option<String>,
|
||||
global_alias: Option<String>,
|
||||
},
|
||||
DeleteBucket {
|
||||
id: String,
|
||||
},
|
||||
UpdateBucket {
|
||||
id: String,
|
||||
},
|
||||
// Bucket-Key Permissions
|
||||
BucketAllowKey,
|
||||
BucketDenyKey,
|
||||
// Bucket aliases
|
||||
GlobalAliasBucket {
|
||||
id: String,
|
||||
alias: String,
|
||||
},
|
||||
GlobalUnaliasBucket {
|
||||
id: String,
|
||||
alias: String,
|
||||
},
|
||||
LocalAliasBucket {
|
||||
id: String,
|
||||
access_key_id: String,
|
||||
alias: String,
|
||||
},
|
||||
LocalUnaliasBucket {
|
||||
id: String,
|
||||
access_key_id: String,
|
||||
alias: String,
|
||||
},
|
||||
}}
|
||||
|
||||
impl Endpoint {
|
||||
/// Determine which S3 endpoint a request is for using the request, and a bucket which was
|
||||
/// possibly extracted from the Host header.
|
||||
/// Returns Self plus bucket name, if endpoint is not Endpoint::ListBuckets
|
||||
pub fn from_request<T>(req: &Request<T>) -> Result<Self, Error> {
|
||||
let uri = req.uri();
|
||||
let path = uri.path();
|
||||
let query = uri.query();
|
||||
|
||||
let mut query = QueryParameters::from_query(query.unwrap_or_default())?;
|
||||
|
||||
let res = router_match!(@gen_path_parser (req.method(), path, query) [
|
||||
OPTIONS _ => Options,
|
||||
GET "/check" => CheckDomain,
|
||||
GET "/health" => Health,
|
||||
GET "/metrics" => Metrics,
|
||||
GET "/v1/status" => GetClusterStatus,
|
||||
GET "/v1/health" => GetClusterHealth,
|
||||
POST ("/v0/connect" | "/v1/connect") => ConnectClusterNodes,
|
||||
// Layout endpoints
|
||||
GET "/v1/layout" => GetClusterLayout,
|
||||
POST "/v1/layout" => UpdateClusterLayout,
|
||||
POST "/v1/layout/apply" => ApplyClusterLayout,
|
||||
POST ("/v0/layout/revert" | "/v1/layout/revert") => RevertClusterLayout,
|
||||
// API key endpoints
|
||||
GET "/v1/key" if id => GetKeyInfo (query_opt::id, query_opt::search, query_opt::show_secret_key),
|
||||
GET "/v1/key" if search => GetKeyInfo (query_opt::id, query_opt::search, query_opt::show_secret_key),
|
||||
POST "/v1/key" if id => UpdateKey (query::id),
|
||||
POST "/v1/key" => CreateKey,
|
||||
POST "/v1/key/import" => ImportKey,
|
||||
DELETE ("/v0/key" | "/v1/key") if id => DeleteKey (query::id),
|
||||
GET ("/v0/key" | "/v1/key") => ListKeys,
|
||||
// Bucket endpoints
|
||||
GET ("/v0/bucket" | "/v1/bucket") if id => GetBucketInfo (query_opt::id, query_opt::global_alias),
|
||||
GET ("/v0/bucket" | "/v1/bucket") if global_alias => GetBucketInfo (query_opt::id, query_opt::global_alias),
|
||||
GET ("/v0/bucket" | "/v1/bucket") => ListBuckets,
|
||||
POST ("/v0/bucket" | "/v1/bucket") => CreateBucket,
|
||||
DELETE ("/v0/bucket" | "/v1/bucket") if id => DeleteBucket (query::id),
|
||||
PUT ("/v0/bucket" | "/v1/bucket") if id => UpdateBucket (query::id),
|
||||
// Bucket-key permissions
|
||||
POST ("/v0/bucket/allow" | "/v1/bucket/allow") => BucketAllowKey,
|
||||
POST ("/v0/bucket/deny" | "/v1/bucket/deny") => BucketDenyKey,
|
||||
// Bucket aliases
|
||||
PUT ("/v0/bucket/alias/global" | "/v1/bucket/alias/global") => GlobalAliasBucket (query::id, query::alias),
|
||||
DELETE ("/v0/bucket/alias/global" | "/v1/bucket/alias/global") => GlobalUnaliasBucket (query::id, query::alias),
|
||||
PUT ("/v0/bucket/alias/local" | "/v1/bucket/alias/local") => LocalAliasBucket (query::id, query::access_key_id, query::alias),
|
||||
DELETE ("/v0/bucket/alias/local" | "/v1/bucket/alias/local") => LocalUnaliasBucket (query::id, query::access_key_id, query::alias),
|
||||
]);
|
||||
|
||||
if let Some(message) = query.nonempty_message() {
|
||||
debug!("Unused query parameter: {}", message)
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
/// Get the kind of authorization which is required to perform the operation.
|
||||
pub fn authorization_type(&self) -> Authorization {
|
||||
match self {
|
||||
Self::Health => Authorization::None,
|
||||
Self::CheckDomain => Authorization::None,
|
||||
Self::Metrics => Authorization::MetricsToken,
|
||||
_ => Authorization::AdminToken,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
generateQueryParameters! {
|
||||
keywords: [],
|
||||
fields: [
|
||||
"format" => format,
|
||||
"id" => id,
|
||||
"search" => search,
|
||||
"globalAlias" => global_alias,
|
||||
"alias" => alias,
|
||||
"accessKeyId" => access_key_id,
|
||||
"showSecretKey" => show_secret_key
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use hyper::{Method, Request};
|
||||
|
||||
use crate::admin::error::*;
|
||||
use crate::router_macros::*;
|
||||
|
||||
router_match! {@func
|
||||
|
||||
/// List of all Admin API endpoints.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Endpoint {
|
||||
Options,
|
||||
CheckDomain,
|
||||
Health,
|
||||
Metrics,
|
||||
GetClusterStatus,
|
||||
GetClusterHealth,
|
||||
ConnectClusterNodes,
|
||||
// Layout
|
||||
GetClusterLayout,
|
||||
UpdateClusterLayout,
|
||||
ApplyClusterLayout,
|
||||
RevertClusterLayout,
|
||||
// Keys
|
||||
ListKeys,
|
||||
CreateKey,
|
||||
ImportKey,
|
||||
GetKeyInfo {
|
||||
id: Option<String>,
|
||||
search: Option<String>,
|
||||
},
|
||||
DeleteKey {
|
||||
id: String,
|
||||
},
|
||||
UpdateKey {
|
||||
id: String,
|
||||
},
|
||||
// Buckets
|
||||
ListBuckets,
|
||||
CreateBucket,
|
||||
GetBucketInfo {
|
||||
id: Option<String>,
|
||||
global_alias: Option<String>,
|
||||
},
|
||||
DeleteBucket {
|
||||
id: String,
|
||||
},
|
||||
UpdateBucket {
|
||||
id: String,
|
||||
},
|
||||
// Bucket-Key Permissions
|
||||
BucketAllowKey,
|
||||
BucketDenyKey,
|
||||
// Bucket aliases
|
||||
GlobalAliasBucket {
|
||||
id: String,
|
||||
alias: String,
|
||||
},
|
||||
GlobalUnaliasBucket {
|
||||
id: String,
|
||||
alias: String,
|
||||
},
|
||||
LocalAliasBucket {
|
||||
id: String,
|
||||
access_key_id: String,
|
||||
alias: String,
|
||||
},
|
||||
LocalUnaliasBucket {
|
||||
id: String,
|
||||
access_key_id: String,
|
||||
alias: String,
|
||||
},
|
||||
}}
|
||||
|
||||
impl Endpoint {
|
||||
/// Determine which S3 endpoint a request is for using the request, and a bucket which was
|
||||
/// possibly extracted from the Host header.
|
||||
/// Returns Self plus bucket name, if endpoint is not Endpoint::ListBuckets
|
||||
pub fn from_request<T>(req: &Request<T>) -> Result<Self, Error> {
|
||||
let uri = req.uri();
|
||||
let path = uri.path();
|
||||
let query = uri.query();
|
||||
|
||||
let mut query = QueryParameters::from_query(query.unwrap_or_default())?;
|
||||
|
||||
let res = router_match!(@gen_path_parser (req.method(), path, query) [
|
||||
OPTIONS _ => Options,
|
||||
GET "/check" => CheckDomain,
|
||||
GET "/health" => Health,
|
||||
GET "/metrics" => Metrics,
|
||||
GET "/v0/status" => GetClusterStatus,
|
||||
GET "/v0/health" => GetClusterHealth,
|
||||
POST "/v0/connect" => ConnectClusterNodes,
|
||||
// Layout endpoints
|
||||
GET "/v0/layout" => GetClusterLayout,
|
||||
POST "/v0/layout" => UpdateClusterLayout,
|
||||
POST "/v0/layout/apply" => ApplyClusterLayout,
|
||||
POST "/v0/layout/revert" => RevertClusterLayout,
|
||||
// API key endpoints
|
||||
GET "/v0/key" if id => GetKeyInfo (query_opt::id, query_opt::search),
|
||||
GET "/v0/key" if search => GetKeyInfo (query_opt::id, query_opt::search),
|
||||
POST "/v0/key" if id => UpdateKey (query::id),
|
||||
POST "/v0/key" => CreateKey,
|
||||
POST "/v0/key/import" => ImportKey,
|
||||
DELETE "/v0/key" if id => DeleteKey (query::id),
|
||||
GET "/v0/key" => ListKeys,
|
||||
// Bucket endpoints
|
||||
GET "/v0/bucket" if id => GetBucketInfo (query_opt::id, query_opt::global_alias),
|
||||
GET "/v0/bucket" if global_alias => GetBucketInfo (query_opt::id, query_opt::global_alias),
|
||||
GET "/v0/bucket" => ListBuckets,
|
||||
POST "/v0/bucket" => CreateBucket,
|
||||
DELETE "/v0/bucket" if id => DeleteBucket (query::id),
|
||||
PUT "/v0/bucket" if id => UpdateBucket (query::id),
|
||||
// Bucket-key permissions
|
||||
POST "/v0/bucket/allow" => BucketAllowKey,
|
||||
POST "/v0/bucket/deny" => BucketDenyKey,
|
||||
// Bucket aliases
|
||||
PUT "/v0/bucket/alias/global" => GlobalAliasBucket (query::id, query::alias),
|
||||
DELETE "/v0/bucket/alias/global" => GlobalUnaliasBucket (query::id, query::alias),
|
||||
PUT "/v0/bucket/alias/local" => LocalAliasBucket (query::id, query::access_key_id, query::alias),
|
||||
DELETE "/v0/bucket/alias/local" => LocalUnaliasBucket (query::id, query::access_key_id, query::alias),
|
||||
]);
|
||||
|
||||
if let Some(message) = query.nonempty_message() {
|
||||
debug!("Unused query parameter: {}", message)
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
generateQueryParameters! {
|
||||
keywords: [],
|
||||
fields: [
|
||||
"format" => format,
|
||||
"id" => id,
|
||||
"search" => search,
|
||||
"globalAlias" => global_alias,
|
||||
"alias" => alias,
|
||||
"accessKeyId" => access_key_id
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use hyper::{Method, Request};
|
||||
|
||||
use crate::admin::error::*;
|
||||
use crate::admin::router_v0;
|
||||
use crate::router_macros::*;
|
||||
|
||||
pub enum Authorization {
|
||||
None,
|
||||
MetricsToken,
|
||||
AdminToken,
|
||||
}
|
||||
|
||||
router_match! {@func
|
||||
|
||||
/// List of all Admin API endpoints.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Endpoint {
|
||||
Options,
|
||||
CheckDomain,
|
||||
Health,
|
||||
Metrics,
|
||||
GetClusterStatus,
|
||||
GetClusterHealth,
|
||||
ConnectClusterNodes,
|
||||
// Layout
|
||||
GetClusterLayout,
|
||||
UpdateClusterLayout,
|
||||
ApplyClusterLayout,
|
||||
RevertClusterLayout,
|
||||
// Keys
|
||||
ListKeys,
|
||||
CreateKey,
|
||||
ImportKey,
|
||||
GetKeyInfo {
|
||||
id: Option<String>,
|
||||
search: Option<String>,
|
||||
show_secret_key: Option<String>,
|
||||
},
|
||||
DeleteKey {
|
||||
id: String,
|
||||
},
|
||||
UpdateKey {
|
||||
id: String,
|
||||
},
|
||||
// Buckets
|
||||
ListBuckets,
|
||||
CreateBucket,
|
||||
GetBucketInfo {
|
||||
id: Option<String>,
|
||||
global_alias: Option<String>,
|
||||
},
|
||||
DeleteBucket {
|
||||
id: String,
|
||||
},
|
||||
UpdateBucket {
|
||||
id: String,
|
||||
},
|
||||
// Bucket-Key Permissions
|
||||
BucketAllowKey,
|
||||
BucketDenyKey,
|
||||
// Bucket aliases
|
||||
GlobalAliasBucket {
|
||||
id: String,
|
||||
alias: String,
|
||||
},
|
||||
GlobalUnaliasBucket {
|
||||
id: String,
|
||||
alias: String,
|
||||
},
|
||||
LocalAliasBucket {
|
||||
id: String,
|
||||
access_key_id: String,
|
||||
alias: String,
|
||||
},
|
||||
LocalUnaliasBucket {
|
||||
id: String,
|
||||
access_key_id: String,
|
||||
alias: String,
|
||||
},
|
||||
}}
|
||||
|
||||
impl Endpoint {
|
||||
/// Determine which S3 endpoint a request is for using the request, and a bucket which was
|
||||
/// possibly extracted from the Host header.
|
||||
/// Returns Self plus bucket name, if endpoint is not Endpoint::ListBuckets
|
||||
pub fn from_request<T>(req: &Request<T>) -> Result<Self, Error> {
|
||||
let uri = req.uri();
|
||||
let path = uri.path();
|
||||
let query = uri.query();
|
||||
|
||||
let mut query = QueryParameters::from_query(query.unwrap_or_default())?;
|
||||
|
||||
let res = router_match!(@gen_path_parser (req.method(), path, query) [
|
||||
OPTIONS _ => Options,
|
||||
GET "/check" => CheckDomain,
|
||||
GET "/health" => Health,
|
||||
GET "/metrics" => Metrics,
|
||||
GET "/v1/status" => GetClusterStatus,
|
||||
GET "/v1/health" => GetClusterHealth,
|
||||
POST "/v1/connect" => ConnectClusterNodes,
|
||||
// Layout endpoints
|
||||
GET "/v1/layout" => GetClusterLayout,
|
||||
POST "/v1/layout" => UpdateClusterLayout,
|
||||
POST "/v1/layout/apply" => ApplyClusterLayout,
|
||||
POST "/v1/layout/revert" => RevertClusterLayout,
|
||||
// API key endpoints
|
||||
GET "/v1/key" if id => GetKeyInfo (query_opt::id, query_opt::search, query_opt::show_secret_key),
|
||||
GET "/v1/key" if search => GetKeyInfo (query_opt::id, query_opt::search, query_opt::show_secret_key),
|
||||
POST "/v1/key" if id => UpdateKey (query::id),
|
||||
POST "/v1/key" => CreateKey,
|
||||
POST "/v1/key/import" => ImportKey,
|
||||
DELETE "/v1/key" if id => DeleteKey (query::id),
|
||||
GET "/v1/key" => ListKeys,
|
||||
// Bucket endpoints
|
||||
GET "/v1/bucket" if id => GetBucketInfo (query_opt::id, query_opt::global_alias),
|
||||
GET "/v1/bucket" if global_alias => GetBucketInfo (query_opt::id, query_opt::global_alias),
|
||||
GET "/v1/bucket" => ListBuckets,
|
||||
POST "/v1/bucket" => CreateBucket,
|
||||
DELETE "/v1/bucket" if id => DeleteBucket (query::id),
|
||||
PUT "/v1/bucket" if id => UpdateBucket (query::id),
|
||||
// Bucket-key permissions
|
||||
POST "/v1/bucket/allow" => BucketAllowKey,
|
||||
POST "/v1/bucket/deny" => BucketDenyKey,
|
||||
// Bucket aliases
|
||||
PUT "/v1/bucket/alias/global" => GlobalAliasBucket (query::id, query::alias),
|
||||
DELETE "/v1/bucket/alias/global" => GlobalUnaliasBucket (query::id, query::alias),
|
||||
PUT "/v1/bucket/alias/local" => LocalAliasBucket (query::id, query::access_key_id, query::alias),
|
||||
DELETE "/v1/bucket/alias/local" => LocalUnaliasBucket (query::id, query::access_key_id, query::alias),
|
||||
]);
|
||||
|
||||
if let Some(message) = query.nonempty_message() {
|
||||
debug!("Unused query parameter: {}", message)
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
/// Some endpoints work exactly the same in their v1/ version as they did in their v0/ version.
|
||||
/// For these endpoints, we can convert a v0/ call to its equivalent as if it was made using
|
||||
/// its v1/ URL.
|
||||
pub fn from_v0(v0_endpoint: router_v0::Endpoint) -> Result<Self, Error> {
|
||||
match v0_endpoint {
|
||||
// Cluster endpoints
|
||||
router_v0::Endpoint::ConnectClusterNodes => Ok(Self::ConnectClusterNodes),
|
||||
// - GetClusterStatus: response format changed
|
||||
// - GetClusterHealth: response format changed
|
||||
|
||||
// Layout endpoints
|
||||
router_v0::Endpoint::RevertClusterLayout => Ok(Self::RevertClusterLayout),
|
||||
// - GetClusterLayout: response format changed
|
||||
// - UpdateClusterLayout: query format changed
|
||||
// - ApplyCusterLayout: response format changed
|
||||
|
||||
// Key endpoints
|
||||
router_v0::Endpoint::ListKeys => Ok(Self::ListKeys),
|
||||
router_v0::Endpoint::CreateKey => Ok(Self::CreateKey),
|
||||
router_v0::Endpoint::GetKeyInfo { id, search } => Ok(Self::GetKeyInfo {
|
||||
id,
|
||||
search,
|
||||
show_secret_key: Some("true".into()),
|
||||
}),
|
||||
router_v0::Endpoint::DeleteKey { id } => Ok(Self::DeleteKey { id }),
|
||||
// - UpdateKey: response format changed (secret key no longer returned)
|
||||
|
||||
// Bucket endpoints
|
||||
router_v0::Endpoint::GetBucketInfo { id, global_alias } => {
|
||||
Ok(Self::GetBucketInfo { id, global_alias })
|
||||
}
|
||||
router_v0::Endpoint::ListBuckets => Ok(Self::ListBuckets),
|
||||
router_v0::Endpoint::CreateBucket => Ok(Self::CreateBucket),
|
||||
router_v0::Endpoint::DeleteBucket { id } => Ok(Self::DeleteBucket { id }),
|
||||
router_v0::Endpoint::UpdateBucket { id } => Ok(Self::UpdateBucket { id }),
|
||||
|
||||
// Bucket-key permissions
|
||||
router_v0::Endpoint::BucketAllowKey => Ok(Self::BucketAllowKey),
|
||||
router_v0::Endpoint::BucketDenyKey => Ok(Self::BucketDenyKey),
|
||||
|
||||
// Bucket alias endpoints
|
||||
router_v0::Endpoint::GlobalAliasBucket { id, alias } => {
|
||||
Ok(Self::GlobalAliasBucket { id, alias })
|
||||
}
|
||||
router_v0::Endpoint::GlobalUnaliasBucket { id, alias } => {
|
||||
Ok(Self::GlobalUnaliasBucket { id, alias })
|
||||
}
|
||||
router_v0::Endpoint::LocalAliasBucket {
|
||||
id,
|
||||
access_key_id,
|
||||
alias,
|
||||
} => Ok(Self::LocalAliasBucket {
|
||||
id,
|
||||
access_key_id,
|
||||
alias,
|
||||
}),
|
||||
router_v0::Endpoint::LocalUnaliasBucket {
|
||||
id,
|
||||
access_key_id,
|
||||
alias,
|
||||
} => Ok(Self::LocalUnaliasBucket {
|
||||
id,
|
||||
access_key_id,
|
||||
alias,
|
||||
}),
|
||||
|
||||
// For endpoints that have different body content syntax, issue
|
||||
// deprecation warning
|
||||
_ => Err(Error::bad_request(format!(
|
||||
"v0/ endpoint is no longer supported: {}",
|
||||
v0_endpoint.name()
|
||||
))),
|
||||
}
|
||||
}
|
||||
/// Get the kind of authorization which is required to perform the operation.
|
||||
pub fn authorization_type(&self) -> Authorization {
|
||||
match self {
|
||||
Self::Health => Authorization::None,
|
||||
Self::CheckDomain => Authorization::None,
|
||||
Self::Metrics => Authorization::MetricsToken,
|
||||
_ => Authorization::AdminToken,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
generateQueryParameters! {
|
||||
keywords: [],
|
||||
fields: [
|
||||
"format" => format,
|
||||
"id" => id,
|
||||
"search" => search,
|
||||
"globalAlias" => global_alias,
|
||||
"alias" => alias,
|
||||
"accessKeyId" => access_key_id,
|
||||
"showSecretKey" => show_secret_key
|
||||
]
|
||||
}
|
||||
@@ -453,7 +453,7 @@ pub enum RepairWhat {
|
||||
/// Do a full sync of metadata tables
|
||||
#[structopt(name = "tables", version = garage_version())]
|
||||
Tables,
|
||||
/// Repair (resync/rebalance) the set of stored blocks
|
||||
/// Repair (resync/rebalance) the set of stored blocks in the cluster
|
||||
#[structopt(name = "blocks", version = garage_version())]
|
||||
Blocks,
|
||||
/// Repropagate object deletions to the version table
|
||||
@@ -471,7 +471,7 @@ pub enum RepairWhat {
|
||||
#[structopt(subcommand)]
|
||||
cmd: ScrubCmd,
|
||||
},
|
||||
/// Rebalance data blocks among storage locations
|
||||
/// Rebalance data blocks among HDDs on individual nodes
|
||||
#[structopt(name = "rebalance", version = garage_version())]
|
||||
Rebalance,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user