mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-28 16:07:05 +00:00
Merge branch 'main' of github.com:rustfs/s3-rustfs into feature/observability-metrics
# Conflicts: # .docker/observability/config/obs.toml # scripts/run.sh
This commit is contained in:
@@ -6,7 +6,7 @@ This directory contains the observability stack for the application. The stack i
|
|||||||
- Grafana 11.6.0
|
- Grafana 11.6.0
|
||||||
- Loki 3.4.2
|
- Loki 3.4.2
|
||||||
- Jaeger 2.4.0
|
- Jaeger 2.4.0
|
||||||
- Otel Collector 0.120.0 #0.121.0 remove loki
|
- Otel Collector 0.120.0 # 0.121.0 remove loki
|
||||||
|
|
||||||
## Prometheus
|
## Prometheus
|
||||||
|
|
||||||
@@ -47,8 +47,16 @@ observability data formats (e.g. Jaeger, Prometheus, etc.) sending to one or mor
|
|||||||
|
|
||||||
To deploy the observability stack, run the following command:
|
To deploy the observability stack, run the following command:
|
||||||
|
|
||||||
|
- docker latest version
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose -f docker-compose.yml -f docker-compose.override.yml up -d
|
docker compose -f docker-compose.yml -f docker-compose.override.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
- docker compose v2.0.0 or before
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docke-compose -f docker-compose.yml -f docker-compose.override.yml up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
To access the Grafana dashboard, navigate to `http://localhost:3000` in your browser. The default username and password
|
To access the Grafana dashboard, navigate to `http://localhost:3000` in your browser. The default username and password
|
||||||
@@ -63,7 +71,7 @@ To access the Prometheus dashboard, navigate to `http://localhost:9090` in your
|
|||||||
To stop the observability stack, run the following command:
|
To stop the observability stack, run the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose -f docker-compose.yml -f docker-compose.override.yml down
|
docker compose -f docker-compose.yml -f docker-compose.override.yml down
|
||||||
```
|
```
|
||||||
|
|
||||||
## How to remove data
|
## How to remove data
|
||||||
@@ -71,7 +79,7 @@ docker-compose -f docker-compose.yml -f docker-compose.override.yml down
|
|||||||
To remove the data generated by the observability stack, run the following command:
|
To remove the data generated by the observability stack, run the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose -f docker-compose.yml -f docker-compose.override.yml down -v
|
docker compose -f docker-compose.yml -f docker-compose.override.yml down -v
|
||||||
```
|
```
|
||||||
|
|
||||||
## How to configure
|
## How to configure
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
## 部署可观测性系统
|
||||||
|
|
||||||
|
OpenTelemetry Collector 提供了一个厂商中立的遥测数据处理方案,用于接收、处理和导出遥测数据。它消除了为支持多种开源可观测性数据格式(如
|
||||||
|
Jaeger、Prometheus 等)而需要运行和维护多个代理/收集器的必要性。
|
||||||
|
|
||||||
|
### 快速部署
|
||||||
|
|
||||||
|
1. 进入 `.docker/observability` 目录
|
||||||
|
2. 执行以下命令启动服务:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d -f docker-compose.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 访问监控面板
|
||||||
|
|
||||||
|
服务启动后,可通过以下地址访问各个监控面板:
|
||||||
|
|
||||||
|
- Grafana: `http://localhost:3000` (默认账号/密码:`admin`/`admin`)
|
||||||
|
- Jaeger: `http://localhost:16686`
|
||||||
|
- Prometheus: `http://localhost:9090`
|
||||||
|
|
||||||
|
## 配置可观测性
|
||||||
|
|
||||||
|
### 创建配置文件
|
||||||
|
|
||||||
|
1. 进入 `deploy/config` 目录
|
||||||
|
2. 复制示例配置:`cp obs.toml.example obs.toml`
|
||||||
|
3. 编辑 `obs.toml` 配置文件,修改以下关键参数:
|
||||||
|
|
||||||
|
| 配置项 | 说明 | 示例值 |
|
||||||
|
|-----------------|----------------------------|-----------------------|
|
||||||
|
| endpoint | OpenTelemetry Collector 地址 | http://localhost:4317 |
|
||||||
|
| service_name | 服务名称 | rustfs |
|
||||||
|
| service_version | 服务版本 | 1.0.0 |
|
||||||
|
| environment | 运行环境 | production |
|
||||||
|
| meter_interval | 指标导出间隔 (秒) | 30 |
|
||||||
|
| sample_ratio | 采样率 | 1.0 |
|
||||||
|
| use_stdout | 是否输出到控制台 | true/false |
|
||||||
|
| logger_level | 日志级别 | info |
|
||||||
|
|
||||||
|
```
|
||||||
@@ -7,6 +7,7 @@ service_name = "rustfs"
|
|||||||
service_version = "0.1.0"
|
service_version = "0.1.0"
|
||||||
environments = "production"
|
environments = "production"
|
||||||
logger_level = "debug"
|
logger_level = "debug"
|
||||||
|
local_logging_enabled = true
|
||||||
|
|
||||||
[sinks]
|
[sinks]
|
||||||
[sinks.kafka] # Kafka sink is disabled by default
|
[sinks.kafka] # Kafka sink is disabled by default
|
||||||
|
|||||||
@@ -37,9 +37,14 @@ extensions:
|
|||||||
traces_archive: another_store
|
traces_archive: another_store
|
||||||
ui:
|
ui:
|
||||||
config_file: ./cmd/jaeger/config-ui.json
|
config_file: ./cmd/jaeger/config-ui.json
|
||||||
|
log_access: true
|
||||||
# The maximum duration that is considered for clock skew adjustments.
|
# The maximum duration that is considered for clock skew adjustments.
|
||||||
# Defaults to 0 seconds, which means it's disabled.
|
# Defaults to 0 seconds, which means it's disabled.
|
||||||
max_clock_skew_adjust: 0s
|
max_clock_skew_adjust: 0s
|
||||||
|
grpc:
|
||||||
|
endpoint: 0.0.0.0:16685
|
||||||
|
http:
|
||||||
|
endpoint: 0.0.0.0:16686
|
||||||
|
|
||||||
jaeger_storage:
|
jaeger_storage:
|
||||||
backends:
|
backends:
|
||||||
@@ -49,6 +54,12 @@ extensions:
|
|||||||
another_store:
|
another_store:
|
||||||
memory:
|
memory:
|
||||||
max_traces: 100000
|
max_traces: 100000
|
||||||
|
metric_backends:
|
||||||
|
some_metrics_storage:
|
||||||
|
prometheus:
|
||||||
|
endpoint: http://prometheus:9090
|
||||||
|
normalize_calls: true
|
||||||
|
normalize_duration: true
|
||||||
|
|
||||||
remote_sampling:
|
remote_sampling:
|
||||||
# You can either use file or adaptive sampling strategy in remote_sampling
|
# You can either use file or adaptive sampling strategy in remote_sampling
|
||||||
|
|||||||
@@ -106,7 +106,6 @@ opentelemetry-otlp = { version = "0.29.0" }
|
|||||||
opentelemetry-semantic-conventions = { version = "0.29.0", features = ["semconv_experimental"] }
|
opentelemetry-semantic-conventions = { version = "0.29.0", features = ["semconv_experimental"] }
|
||||||
parking_lot = "0.12.3"
|
parking_lot = "0.12.3"
|
||||||
pin-project-lite = "0.2.16"
|
pin-project-lite = "0.2.16"
|
||||||
prometheus = "0.14.0"
|
|
||||||
# pin-utils = "0.1.0"
|
# pin-utils = "0.1.0"
|
||||||
prost = "0.13.5"
|
prost = "0.13.5"
|
||||||
prost-build = "0.13.5"
|
prost-build = "0.13.5"
|
||||||
|
|||||||
@@ -1,54 +1,68 @@
|
|||||||
# How to compile RustFS
|
# RustFS
|
||||||
|
|
||||||
| Must package | Version | download link |
|
## English Documentation |[中文文档](README_ZH.md)
|
||||||
|--------------|---------|----------------------------------------------------------------------------------------------------------------------------------|
|
|
||||||
| Rust | 1.8.5 | https://www.rust-lang.org/tools/install |
|
|
||||||
| protoc | 30.2 | [protoc-30.2-linux-x86_64.zip](https://github.com/protocolbuffers/protobuf/releases/download/v30.2/protoc-30.2-linux-x86_64.zip) |
|
|
||||||
| flatc | 24.0+ | [Linux.flatc.binary.g++-13.zip](https://github.com/google/flatbuffers/releases/download/v25.2.10/Linux.flatc.binary.g++-13.zip) |
|
|
||||||
|
|
||||||
Download Links:
|
### Prerequisites
|
||||||
|
|
||||||
https://github.com/google/flatbuffers/releases/download/v25.2.10/Linux.flatc.binary.g++-13.zip
|
| Package | Version | Download Link |
|
||||||
|
|---------|---------|----------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| Rust | 1.8.5+ | [rust-lang.org/tools/install](https://www.rust-lang.org/tools/install) |
|
||||||
|
| protoc | 30.2+ | [protoc-30.2-linux-x86_64.zip](https://github.com/protocolbuffers/protobuf/releases/download/v30.2/protoc-30.2-linux-x86_64.zip) |
|
||||||
|
| flatc | 24.0+ | [Linux.flatc.binary.g++-13.zip](https://github.com/google/flatbuffers/releases/download/v25.2.10/Linux.flatc.binary.g++-13.zip) |
|
||||||
|
|
||||||
https://github.com/protocolbuffers/protobuf/releases/download/v30.2/protoc-30.2-linux-x86_64.zip
|
### Building RustFS
|
||||||
|
|
||||||
generate protobuf code:
|
#### Generate Protobuf Code
|
||||||
|
|
||||||
```cargo run --bin gproto```
|
```bash
|
||||||
|
cargo run --bin gproto
|
||||||
|
```
|
||||||
|
|
||||||
Or use Docker:
|
#### Using Docker for Prerequisites
|
||||||
|
|
||||||
```yml
|
```yaml
|
||||||
- uses: arduino/setup-protoc@v3
|
- uses: arduino/setup-protoc@v3
|
||||||
with:
|
with:
|
||||||
version: "30.2"
|
version: "30.2"
|
||||||
|
|
||||||
- uses: Nugine/setup-flatc@v1
|
- uses: Nugine/setup-flatc@v1
|
||||||
with:
|
with:
|
||||||
version: "25.2.10"
|
version: "25.2.10"
|
||||||
```
|
```
|
||||||
|
|
||||||
# How to add Console web
|
#### Adding Console Web UI
|
||||||
|
|
||||||
1. `wget https://dl.rustfs.com/artifacts/console/rustfs-console-latest.zip`
|
1. Download the latest console UI:
|
||||||
|
```bash
|
||||||
|
wget https://dl.rustfs.com/artifacts/console/rustfs-console-latest.zip
|
||||||
|
```
|
||||||
|
2. Create the static directory:
|
||||||
|
```bash
|
||||||
|
mkdir -p ./rustfs/static
|
||||||
|
```
|
||||||
|
3. Extract and compile RustFS:
|
||||||
|
```bash
|
||||||
|
unzip rustfs-console-latest.zip -d ./rustfs/static
|
||||||
|
cargo build
|
||||||
|
```
|
||||||
|
|
||||||
2. mkdir in this repos folder `./rustfs/static`
|
### Running RustFS
|
||||||
|
|
||||||
3. Compile RustFS
|
#### Configuration
|
||||||
|
|
||||||
# Star RustFS
|
Set the required environment variables:
|
||||||
|
|
||||||
Add Env Information:
|
```bash
|
||||||
|
# Basic config
|
||||||
```
|
|
||||||
export RUSTFS_VOLUMES="./target/volume/test"
|
export RUSTFS_VOLUMES="./target/volume/test"
|
||||||
export RUSTFS_ADDRESS="0.0.0.0:9000"
|
export RUSTFS_ADDRESS="0.0.0.0:9000"
|
||||||
export RUSTFS_CONSOLE_ENABLE=true
|
export RUSTFS_CONSOLE_ENABLE=true
|
||||||
export RUSTFS_CONSOLE_ADDRESS="0.0.0.0:9001"
|
export RUSTFS_CONSOLE_ADDRESS="0.0.0.0:9001"
|
||||||
# 具体路径修改为配置文件真实路径,obs.example.toml 仅供参考 其中`RUSTFS_OBS_CONFIG` 和下面变量二选一
|
|
||||||
export RUSTFS_OBS_CONFIG="./deploy/config/obs.example.toml"
|
|
||||||
|
|
||||||
# 如下变量需要必须参数都有值才可以,以及会覆盖配置文件`obs.example.toml`中的值
|
# Observability config (option 1: config file)
|
||||||
|
export RUSTFS_OBS_CONFIG="./deploy/config/obs.toml"
|
||||||
|
|
||||||
|
# Observability config (option 2: environment variables)
|
||||||
export RUSTFS__OBSERVABILITY__ENDPOINT=http://localhost:4317
|
export RUSTFS__OBSERVABILITY__ENDPOINT=http://localhost:4317
|
||||||
export RUSTFS__OBSERVABILITY__USE_STDOUT=true
|
export RUSTFS__OBSERVABILITY__USE_STDOUT=true
|
||||||
export RUSTFS__OBSERVABILITY__SAMPLE_RATIO=2.0
|
export RUSTFS__OBSERVABILITY__SAMPLE_RATIO=2.0
|
||||||
@@ -57,6 +71,9 @@ export RUSTFS__OBSERVABILITY__SERVICE_NAME=rustfs
|
|||||||
export RUSTFS__OBSERVABILITY__SERVICE_VERSION=0.1.0
|
export RUSTFS__OBSERVABILITY__SERVICE_VERSION=0.1.0
|
||||||
export RUSTFS__OBSERVABILITY__ENVIRONMENT=develop
|
export RUSTFS__OBSERVABILITY__ENVIRONMENT=develop
|
||||||
export RUSTFS__OBSERVABILITY__LOGGER_LEVEL=info
|
export RUSTFS__OBSERVABILITY__LOGGER_LEVEL=info
|
||||||
|
export RUSTFS__OBSERVABILITY__LOCAL_LOGGING_ENABLED=true
|
||||||
|
|
||||||
|
# Logging sinks
|
||||||
export RUSTFS__SINKS__FILE__ENABLED=true
|
export RUSTFS__SINKS__FILE__ENABLED=true
|
||||||
export RUSTFS__SINKS__FILE__PATH="./deploy/logs/rustfs.log"
|
export RUSTFS__SINKS__FILE__PATH="./deploy/logs/rustfs.log"
|
||||||
export RUSTFS__SINKS__WEBHOOK__ENABLED=false
|
export RUSTFS__SINKS__WEBHOOK__ENABLED=false
|
||||||
@@ -68,54 +85,50 @@ export RUSTFS__SINKS__KAFKA__TOPIC=""
|
|||||||
export RUSTFS__LOGGER__QUEUE_CAPACITY=10
|
export RUSTFS__LOGGER__QUEUE_CAPACITY=10
|
||||||
```
|
```
|
||||||
|
|
||||||
You need replace your real data folder:
|
#### Start the service
|
||||||
|
|
||||||
```
|
```bash
|
||||||
./rustfs /data/rustfs
|
./rustfs /data/rustfs
|
||||||
```
|
```
|
||||||
|
|
||||||
## How to deploy the observability stack
|
### Observability Stack
|
||||||
|
|
||||||
The OpenTelemetry Collector offers a vendor-agnostic implementation on how to receive, process, and export telemetry
|
#### Deployment
|
||||||
data. It removes the need to run, operate, and maintain multiple agents/collectors in order to support open-source
|
|
||||||
observability data formats (e.g. Jaeger, Prometheus, etc.) sending to one or more open-source or commercial back-ends.
|
|
||||||
|
|
||||||
1. Enter the `.docker/observability` directory,
|
1. Navigate to the observability directory:
|
||||||
2. Run the following command:
|
```bash
|
||||||
|
cd .docker/observability
|
||||||
|
```
|
||||||
|
|
||||||
```bash
|
2. Start the observability stack:
|
||||||
docker-compose -f docker-compose.yml up -d
|
```bash
|
||||||
```
|
docker compose up -d -f docker-compose.yml
|
||||||
|
```
|
||||||
|
|
||||||
3. Access the Grafana dashboard by navigating to `http://localhost:3000` in your browser. The default username and
|
#### Access Monitoring Dashboards
|
||||||
password are `admin` and `admin`, respectively.
|
|
||||||
|
|
||||||
4. Access the Jaeger dashboard by navigating to `http://localhost:16686` in your browser.
|
- Grafana: `http://localhost:3000` (credentials: `admin`/`admin`)
|
||||||
|
- Jaeger: `http://localhost:16686`
|
||||||
|
- Prometheus: `http://localhost:9090`
|
||||||
|
|
||||||
5. Access the Prometheus dashboard by navigating to `http://localhost:9090` in your browser.
|
#### Configuring Observability
|
||||||
|
|
||||||
## Create a new Observability configuration file
|
1. Copy the example configuration:
|
||||||
|
```bash
|
||||||
#### 1. Enter the `deploy/config` directory,
|
cd deploy/config
|
||||||
|
cp obs.toml.example obs.toml
|
||||||
#### 2. Copy `obs.toml.example` to `obs.toml`
|
```
|
||||||
|
|
||||||
#### 3. Modify the `obs.toml` configuration file
|
|
||||||
|
|
||||||
##### 3.1. Modify the `endpoint` value to the address of the OpenTelemetry Collector
|
|
||||||
|
|
||||||
##### 3.2. Modify the `service_name` value to the name of the service
|
|
||||||
|
|
||||||
##### 3.3. Modify the `service_version` value to the version of the service
|
|
||||||
|
|
||||||
##### 3.4. Modify the `environment` value to the environment of the service
|
|
||||||
|
|
||||||
##### 3.5. Modify the `meter_interval` value to export interval
|
|
||||||
|
|
||||||
##### 3.6. Modify the `sample_ratio` value to the sample ratio
|
|
||||||
|
|
||||||
##### 3.7. Modify the `use_stdout` value to export to stdout
|
|
||||||
|
|
||||||
##### 3.8. Modify the `logger_level` value to the logger level
|
|
||||||
|
|
||||||
|
2. Edit `obs.toml` with the following parameters:
|
||||||
|
|
||||||
|
| Parameter | Description | Example |
|
||||||
|
|----------------------|-----------------------------------|-----------------------|
|
||||||
|
| endpoint | OpenTelemetry Collector address | http://localhost:4317 |
|
||||||
|
| service_name | Service name | rustfs |
|
||||||
|
| service_version | Service version | 1.0.0 |
|
||||||
|
| environment | Runtime environment | production |
|
||||||
|
| meter_interval | Metrics export interval (seconds) | 30 |
|
||||||
|
| sample_ratio | Sampling ratio | 1.0 |
|
||||||
|
| use_stdout | Output to console | true/false |
|
||||||
|
| logger_level | Log level | info |
|
||||||
|
| local_logging_enable | stdout | true/false |
|
||||||
|
|||||||
+136
@@ -0,0 +1,136 @@
|
|||||||
|
# RustFS
|
||||||
|
|
||||||
|
## [English Documentation](README.md) |中文文档
|
||||||
|
|
||||||
|
### 前置要求
|
||||||
|
|
||||||
|
| 软件包 | 版本 | 下载链接 |
|
||||||
|
|--------|--------|----------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| Rust | 1.8.5+ | [rust-lang.org/tools/install](https://www.rust-lang.org/tools/install) |
|
||||||
|
| protoc | 30.2+ | [protoc-30.2-linux-x86_64.zip](https://github.com/protocolbuffers/protobuf/releases/download/v30.2/protoc-30.2-linux-x86_64.zip) |
|
||||||
|
| flatc | 24.0+ | [Linux.flatc.binary.g++-13.zip](https://github.com/google/flatbuffers/releases/download/v25.2.10/Linux.flatc.binary.g++-13.zip) |
|
||||||
|
|
||||||
|
### 构建 RustFS
|
||||||
|
|
||||||
|
#### 生成 Protobuf 代码
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run --bin gproto
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 使用 Docker 安装依赖
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: arduino/setup-protoc@v3
|
||||||
|
with:
|
||||||
|
version: "30.2"
|
||||||
|
|
||||||
|
- uses: Nugine/setup-flatc@v1
|
||||||
|
with:
|
||||||
|
version: "25.2.10"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 添加控制台 Web UI
|
||||||
|
|
||||||
|
1. 下载最新的控制台 UI:
|
||||||
|
```bash
|
||||||
|
wget https://dl.rustfs.com/artifacts/console/rustfs-console-latest.zip
|
||||||
|
```
|
||||||
|
2. 创建静态资源目录:
|
||||||
|
```bash
|
||||||
|
mkdir -p ./rustfs/static
|
||||||
|
```
|
||||||
|
3. 解压并编译 RustFS:
|
||||||
|
```bash
|
||||||
|
unzip rustfs-console-latest.zip -d ./rustfs/static
|
||||||
|
cargo build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行 RustFS
|
||||||
|
|
||||||
|
#### 配置
|
||||||
|
|
||||||
|
设置必要的环境变量:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 基础配置
|
||||||
|
export RUSTFS_VOLUMES="./target/volume/test"
|
||||||
|
export RUSTFS_ADDRESS="0.0.0.0:9000"
|
||||||
|
export RUSTFS_CONSOLE_ENABLE=true
|
||||||
|
export RUSTFS_CONSOLE_ADDRESS="0.0.0.0:9001"
|
||||||
|
|
||||||
|
# 可观测性配置(方式一:配置文件)
|
||||||
|
export RUSTFS_OBS_CONFIG="./deploy/config/obs.toml"
|
||||||
|
|
||||||
|
# 可观测性配置(方式二:环境变量)
|
||||||
|
export RUSTFS__OBSERVABILITY__ENDPOINT=http://localhost:4317
|
||||||
|
export RUSTFS__OBSERVABILITY__USE_STDOUT=true
|
||||||
|
export RUSTFS__OBSERVABILITY__SAMPLE_RATIO=2.0
|
||||||
|
export RUSTFS__OBSERVABILITY__METER_INTERVAL=30
|
||||||
|
export RUSTFS__OBSERVABILITY__SERVICE_NAME=rustfs
|
||||||
|
export RUSTFS__OBSERVABILITY__SERVICE_VERSION=0.1.0
|
||||||
|
export RUSTFS__OBSERVABILITY__ENVIRONMENT=develop
|
||||||
|
export RUSTFS__OBSERVABILITY__LOGGER_LEVEL=info
|
||||||
|
export RUSTFS__OBSERVABILITY__LOCAL_LOGGING_ENABLED=true
|
||||||
|
|
||||||
|
# 日志接收器
|
||||||
|
export RUSTFS__SINKS__FILE__ENABLED=true
|
||||||
|
export RUSTFS__SINKS__FILE__PATH="./deploy/logs/rustfs.log"
|
||||||
|
export RUSTFS__SINKS__WEBHOOK__ENABLED=false
|
||||||
|
export RUSTFS__SINKS__WEBHOOK__ENDPOINT=""
|
||||||
|
export RUSTFS__SINKS__WEBHOOK__AUTH_TOKEN=""
|
||||||
|
export RUSTFS__SINKS__KAFKA__ENABLED=false
|
||||||
|
export RUSTFS__SINKS__KAFKA__BOOTSTRAP_SERVERS=""
|
||||||
|
export RUSTFS__SINKS__KAFKA__TOPIC=""
|
||||||
|
export RUSTFS__LOGGER__QUEUE_CAPACITY=10
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 启动服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./rustfs /data/rustfs
|
||||||
|
```
|
||||||
|
|
||||||
|
### 可观测性系统
|
||||||
|
|
||||||
|
#### 部署
|
||||||
|
|
||||||
|
1. 进入可观测性目录:
|
||||||
|
```bash
|
||||||
|
cd .docker/observability
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 启动可观测性系统:
|
||||||
|
```bash
|
||||||
|
docker compose up -d -f docker-compose.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 访问监控面板
|
||||||
|
|
||||||
|
- Grafana: `http://localhost:3000` (默认账号/密码:`admin`/`admin`)
|
||||||
|
- Jaeger: `http://localhost:16686`
|
||||||
|
- Prometheus: `http://localhost:9090`
|
||||||
|
|
||||||
|
#### 配置可观测性
|
||||||
|
|
||||||
|
1. 复制示例配置:
|
||||||
|
```bash
|
||||||
|
cd deploy/config
|
||||||
|
cp obs.toml.example obs.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 编辑 `obs.toml` 配置文件,参数如下:
|
||||||
|
|
||||||
|
| 配置项 | 说明 | 示例值 |
|
||||||
|
|----------------------|----------------------------|-----------------------|
|
||||||
|
| endpoint | OpenTelemetry Collector 地址 | http://localhost:4317 |
|
||||||
|
| service_name | 服务名称 | rustfs |
|
||||||
|
| service_version | 服务版本 | 1.0.0 |
|
||||||
|
| environment | 运行环境 | production |
|
||||||
|
| meter_interval | 指标导出间隔 (秒) | 30 |
|
||||||
|
| sample_ratio | 采样率 | 1.0 |
|
||||||
|
| use_stdout | 是否输出到控制台 | true/false |
|
||||||
|
| logger_level | 日志级别 | info |
|
||||||
|
| local_logging_enable | 控制台是否答应日志 | true/false |
|
||||||
|
|
||||||
|
```
|
||||||
@@ -21,6 +21,7 @@ full = ["file", "gpu", "kafka", "webhook"]
|
|||||||
async-trait = { workspace = true }
|
async-trait = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
config = { workspace = true }
|
config = { workspace = true }
|
||||||
|
local-ip-address = { workspace = true }
|
||||||
nvml-wrapper = { workspace = true, optional = true }
|
nvml-wrapper = { workspace = true, optional = true }
|
||||||
opentelemetry = { workspace = true }
|
opentelemetry = { workspace = true }
|
||||||
opentelemetry-appender-tracing = { workspace = true, features = ["experimental_use_tracing_span_context", "experimental_metadata_attributes"] }
|
opentelemetry-appender-tracing = { workspace = true, features = ["experimental_use_tracing_span_context", "experimental_metadata_attributes"] }
|
||||||
@@ -41,7 +42,7 @@ reqwest = { workspace = true, optional = true, default-features = false }
|
|||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
sysinfo = { workspace = true }
|
sysinfo = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
local-ip-address = { workspace = true }
|
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
@@ -213,22 +213,29 @@ pub fn init_telemetry(config: &OtelConfig) -> OtelGuard {
|
|||||||
.with_line_number(true);
|
.with_line_number(true);
|
||||||
|
|
||||||
let filter = build_env_filter(logger_level, None);
|
let filter = build_env_filter(logger_level, None);
|
||||||
|
|
||||||
let tracer = tracer_provider.tracer(Cow::Borrowed(service_name).to_string());
|
|
||||||
|
|
||||||
let otel_filter = build_env_filter(logger_level, None);
|
let otel_filter = build_env_filter(logger_level, None);
|
||||||
let otel_layer = OpenTelemetryTracingBridge::new(&logger_provider).with_filter(otel_filter);
|
let otel_layer = OpenTelemetryTracingBridge::new(&logger_provider).with_filter(otel_filter);
|
||||||
|
let tracer = tracer_provider.tracer(Cow::Borrowed(service_name).to_string());
|
||||||
|
|
||||||
// Configure registry to avoid repeated calls to filter methods
|
// Configure registry to avoid repeated calls to filter methods
|
||||||
tracing_subscriber::registry()
|
let _registry = tracing_subscriber::registry()
|
||||||
.with(filter)
|
.with(filter)
|
||||||
.with(fmt_layer)
|
|
||||||
.with(ErrorLayer::default())
|
.with(ErrorLayer::default())
|
||||||
|
.with(if config.local_logging_enabled.unwrap_or(false) {
|
||||||
|
Some(fmt_layer)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.with(OpenTelemetryLayer::new(tracer))
|
||||||
.with(otel_layer)
|
.with(otel_layer)
|
||||||
.with(MetricsLayer::new(meter_provider.clone()))
|
.with(MetricsLayer::new(meter_provider.clone()))
|
||||||
.with(OpenTelemetryLayer::new(tracer))
|
|
||||||
.with(ErrorLayer::default())
|
|
||||||
.init();
|
.init();
|
||||||
|
info!("Telemetry logging enabled: {:?}", config.local_logging_enabled);
|
||||||
|
// if config.local_logging_enabled.unwrap_or(false) {
|
||||||
|
// registry.with(fmt_layer).init();
|
||||||
|
// } else {
|
||||||
|
// registry.init();
|
||||||
|
// }
|
||||||
|
|
||||||
if !endpoint.is_empty() {
|
if !endpoint.is_empty() {
|
||||||
info!(
|
info!(
|
||||||
|
|||||||
+13
-5
@@ -23,13 +23,21 @@ managing and monitoring the system.
|
|||||||
| |--rustfs.service // systemd service file
|
| |--rustfs.service // systemd service file
|
||||||
| |--rustfs-zh.service.md // systemd service file in Chinese
|
| |--rustfs-zh.service.md // systemd service file in Chinese
|
||||||
|--certs
|
|--certs
|
||||||
| |--README.md // certs readme
|
| ├── rustfs_cert.pem // Default|fallback certificate
|
||||||
| |--rustfs_tls_cert.pem // API cert.pem
|
| ├── rustfs_key.pem // Default|fallback private key
|
||||||
| |--rustfs_tls_key.pem // API key.pem
|
| ├── example.com/ // certificate directory of specific domain names
|
||||||
| |--rustfs_console_tls_cert.pem // console cert.pem
|
| │ ├── rustfs_cert.pem
|
||||||
| |--rustfs_console_tls_key.pem // console key.pem
|
| │ └── rustfs_key.pem
|
||||||
|
| ├── api.example.com/
|
||||||
|
| │ ├── rustfs_cert.pem
|
||||||
|
| │ └── rustfs_key.pem
|
||||||
|
| └── cdn.example.com/
|
||||||
|
| ├── rustfs_cert.pem
|
||||||
|
| └── rustfs_key.pem
|
||||||
|--config
|
|--config
|
||||||
| |--obs.example.yaml // example config
|
| |--obs.example.yaml // example config
|
||||||
| |--rustfs.env // env config
|
| |--rustfs.env // env config
|
||||||
| |--rustfs-zh.env // env config in Chinese
|
| |--rustfs-zh.env // env config in Chinese
|
||||||
|
| |--.example.obs.env // example env config
|
||||||
|
| |--event.example.toml // event config
|
||||||
```
|
```
|
||||||
@@ -51,6 +51,10 @@ ExecStart=/usr/local/bin/rustfs \
|
|||||||
EnvironmentFile=-/etc/default/rustfs
|
EnvironmentFile=-/etc/default/rustfs
|
||||||
ExecStart=/usr/local/bin/rustfs $RUSTFS_VOLUMES $RUSTFS_OPTS
|
ExecStart=/usr/local/bin/rustfs $RUSTFS_VOLUMES $RUSTFS_OPTS
|
||||||
|
|
||||||
|
# standard output and error log configuration
|
||||||
|
StandardOutput=append:/data/deploy/rust/logs/rustfs.log
|
||||||
|
StandardError=append:/data/deploy/rust/logs/rustfs-err.log
|
||||||
|
|
||||||
# resource constraints
|
# resource constraints
|
||||||
LimitNOFILE=1048576
|
LimitNOFILE=1048576
|
||||||
# 设置文件描述符上限为 1048576,支持高并发连接。
|
# 设置文件描述符上限为 1048576,支持高并发连接。
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ ExecStart=/usr/local/bin/rustfs \
|
|||||||
EnvironmentFile=-/etc/default/rustfs
|
EnvironmentFile=-/etc/default/rustfs
|
||||||
ExecStart=/usr/local/bin/rustfs $RUSTFS_VOLUMES $RUSTFS_OPTS
|
ExecStart=/usr/local/bin/rustfs $RUSTFS_VOLUMES $RUSTFS_OPTS
|
||||||
|
|
||||||
|
# service log configuration
|
||||||
|
StandardOutput=append:/data/deploy/rust/logs/rustfs.log
|
||||||
|
StandardError=append:/data/deploy/rust/logs/rustfs-err.log
|
||||||
|
|
||||||
# resource constraints
|
# resource constraints
|
||||||
LimitNOFILE=1048576
|
LimitNOFILE=1048576
|
||||||
LimitNPROC=32768
|
LimitNPROC=32768
|
||||||
|
|||||||
+13
-3
@@ -32,7 +32,17 @@ openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -node
|
|||||||
### TLS File
|
### TLS File
|
||||||
|
|
||||||
```text
|
```text
|
||||||
rustfs_public.crt #api cert.pem
|
cd deploy/certs/
|
||||||
|
ls -la
|
||||||
rustfs_private.key #api key.pem
|
├── rustfs_cert.pem // Default|fallback certificate
|
||||||
|
├── rustfs_key.pem // Default|fallback private key
|
||||||
|
├── example.com/ // certificate directory of specific domain names
|
||||||
|
│ ├── rustfs_cert.pem
|
||||||
|
│ └── rustfs_key.pem
|
||||||
|
├── api.example.com/
|
||||||
|
│ ├── rustfs_cert.pem
|
||||||
|
│ └── rustfs_key.pem
|
||||||
|
└── cdn.example.com/
|
||||||
|
├── rustfs_cert.pem
|
||||||
|
└── rustfs_key.pem
|
||||||
```
|
```
|
||||||
@@ -6,7 +6,8 @@ meter_interval = 30 # 指标收集间隔,单位为秒
|
|||||||
service_name = "rustfs" # 服务名称,用于标识当前服务
|
service_name = "rustfs" # 服务名称,用于标识当前服务
|
||||||
service_version = "0.1.0" # 服务版本号
|
service_version = "0.1.0" # 服务版本号
|
||||||
environments = "develop" # 运行环境,如开发环境 (develop)
|
environments = "develop" # 运行环境,如开发环境 (develop)
|
||||||
logger_level = "debug" # 日志级别,可选 debug/info/warn/error 等
|
logger_level = "debug" # 日志级别,可选 debug/info/warn/error 等
|
||||||
|
local_logging_enabled = true # 是否启用本地 stdout 日志记录,true 表示启用,false 表示禁用
|
||||||
|
|
||||||
[sinks]
|
[sinks]
|
||||||
[sinks.kafka] # Kafka 接收器配置
|
[sinks.kafka] # Kafka 接收器配置
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ service_name = "rustfs"
|
|||||||
service_version = "0.1.0"
|
service_version = "0.1.0"
|
||||||
environment = "develop"
|
environment = "develop"
|
||||||
logger_level = "error"
|
logger_level = "error"
|
||||||
|
local_logging_enabled = true
|
||||||
|
|
||||||
[sinks]
|
[sinks]
|
||||||
[sinks.kafka] # Kafka sink is disabled by default
|
[sinks.kafka] # Kafka sink is disabled by default
|
||||||
|
|||||||
@@ -661,7 +661,10 @@ impl ReadAt for BitrotFileReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if offset != self.curr_offset {
|
if offset != self.curr_offset {
|
||||||
error!("BitrotFileReader read_at offset != self.curr_offset, {} != {}", offset, self.curr_offset);
|
error!(
|
||||||
|
"BitrotFileReader read_at {}/{} offset != self.curr_offset, {} != {}",
|
||||||
|
&self.volume, &self.file_path, offset, self.curr_offset
|
||||||
|
);
|
||||||
return Err(Error::new(DiskError::Unexpected));
|
return Err(Error::new(DiskError::Unexpected));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -294,7 +294,12 @@ pub async fn list_path_raw(mut rx: B_Receiver<bool>, opts: ListPathRawOptions) -
|
|||||||
|
|
||||||
jobs.push(revjob);
|
jobs.push(revjob);
|
||||||
|
|
||||||
let _ = join_all(jobs).await;
|
let results = join_all(jobs).await;
|
||||||
|
for result in results {
|
||||||
|
if let Err(err) = result {
|
||||||
|
error!("list_path_raw err {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -455,7 +455,7 @@ impl LocalDisk {
|
|||||||
{
|
{
|
||||||
if let Err(aerr) = access(volume_dir.as_ref()).await {
|
if let Err(aerr) = access(volume_dir.as_ref()).await {
|
||||||
if os_is_not_exist(&aerr) {
|
if os_is_not_exist(&aerr) {
|
||||||
warn!("read_metadata_with_dmtime os err {:?}", &aerr);
|
// warn!("read_metadata_with_dmtime os err {:?}", &aerr);
|
||||||
return Err(Error::new(DiskError::VolumeNotFound));
|
return Err(Error::new(DiskError::VolumeNotFound));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -470,7 +470,7 @@ impl Erasure {
|
|||||||
self.encoder.as_ref().unwrap().reconstruct(&mut bufs)?;
|
self.encoder.as_ref().unwrap().reconstruct(&mut bufs)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let shards = bufs.into_iter().flatten().collect::<Vec<_>>();
|
let shards = bufs.into_iter().flatten().map(Bytes::from).collect::<Vec<_>>();
|
||||||
if shards.len() != self.parity_shards + self.data_shards {
|
if shards.len() != self.parity_shards + self.data_shards {
|
||||||
return Err(Error::from_string("can not reconstruct data"));
|
return Err(Error::from_string("can not reconstruct data"));
|
||||||
}
|
}
|
||||||
@@ -479,7 +479,7 @@ impl Erasure {
|
|||||||
if w.is_none() {
|
if w.is_none() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
match w.as_mut().unwrap().write(shards[i].clone().into()).await {
|
match w.as_mut().unwrap().write(shards[i].clone()).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
info!("write failed, err: {:?}", e);
|
info!("write failed, err: {:?}", e);
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ use std::path::Path;
|
|||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
use tracing::warn;
|
|
||||||
|
|
||||||
use super::data_scanner::{SizeSummary, DATA_SCANNER_FORCE_COMPACT_AT_FOLDERS};
|
use super::data_scanner::{SizeSummary, DATA_SCANNER_FORCE_COMPACT_AT_FOLDERS};
|
||||||
use super::data_usage::{BucketTargetUsageInfo, BucketUsageInfo, DataUsageInfo};
|
use super::data_usage::{BucketTargetUsageInfo, BucketUsageInfo, DataUsageInfo};
|
||||||
@@ -402,7 +401,7 @@ impl DataUsageCache {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!("Failed to load data usage cache from backend: {}", &err);
|
// warn!("Failed to load data usage cache from backend: {}", &err);
|
||||||
match err.downcast_ref::<DiskError>() {
|
match err.downcast_ref::<DiskError>() {
|
||||||
Some(DiskError::FileNotFound) | Some(DiskError::VolumeNotFound) => {
|
Some(DiskError::FileNotFound) | Some(DiskError::VolumeNotFound) => {
|
||||||
match store
|
match store
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use lazy_static::lazy_static;
|
|||||||
use madmin::{ItemState, ServerProperties};
|
use madmin::{ItemState, ServerProperties};
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
use tracing::error;
|
use tracing::{error, warn};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref GLOBAL_NotificationSys: OnceLock<NotificationSys> = OnceLock::new();
|
pub static ref GLOBAL_NotificationSys: OnceLock<NotificationSys> = OnceLock::new();
|
||||||
@@ -151,6 +151,8 @@ impl NotificationSys {
|
|||||||
for result in results {
|
for result in results {
|
||||||
if let Err(err) = result {
|
if let Err(err) = result {
|
||||||
error!("notification load_rebalance_meta err {:?}", err);
|
error!("notification load_rebalance_meta err {:?}", err);
|
||||||
|
} else {
|
||||||
|
warn!("notification load_rebalance_meta success");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -663,6 +663,8 @@ impl PeerRestClient {
|
|||||||
let request = Request::new(LoadRebalanceMetaRequest { start_rebalance });
|
let request = Request::new(LoadRebalanceMetaRequest { start_rebalance });
|
||||||
|
|
||||||
let response = client.load_rebalance_meta(request).await?.into_inner();
|
let response = client.load_rebalance_meta(request).await?.into_inner();
|
||||||
|
|
||||||
|
warn!("load_rebalance_meta response {:?}", response);
|
||||||
if !response.success {
|
if !response.success {
|
||||||
if let Some(msg) = response.error_info {
|
if let Some(msg) = response.error_info {
|
||||||
return Err(Error::msg(msg));
|
return Err(Error::msg(msg));
|
||||||
|
|||||||
+82
-9
@@ -2,6 +2,7 @@ use crate::bucket::versioning_sys::BucketVersioningSys;
|
|||||||
use crate::cache_value::metacache_set::{list_path_raw, ListPathRawOptions};
|
use crate::cache_value::metacache_set::{list_path_raw, ListPathRawOptions};
|
||||||
use crate::config::com::{read_config, save_config, CONFIG_PREFIX};
|
use crate::config::com::{read_config, save_config, CONFIG_PREFIX};
|
||||||
use crate::config::error::ConfigError;
|
use crate::config::error::ConfigError;
|
||||||
|
use crate::disk::error::is_err_volume_not_found;
|
||||||
use crate::disk::{MetaCacheEntries, MetaCacheEntry, MetadataResolutionParams, BUCKET_META_PREFIX, RUSTFS_META_BUCKET};
|
use crate::disk::{MetaCacheEntries, MetaCacheEntry, MetadataResolutionParams, BUCKET_META_PREFIX, RUSTFS_META_BUCKET};
|
||||||
use crate::heal::data_usage::DATA_USAGE_CACHE_NAME;
|
use crate::heal::data_usage::DATA_USAGE_CACHE_NAME;
|
||||||
use crate::heal::heal_commands::HealOpts;
|
use crate::heal::heal_commands::HealOpts;
|
||||||
@@ -609,6 +610,12 @@ impl ECStore {
|
|||||||
let mut lock = self.pool_meta.write().await;
|
let mut lock = self.pool_meta.write().await;
|
||||||
if lock.decommission_cancel(idx) {
|
if lock.decommission_cancel(idx) {
|
||||||
lock.save(self.pools.clone()).await?;
|
lock.save(self.pools.clone()).await?;
|
||||||
|
|
||||||
|
drop(lock);
|
||||||
|
|
||||||
|
if let Some(notification_sys) = get_global_notification_sys() {
|
||||||
|
notification_sys.reload_pool_meta().await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -628,6 +635,7 @@ impl ECStore {
|
|||||||
|
|
||||||
#[tracing::instrument(skip(self, rx))]
|
#[tracing::instrument(skip(self, rx))]
|
||||||
pub async fn decommission(&self, rx: B_Receiver<bool>, indices: Vec<usize>) -> Result<()> {
|
pub async fn decommission(&self, rx: B_Receiver<bool>, indices: Vec<usize>) -> Result<()> {
|
||||||
|
warn!("decommission: {:?}", indices);
|
||||||
if indices.is_empty() {
|
if indices.is_empty() {
|
||||||
return Err(Error::msg("errInvalidArgument"));
|
return Err(Error::msg("errInvalidArgument"));
|
||||||
}
|
}
|
||||||
@@ -662,8 +670,10 @@ impl ECStore {
|
|||||||
wk: Arc<Workers>,
|
wk: Arc<Workers>,
|
||||||
rcfg: Option<String>,
|
rcfg: Option<String>,
|
||||||
) {
|
) {
|
||||||
|
warn!("decommission_entry: {} {}", &bucket, &entry.name);
|
||||||
wk.give().await;
|
wk.give().await;
|
||||||
if entry.is_dir() {
|
if entry.is_dir() {
|
||||||
|
warn!("decommission_entry: skip dir {}", &entry.name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -782,6 +792,9 @@ impl ECStore {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let bucket_name = bucket.clone();
|
||||||
|
let object_name = rd.object_info.name.clone();
|
||||||
|
|
||||||
if let Err(err) = self.clone().decommission_object(idx, bucket, rd).await {
|
if let Err(err) = self.clone().decommission_object(idx, bucket, rd).await {
|
||||||
if is_err_object_not_found(&err) || is_err_version_not_found(&err) || is_err_data_movement_overwrite(&err) {
|
if is_err_object_not_found(&err) || is_err_version_not_found(&err) || is_err_data_movement_overwrite(&err) {
|
||||||
ignore = true;
|
ignore = true;
|
||||||
@@ -794,6 +807,11 @@ impl ECStore {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
warn!(
|
||||||
|
"decommission_pool: decommission_object done {}/{} {}",
|
||||||
|
&bucket_name, &object_name, &version.name
|
||||||
|
);
|
||||||
|
|
||||||
failure = false;
|
failure = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -841,10 +859,16 @@ impl ECStore {
|
|||||||
.update_after(idx, self.pools.clone(), Duration::seconds(30))
|
.update_after(idx, self.pools.clone(), Duration::seconds(30))
|
||||||
.await
|
.await
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
drop(pool_meta);
|
||||||
if ok {
|
if ok {
|
||||||
// TODO: ReloadPoolMeta
|
if let Some(notification_sys) = get_global_notification_sys() {
|
||||||
|
notification_sys.reload_pool_meta().await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
warn!("decommission_pool: decommission_entry done {} {}", &bucket, &entry.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self, rx))]
|
#[tracing::instrument(skip(self, rx))]
|
||||||
@@ -872,6 +896,8 @@ impl ECStore {
|
|||||||
for (set_idx, set) in pool.disk_set.iter().enumerate() {
|
for (set_idx, set) in pool.disk_set.iter().enumerate() {
|
||||||
wk.clone().take().await;
|
wk.clone().take().await;
|
||||||
|
|
||||||
|
warn!("decommission_pool: decommission_pool {} {}", set_idx, &bi.name);
|
||||||
|
|
||||||
let decommission_entry: ListCallback = Arc::new({
|
let decommission_entry: ListCallback = Arc::new({
|
||||||
let this = Arc::clone(self);
|
let this = Arc::clone(self);
|
||||||
let bucket = bi.name.clone();
|
let bucket = bi.name.clone();
|
||||||
@@ -895,20 +921,39 @@ impl ECStore {
|
|||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
if rx.try_recv().is_ok() {
|
if rx.try_recv().is_ok() {
|
||||||
|
warn!("decommission_pool: cancel {}", set_id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if let Err(err) = set
|
warn!("decommission_pool: list_objects_to_decommission {} {}", set_id, &bi.name);
|
||||||
|
|
||||||
|
match set
|
||||||
.list_objects_to_decommission(rx.resubscribe(), bi.clone(), decommission_entry.clone())
|
.list_objects_to_decommission(rx.resubscribe(), bi.clone(), decommission_entry.clone())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
error!("decommission_pool: list_objects_to_decommission {} err {:?}", set_id, &err);
|
Ok(_) => {
|
||||||
|
warn!("decommission_pool: list_objects_to_decommission {} done", set_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("decommission_pool: list_objects_to_decommission {} err {:?}", set_id, &err);
|
||||||
|
if is_err_volume_not_found(&err) {
|
||||||
|
warn!("decommission_pool: list_objects_to_decommission {} volume not found", set_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
warn!("decommission_pool: decommission_pool wait {} {}", idx, &bi.name);
|
||||||
|
|
||||||
wk.wait().await;
|
wk.wait().await;
|
||||||
|
|
||||||
|
warn!("decommission_pool: decommission_pool done {} {}", idx, &bi.name);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -918,11 +963,15 @@ impl ECStore {
|
|||||||
error!("decom err {:?}", &err);
|
error!("decom err {:?}", &err);
|
||||||
if let Err(er) = self.decommission_failed(idx).await {
|
if let Err(er) = self.decommission_failed(idx).await {
|
||||||
error!("decom failed err {:?}", &er);
|
error!("decom failed err {:?}", &er);
|
||||||
|
} else {
|
||||||
|
warn!("decommission: decommission_failed {}", idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
warn!("decommission: decommission_in_background complete {}", idx);
|
||||||
|
|
||||||
let (failed, cmd_line) = {
|
let (failed, cmd_line) = {
|
||||||
let pool_meta = self.pool_meta.read().await;
|
let pool_meta = self.pool_meta.read().await;
|
||||||
let failed = {
|
let failed = {
|
||||||
@@ -944,6 +993,8 @@ impl ECStore {
|
|||||||
} else if let Err(er) = self.complete_decommission(idx).await {
|
} else if let Err(er) = self.complete_decommission(idx).await {
|
||||||
error!("decom complete err {:?}", &er);
|
error!("decom complete err {:?}", &er);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
warn!("Decommissioning complete for pool {}", cmd_line);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
@@ -955,6 +1006,9 @@ impl ECStore {
|
|||||||
let mut pool_meta = self.pool_meta.write().await;
|
let mut pool_meta = self.pool_meta.write().await;
|
||||||
if pool_meta.decommission_failed(idx) {
|
if pool_meta.decommission_failed(idx) {
|
||||||
pool_meta.save(self.pools.clone()).await?;
|
pool_meta.save(self.pools.clone()).await?;
|
||||||
|
|
||||||
|
drop(pool_meta);
|
||||||
|
|
||||||
if let Some(notification_sys) = get_global_notification_sys() {
|
if let Some(notification_sys) = get_global_notification_sys() {
|
||||||
notification_sys.reload_pool_meta().await;
|
notification_sys.reload_pool_meta().await;
|
||||||
}
|
}
|
||||||
@@ -972,6 +1026,7 @@ impl ECStore {
|
|||||||
let mut pool_meta = self.pool_meta.write().await;
|
let mut pool_meta = self.pool_meta.write().await;
|
||||||
if pool_meta.decommission_complete(idx) {
|
if pool_meta.decommission_complete(idx) {
|
||||||
pool_meta.save(self.pools.clone()).await?;
|
pool_meta.save(self.pools.clone()).await?;
|
||||||
|
drop(pool_meta);
|
||||||
if let Some(notification_sys) = get_global_notification_sys() {
|
if let Some(notification_sys) = get_global_notification_sys() {
|
||||||
notification_sys.reload_pool_meta().await;
|
notification_sys.reload_pool_meta().await;
|
||||||
}
|
}
|
||||||
@@ -996,7 +1051,7 @@ impl ECStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if is_decommissioned {
|
if is_decommissioned {
|
||||||
info!("decommission: already done, moving on {}", bucket.to_string());
|
warn!("decommission: already done, moving on {}", bucket.to_string());
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut pool_meta = self.pool_meta.write().await;
|
let mut pool_meta = self.pool_meta.write().await;
|
||||||
@@ -1009,7 +1064,7 @@ impl ECStore {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("decommission: currently on bucket {}", &bucket.name);
|
warn!("decommission: currently on bucket {}", &bucket.name);
|
||||||
|
|
||||||
if let Err(err) = self
|
if let Err(err) = self
|
||||||
.decommission_pool(rx.resubscribe(), idx, pool.clone(), bucket.clone())
|
.decommission_pool(rx.resubscribe(), idx, pool.clone(), bucket.clone())
|
||||||
@@ -1017,6 +1072,8 @@ impl ECStore {
|
|||||||
{
|
{
|
||||||
error!("decommission: decommission_pool err {:?}", &err);
|
error!("decommission: decommission_pool err {:?}", &err);
|
||||||
return Err(err);
|
return Err(err);
|
||||||
|
} else {
|
||||||
|
warn!("decommission: decommission_pool done {}", &bucket.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -1026,6 +1083,8 @@ impl ECStore {
|
|||||||
error!("decom pool_meta.save err {:?}", err);
|
error!("decom pool_meta.save err {:?}", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
warn!("decommission: decommission_pool bucket_done {}", &bucket.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1107,6 +1166,7 @@ impl ECStore {
|
|||||||
|
|
||||||
#[tracing::instrument(skip(self, rd))]
|
#[tracing::instrument(skip(self, rd))]
|
||||||
async fn decommission_object(self: Arc<Self>, pool_idx: usize, bucket: String, rd: GetObjectReader) -> Result<()> {
|
async fn decommission_object(self: Arc<Self>, pool_idx: usize, bucket: String, rd: GetObjectReader) -> Result<()> {
|
||||||
|
warn!("decommission_object: {} {}", &bucket, &rd.object_info.name);
|
||||||
let object_info = rd.object_info.clone();
|
let object_info = rd.object_info.clone();
|
||||||
|
|
||||||
// TODO: check : use size or actual_size ?
|
// TODO: check : use size or actual_size ?
|
||||||
@@ -1269,10 +1329,23 @@ impl SetDisks {
|
|||||||
min_disks: listing_quorum,
|
min_disks: listing_quorum,
|
||||||
partial: Some(Box::new(move |entries: MetaCacheEntries, _: &[Option<Error>]| {
|
partial: Some(Box::new(move |entries: MetaCacheEntries, _: &[Option<Error>]| {
|
||||||
let resolver = resolver.clone();
|
let resolver = resolver.clone();
|
||||||
if let Ok(Some(entry)) = entries.resolve(resolver) {
|
let cb_func = cb_func.clone();
|
||||||
cb_func(entry)
|
|
||||||
} else {
|
match entries.resolve(resolver) {
|
||||||
Box::pin(async {})
|
Ok(Some(entry)) => {
|
||||||
|
warn!("decommission_pool: list_objects_to_decommission get {}", &entry.name);
|
||||||
|
Box::pin(async move {
|
||||||
|
cb_func(entry).await;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
warn!("decommission_pool: list_objects_to_decommission get none");
|
||||||
|
Box::pin(async {})
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("decommission_pool: list_objects_to_decommission get err {:?}", &err);
|
||||||
|
Box::pin(async {})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|||||||
+58
-12
@@ -20,7 +20,7 @@ use http::HeaderMap;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::broadcast::{self, Receiver as B_Receiver};
|
use tokio::sync::broadcast::{self, Receiver as B_Receiver};
|
||||||
use tokio::time::{Duration, Instant};
|
use tokio::time::{Duration, Instant};
|
||||||
use tracing::{error, info};
|
use tracing::{error, info, warn};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use workers::workers::Workers;
|
use workers::workers::Workers;
|
||||||
|
|
||||||
@@ -162,6 +162,7 @@ impl RebalanceMeta {
|
|||||||
pub async fn load_with_opts<S: StorageAPI>(&mut self, store: Arc<S>, opts: ObjectOptions) -> Result<()> {
|
pub async fn load_with_opts<S: StorageAPI>(&mut self, store: Arc<S>, opts: ObjectOptions) -> Result<()> {
|
||||||
let (data, _) = read_config_with_metadata(store, REBAL_META_NAME, &opts).await?;
|
let (data, _) = read_config_with_metadata(store, REBAL_META_NAME, &opts).await?;
|
||||||
if data.is_empty() {
|
if data.is_empty() {
|
||||||
|
warn!("rebalanceMeta: no data");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
if data.len() <= 4 {
|
if data.len() <= 4 {
|
||||||
@@ -183,6 +184,7 @@ impl RebalanceMeta {
|
|||||||
|
|
||||||
self.last_refreshed_at = Some(SystemTime::now());
|
self.last_refreshed_at = Some(SystemTime::now());
|
||||||
|
|
||||||
|
warn!("rebalanceMeta: loaded meta done");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,20 +216,33 @@ impl ECStore {
|
|||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn load_rebalance_meta(&self) -> Result<()> {
|
pub async fn load_rebalance_meta(&self) -> Result<()> {
|
||||||
let mut meta = RebalanceMeta::new();
|
let mut meta = RebalanceMeta::new();
|
||||||
|
warn!("rebalanceMeta: load rebalance meta");
|
||||||
match meta.load(self.pools[0].clone()).await {
|
match meta.load(self.pools[0].clone()).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let mut rebalance_meta = self.rebalance_meta.write().await;
|
warn!("rebalanceMeta: rebalance meta loaded0");
|
||||||
|
{
|
||||||
|
let mut rebalance_meta = self.rebalance_meta.write().await;
|
||||||
|
|
||||||
*rebalance_meta = Some(meta);
|
*rebalance_meta = Some(meta);
|
||||||
|
|
||||||
|
drop(rebalance_meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
warn!("rebalanceMeta: rebalance meta loaded1");
|
||||||
|
|
||||||
if let Err(err) = self.update_rebalance_stats().await {
|
if let Err(err) = self.update_rebalance_stats().await {
|
||||||
error!("Failed to update rebalance stats: {}", err);
|
error!("Failed to update rebalance stats: {}", err);
|
||||||
|
} else {
|
||||||
|
warn!("rebalanceMeta: rebalance meta loaded2");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if !is_err_config_not_found(&err) {
|
if !is_err_config_not_found(&err) {
|
||||||
|
error!("rebalanceMeta: load rebalance meta err {:?}", &err);
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
error!("rebalanceMeta: not found, rebalance not started");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,21 +252,24 @@ impl ECStore {
|
|||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn update_rebalance_stats(&self) -> Result<()> {
|
pub async fn update_rebalance_stats(&self) -> Result<()> {
|
||||||
let mut ok = false;
|
let mut ok = false;
|
||||||
let mut rebalance_meta = self.rebalance_meta.write().await;
|
|
||||||
|
|
||||||
for i in 0..self.pools.len() {
|
for i in 0..self.pools.len() {
|
||||||
if self.find_index(i).await.is_none() {
|
if self.find_index(i).await.is_none() {
|
||||||
|
let mut rebalance_meta = self.rebalance_meta.write().await;
|
||||||
if let Some(meta) = rebalance_meta.as_mut() {
|
if let Some(meta) = rebalance_meta.as_mut() {
|
||||||
meta.pool_stats.push(RebalanceStats::default());
|
meta.pool_stats.push(RebalanceStats::default());
|
||||||
}
|
}
|
||||||
ok = true;
|
ok = true;
|
||||||
|
drop(rebalance_meta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
|
let mut rebalance_meta = self.rebalance_meta.write().await;
|
||||||
if let Some(meta) = rebalance_meta.as_mut() {
|
if let Some(meta) = rebalance_meta.as_mut() {
|
||||||
meta.save(self.pools[0].clone()).await?;
|
meta.save(self.pools[0].clone()).await?;
|
||||||
}
|
}
|
||||||
|
drop(rebalance_meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -267,6 +285,7 @@ impl ECStore {
|
|||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub async fn init_rebalance_meta(&self, bucktes: Vec<String>) -> Result<String> {
|
pub async fn init_rebalance_meta(&self, bucktes: Vec<String>) -> Result<String> {
|
||||||
|
warn!("init_rebalance_meta: start rebalance");
|
||||||
let si = self.storage_info().await;
|
let si = self.storage_info().await;
|
||||||
|
|
||||||
let mut disk_stats = vec![DiskStat::default(); self.pools.len()];
|
let mut disk_stats = vec![DiskStat::default(); self.pools.len()];
|
||||||
@@ -321,10 +340,15 @@ impl ECStore {
|
|||||||
|
|
||||||
meta.save(self.pools[0].clone()).await?;
|
meta.save(self.pools[0].clone()).await?;
|
||||||
|
|
||||||
|
warn!("init_rebalance_meta: rebalance meta saved");
|
||||||
|
|
||||||
let id = meta.id.clone();
|
let id = meta.id.clone();
|
||||||
|
|
||||||
let mut rebalance_meta = self.rebalance_meta.write().await;
|
{
|
||||||
*rebalance_meta = Some(meta);
|
let mut rebalance_meta = self.rebalance_meta.write().await;
|
||||||
|
*rebalance_meta = Some(meta);
|
||||||
|
drop(rebalance_meta);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
@@ -424,6 +448,7 @@ impl ECStore {
|
|||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn start_rebalance(self: &Arc<Self>) {
|
pub async fn start_rebalance(self: &Arc<Self>) {
|
||||||
|
warn!("start_rebalance: start rebalance");
|
||||||
// let rebalance_meta = self.rebalance_meta.read().await;
|
// let rebalance_meta = self.rebalance_meta.read().await;
|
||||||
|
|
||||||
let (tx, rx) = broadcast::channel::<bool>(1);
|
let (tx, rx) = broadcast::channel::<bool>(1);
|
||||||
@@ -433,13 +458,17 @@ impl ECStore {
|
|||||||
if let Some(meta) = rebalance_meta.as_mut() {
|
if let Some(meta) = rebalance_meta.as_mut() {
|
||||||
meta.cancel = Some(tx)
|
meta.cancel = Some(tx)
|
||||||
} else {
|
} else {
|
||||||
|
error!("start_rebalance: rebalance_meta is None exit");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drop(rebalance_meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
let participants = {
|
let participants = {
|
||||||
if let Some(ref meta) = *self.rebalance_meta.read().await {
|
if let Some(ref meta) = *self.rebalance_meta.read().await {
|
||||||
if meta.stopped_at.is_some() {
|
if meta.stopped_at.is_some() {
|
||||||
|
warn!("start_rebalance: rebalance already stopped exit");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,6 +486,7 @@ impl ECStore {
|
|||||||
|
|
||||||
for (idx, participating) in participants.iter().enumerate() {
|
for (idx, participating) in participants.iter().enumerate() {
|
||||||
if !*participating {
|
if !*participating {
|
||||||
|
warn!("start_rebalance: pool {} is not participating, skipping", idx);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,6 +495,7 @@ impl ECStore {
|
|||||||
.get(idx)
|
.get(idx)
|
||||||
.map_or(true, |v| v.endpoints.as_ref().first().map_or(true, |e| e.is_local))
|
.map_or(true, |v| v.endpoints.as_ref().first().map_or(true, |e| e.is_local))
|
||||||
{
|
{
|
||||||
|
warn!("start_rebalance: pool {} is not local, skipping", idx);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,6 +510,8 @@ impl ECStore {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
warn!("start_rebalance: rebalance started done");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self, rx))]
|
#[tracing::instrument(skip(self, rx))]
|
||||||
@@ -540,6 +573,7 @@ impl ECStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if quit {
|
if quit {
|
||||||
|
warn!("{}: exiting save_task", msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -547,13 +581,14 @@ impl ECStore {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tracing::info!("Pool {} rebalancing is started", pool_index + 1);
|
tracing::warn!("Pool {} rebalancing is started", pool_index + 1);
|
||||||
|
|
||||||
while let Some(bucket) = self.next_rebal_bucket(pool_index).await? {
|
while let Some(bucket) = self.next_rebal_bucket(pool_index).await? {
|
||||||
tracing::info!("Rebalancing bucket: {}", bucket);
|
tracing::info!("Rebalancing bucket: {}", bucket);
|
||||||
|
|
||||||
if let Err(err) = self.rebalance_bucket(rx.resubscribe(), bucket.clone(), pool_index).await {
|
if let Err(err) = self.rebalance_bucket(rx.resubscribe(), bucket.clone(), pool_index).await {
|
||||||
if err.to_string().contains("not initialized") {
|
if err.to_string().contains("not initialized") {
|
||||||
|
warn!("rebalance_bucket: rebalance not initialized, continue");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
tracing::error!("Error rebalancing bucket {}: {:?}", bucket, err);
|
tracing::error!("Error rebalancing bucket {}: {:?}", bucket, err);
|
||||||
@@ -564,7 +599,7 @@ impl ECStore {
|
|||||||
self.bucket_rebalance_done(pool_index, bucket).await?;
|
self.bucket_rebalance_done(pool_index, bucket).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::info!("Pool {} rebalancing is done", pool_index + 1);
|
tracing::warn!("Pool {} rebalancing is done", pool_index + 1);
|
||||||
|
|
||||||
done_tx.send(Ok(())).await.ok();
|
done_tx.send(Ok(())).await.ok();
|
||||||
save_task.await.ok();
|
save_task.await.ok();
|
||||||
@@ -1037,10 +1072,21 @@ impl SetDisks {
|
|||||||
partial: Some(Box::new(move |entries: MetaCacheEntries, _: &[Option<Error>]| {
|
partial: Some(Box::new(move |entries: MetaCacheEntries, _: &[Option<Error>]| {
|
||||||
// let cb = cb.clone();
|
// let cb = cb.clone();
|
||||||
let resolver = resolver.clone();
|
let resolver = resolver.clone();
|
||||||
if let Ok(Some(entry)) = entries.resolve(resolver) {
|
let cb = cb.clone();
|
||||||
cb(entry)
|
|
||||||
} else {
|
match entries.resolve(resolver) {
|
||||||
Box::pin(async {})
|
Ok(Some(entry)) => {
|
||||||
|
warn!("rebalance: list_objects_to_decommission get {}", &entry.name);
|
||||||
|
Box::pin(async move { cb(entry).await })
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
warn!("rebalance: list_objects_to_decommission get none");
|
||||||
|
Box::pin(async {})
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("rebalance: list_objects_to_decommission get err {:?}", &err);
|
||||||
|
Box::pin(async {})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|||||||
@@ -3667,7 +3667,7 @@ impl ObjectIO for SetDisks {
|
|||||||
error!("get_object_with_fileinfo err {:?}", e);
|
error!("get_object_with_fileinfo err {:?}", e);
|
||||||
};
|
};
|
||||||
|
|
||||||
// error!("get_object_with_fileinfo end");
|
// error!("get_object_with_fileinfo end {}/{}", bucket, object);
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(reader)
|
Ok(reader)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use lazy_static::lazy_static;
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
net::{IpAddr, SocketAddr, TcpListener, ToSocketAddrs},
|
net::{IpAddr, Ipv6Addr, SocketAddr, TcpListener, ToSocketAddrs},
|
||||||
};
|
};
|
||||||
|
|
||||||
use url::Host;
|
use url::Host;
|
||||||
@@ -141,6 +141,33 @@ impl TryFrom<String> for XHost {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// parses the address string, process the ":port" format for double-stack binding,
|
||||||
|
/// and resolve the host name or IP address. If the port is 0, an available port is assigned.
|
||||||
|
pub fn parse_and_resolve_address(addr_str: &str) -> Result<SocketAddr> {
|
||||||
|
let resolved_addr: SocketAddr = if let Some(port) = addr_str.strip_prefix(":") {
|
||||||
|
// Process the ":port" format for double stack binding
|
||||||
|
let port_str = port;
|
||||||
|
let port: u16 = port_str
|
||||||
|
.parse()
|
||||||
|
.map_err(|e| Error::from_string(format!("Invalid port format: {}, err:{:?}", addr_str, e)))?;
|
||||||
|
let final_port = if port == 0 {
|
||||||
|
get_available_port() // assume get_available_port is available here
|
||||||
|
} else {
|
||||||
|
port
|
||||||
|
};
|
||||||
|
// Using IPv6 without address specified [::], it should handle both IPv4 and IPv6
|
||||||
|
SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), final_port)
|
||||||
|
} else {
|
||||||
|
// Use existing logic to handle regular address formats
|
||||||
|
let mut addr = check_local_server_addr(addr_str)?; // assume check_local_server_addr is available here
|
||||||
|
if addr.port() == 0 {
|
||||||
|
addr.set_port(get_available_port());
|
||||||
|
}
|
||||||
|
addr
|
||||||
|
};
|
||||||
|
Ok(resolved_addr)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ pub struct RebalPoolProgress {
|
|||||||
#[serde(rename = "object")]
|
#[serde(rename = "object")]
|
||||||
pub object: String,
|
pub object: String,
|
||||||
#[serde(rename = "elapsed")]
|
#[serde(rename = "elapsed")]
|
||||||
pub elapsed: Duration,
|
pub elapsed: u64,
|
||||||
#[serde(rename = "eta")]
|
#[serde(rename = "eta")]
|
||||||
pub eta: Duration,
|
pub eta: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
@@ -78,13 +78,13 @@ impl Operation for RebalanceStart {
|
|||||||
|
|
||||||
if store.is_decommission_running().await {
|
if store.is_decommission_running().await {
|
||||||
return Err(s3_error!(
|
return Err(s3_error!(
|
||||||
InternalError,
|
InvalidRequest,
|
||||||
"Rebalance cannot be started, decommission is already in progress"
|
"Rebalance cannot be started, decommission is already in progress"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if store.is_rebalance_started().await {
|
if store.is_rebalance_started().await {
|
||||||
return Err(s3_error!(InternalError, "Rebalance already in progress"));
|
return Err(s3_error!(OperationAborted, "Rebalance already in progress"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let bucket_infos = store
|
let bucket_infos = store
|
||||||
@@ -101,8 +101,11 @@ impl Operation for RebalanceStart {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
warn!("Rebalance started with id: {}", id);
|
||||||
if let Some(notification_sys) = get_global_notification_sys() {
|
if let Some(notification_sys) = get_global_notification_sys() {
|
||||||
|
warn!("Loading rebalance meta");
|
||||||
notification_sys.load_rebalance_meta(true).await;
|
notification_sys.load_rebalance_meta(true).await;
|
||||||
|
warn!("Rebalance meta loaded");
|
||||||
}
|
}
|
||||||
|
|
||||||
let resp = RebalanceResp { id };
|
let resp = RebalanceResp { id };
|
||||||
@@ -149,7 +152,7 @@ impl Operation for RebalanceStatus {
|
|||||||
disk_stats[disk.pool_index as usize].total_space += disk.total_space;
|
disk_stats[disk.pool_index as usize].total_space += disk.total_space;
|
||||||
}
|
}
|
||||||
|
|
||||||
let stop_time = meta.stopped_at;
|
let mut stop_time = meta.stopped_at;
|
||||||
let mut admin_status = RebalanceAdminStatus {
|
let mut admin_status = RebalanceAdminStatus {
|
||||||
id: meta.id.clone(),
|
id: meta.id.clone(),
|
||||||
stopped_at: meta.stopped_at,
|
stopped_at: meta.stopped_at,
|
||||||
@@ -171,7 +174,7 @@ impl Operation for RebalanceStatus {
|
|||||||
// Calculate total bytes to be rebalanced
|
// Calculate total bytes to be rebalanced
|
||||||
let total_bytes_to_rebal = ps.init_capacity as f64 * meta.percent_free_goal - ps.init_free_space as f64;
|
let total_bytes_to_rebal = ps.init_capacity as f64 * meta.percent_free_goal - ps.init_free_space as f64;
|
||||||
|
|
||||||
let elapsed = if let Some(start_time) = ps.info.start_time {
|
let mut elapsed = if let Some(start_time) = ps.info.start_time {
|
||||||
SystemTime::now()
|
SystemTime::now()
|
||||||
.duration_since(start_time)
|
.duration_since(start_time)
|
||||||
.map_err(|e| s3_error!(InternalError, "Failed to calculate elapsed time: {}", e))?
|
.map_err(|e| s3_error!(InternalError, "Failed to calculate elapsed time: {}", e))?
|
||||||
@@ -179,21 +182,25 @@ impl Operation for RebalanceStatus {
|
|||||||
return Err(s3_error!(InternalError, "Start time is not available"));
|
return Err(s3_error!(InternalError, "Start time is not available"));
|
||||||
};
|
};
|
||||||
|
|
||||||
let eta = if ps.bytes > 0 {
|
let mut eta = if ps.bytes > 0 {
|
||||||
Duration::from_secs_f64(total_bytes_to_rebal * elapsed.as_secs_f64() / ps.bytes as f64)
|
Duration::from_secs_f64(total_bytes_to_rebal * elapsed.as_secs_f64() / ps.bytes as f64)
|
||||||
} else {
|
} else {
|
||||||
Duration::ZERO
|
Duration::ZERO
|
||||||
};
|
};
|
||||||
|
|
||||||
let stop_time = ps.info.end_time.unwrap_or(stop_time.unwrap_or(SystemTime::now()));
|
if ps.info.end_time.is_some() {
|
||||||
|
stop_time = ps.info.end_time;
|
||||||
|
}
|
||||||
|
|
||||||
let elapsed = if ps.info.end_time.is_some() || meta.stopped_at.is_some() {
|
if let Some(stopped_at) = stop_time {
|
||||||
stop_time
|
if let Ok(du) = stopped_at.duration_since(ps.info.start_time.unwrap_or(stopped_at)) {
|
||||||
.duration_since(ps.info.start_time.unwrap_or(stop_time))
|
elapsed = du;
|
||||||
.unwrap_or_default()
|
} else {
|
||||||
} else {
|
return Err(s3_error!(InternalError, "Failed to calculate elapsed time"));
|
||||||
elapsed
|
}
|
||||||
};
|
|
||||||
|
eta = Duration::ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
admin_status.pools[i].progress = Some(RebalPoolProgress {
|
admin_status.pools[i].progress = Some(RebalPoolProgress {
|
||||||
num_objects: ps.num_objects,
|
num_objects: ps.num_objects,
|
||||||
@@ -201,8 +208,8 @@ impl Operation for RebalanceStatus {
|
|||||||
bytes: ps.bytes,
|
bytes: ps.bytes,
|
||||||
bucket: ps.bucket.clone(),
|
bucket: ps.bucket.clone(),
|
||||||
object: ps.object.clone(),
|
object: ps.object.clone(),
|
||||||
elapsed,
|
elapsed: elapsed.as_secs(),
|
||||||
eta,
|
eta: eta.as_secs(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,11 +29,11 @@ pub const DEFAULT_OBS_CONFIG: &str = "config/obs.toml";
|
|||||||
|
|
||||||
/// Default TLS key for rustfs
|
/// Default TLS key for rustfs
|
||||||
/// This is the default key for TLS.
|
/// This is the default key for TLS.
|
||||||
pub(crate) const RUSTFS_TLS_KEY: &str = "rustfs_private.key";
|
pub(crate) const RUSTFS_TLS_KEY: &str = "rustfs_key.pem";
|
||||||
|
|
||||||
/// Default TLS cert for rustfs
|
/// Default TLS cert for rustfs
|
||||||
/// This is the default cert for TLS.
|
/// This is the default cert for TLS.
|
||||||
pub(crate) const RUSTFS_TLS_CERT: &str = "rustfs_public.crt";
|
pub(crate) const RUSTFS_TLS_CERT: &str = "rustfs_cert.pem";
|
||||||
|
|
||||||
#[allow(clippy::const_is_empty)]
|
#[allow(clippy::const_is_empty)]
|
||||||
const SHORT_VERSION: &str = {
|
const SHORT_VERSION: &str = {
|
||||||
|
|||||||
+16
-49
@@ -207,42 +207,6 @@ async fn config_handler(uri: Uri, Host(host): Host) -> impl IntoResponse {
|
|||||||
let mut cfg = CONSOLE_CONFIG.get().unwrap().clone();
|
let mut cfg = CONSOLE_CONFIG.get().unwrap().clone();
|
||||||
|
|
||||||
let url = format!("{}://{}:{}", scheme, host, cfg.port);
|
let url = format!("{}://{}:{}", scheme, host, cfg.port);
|
||||||
|
|
||||||
// // 如果指定入口,直接使用
|
|
||||||
// let url = if let Some(endpoint) = &config::get_config().console_fs_endpoint {
|
|
||||||
// debug!("axum Using rustfs endpoint address: {}", endpoint);
|
|
||||||
// endpoint.clone()
|
|
||||||
// } else {
|
|
||||||
// let host_with_port = if host.contains(':') {
|
|
||||||
// host.clone()
|
|
||||||
// } else {
|
|
||||||
// format!("{}:80", host)
|
|
||||||
// };
|
|
||||||
// // 尝试解析为 socket address,但不强制要求一定要是 IP 地址
|
|
||||||
// let socket_addr = host_with_port.to_socket_addrs().ok().and_then(|mut addrs| addrs.next());
|
|
||||||
// debug!("axum Using host with port: {}, Socket address: {:?}", host_with_port, socket_addr);
|
|
||||||
// match socket_addr {
|
|
||||||
// Some(addr) if addr.ip().is_ipv4() => {
|
|
||||||
// let ipv4 = addr.ip().to_string();
|
|
||||||
// // 如果是私有 IP、环回地址或未指定地址,保留原始域名
|
|
||||||
// if is_private_ip(addr.ip()) || addr.ip().is_loopback() || addr.ip().is_unspecified() {
|
|
||||||
// let (host, _) = host_with_port.split_once(':').unwrap_or((&host, "80"));
|
|
||||||
// debug!("axum Using private IPv4 address: {}", host);
|
|
||||||
// format!("http://{}:{}", host, cfg.port)
|
|
||||||
// } else {
|
|
||||||
// debug!("axum Using public IPv4 address");
|
|
||||||
// format!("http://{}:{}", ipv4, cfg.port)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// _ => {
|
|
||||||
// // 如果不是有效的 IPv4 地址,保留原始域名
|
|
||||||
// let (host, _) = host_with_port.split_once(':').unwrap_or((&host, "80"));
|
|
||||||
// debug!("axum Using domain address: {}", host);
|
|
||||||
// format!("http://{}:{}", host, cfg.port)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
cfg.api.base_url = format!("{}{}", url, RUSTFS_ADMIN_PREFIX);
|
cfg.api.base_url = format!("{}{}", url, RUSTFS_ADMIN_PREFIX);
|
||||||
cfg.s3.endpoint = url;
|
cfg.s3.endpoint = url;
|
||||||
|
|
||||||
@@ -273,26 +237,29 @@ pub async fn start_static_file_server(
|
|||||||
.layer(cors)
|
.layer(cors)
|
||||||
.layer(tower_http::compression::CompressionLayer::new().gzip(true).deflate(true))
|
.layer(tower_http::compression::CompressionLayer::new().gzip(true).deflate(true))
|
||||||
.layer(TraceLayer::new_for_http());
|
.layer(TraceLayer::new_for_http());
|
||||||
let local_addr: SocketAddr = addrs.parse().expect("Failed to parse socket address");
|
|
||||||
info!("WebUI: http://{}:{} http://127.0.0.1:{}", local_ip, local_addr.port(), local_addr.port());
|
use ecstore::utils::net;
|
||||||
|
let server_addr = net::parse_and_resolve_address(addrs).expect("Failed to parse socket address");
|
||||||
|
let server_port = server_addr.port();
|
||||||
|
let server_address = server_addr.to_string();
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"WebUI: http://{}:{} http://127.0.0.1:{} http://{}",
|
||||||
|
local_ip, server_port, server_port, server_address
|
||||||
|
);
|
||||||
info!(" RootUser: {}", access_key);
|
info!(" RootUser: {}", access_key);
|
||||||
info!(" RootPass: {}", secret_key);
|
info!(" RootPass: {}", secret_key);
|
||||||
|
|
||||||
// Check and start the HTTPS/HTTP server
|
// Check and start the HTTPS/HTTP server
|
||||||
match start_server(addrs, local_addr, tls_path, app.clone()).await {
|
match start_server(server_addr, tls_path, app.clone()).await {
|
||||||
Ok(_) => info!("Server shutdown gracefully"),
|
Ok(_) => info!("Server shutdown gracefully"),
|
||||||
Err(e) => error!("Server error: {}", e),
|
Err(e) => error!("Server error: {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn start_server(addrs: &str, local_addr: SocketAddr, tls_path: Option<String>, app: Router) -> io::Result<()> {
|
async fn start_server(server_addr: SocketAddr, tls_path: Option<String>, app: Router) -> io::Result<()> {
|
||||||
let tls_path = tls_path.unwrap_or_default();
|
let tls_path = tls_path.unwrap_or_default();
|
||||||
let key_path = format!("{}/{}", tls_path, RUSTFS_TLS_KEY);
|
let key_path = format!("{}/{}", tls_path, RUSTFS_TLS_KEY);
|
||||||
let cert_path = format!("{}/{}", tls_path, RUSTFS_TLS_CERT);
|
let cert_path = format!("{}/{}", tls_path, RUSTFS_TLS_CERT);
|
||||||
|
|
||||||
let addr = addrs
|
|
||||||
.parse::<SocketAddr>()
|
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("Invalid address: {}", e)))?;
|
|
||||||
|
|
||||||
let handle = axum_server::Handle::new();
|
let handle = axum_server::Handle::new();
|
||||||
// create a signal off listening task
|
// create a signal off listening task
|
||||||
let handle_clone = handle.clone();
|
let handle_clone = handle.clone();
|
||||||
@@ -309,24 +276,24 @@ async fn start_server(addrs: &str, local_addr: SocketAddr, tls_path: Option<Stri
|
|||||||
match RustlsConfig::from_pem_file(cert_path, key_path).await {
|
match RustlsConfig::from_pem_file(cert_path, key_path).await {
|
||||||
Ok(config) => {
|
Ok(config) => {
|
||||||
info!("Starting HTTPS server...");
|
info!("Starting HTTPS server...");
|
||||||
axum_server::bind_rustls(local_addr, config)
|
axum_server::bind_rustls(server_addr, config)
|
||||||
.handle(handle.clone())
|
.handle(handle.clone())
|
||||||
.serve(app.into_make_service())
|
.serve(app.into_make_service())
|
||||||
.await
|
.await
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||||
|
|
||||||
info!("HTTPS server running on https://{}", addr);
|
info!("HTTPS server running on https://{}", server_addr);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to create TLS config: {}", e);
|
error!("Failed to create TLS config: {}", e);
|
||||||
start_http_server(addr, app, handle).await
|
start_http_server(server_addr, app, handle).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
info!("TLS certificates not found at {} and {}", key_path, cert_path);
|
info!("TLS certificates not found at {} and {}", key_path, cert_path);
|
||||||
start_http_server(addr, app, handle).await
|
start_http_server(server_addr, app, handle).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-2
@@ -40,7 +40,7 @@ use tokio::spawn;
|
|||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio_stream::wrappers::ReceiverStream;
|
use tokio_stream::wrappers::ReceiverStream;
|
||||||
use tonic::{Request, Response, Status, Streaming};
|
use tonic::{Request, Response, Status, Streaming};
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
type ResponseStream<T> = Pin<Box<dyn Stream<Item = Result<T, tonic::Status>> + Send>>;
|
type ResponseStream<T> = Pin<Box<dyn Stream<Item = Result<T, tonic::Status>> + Send>>;
|
||||||
|
|
||||||
@@ -2388,19 +2388,27 @@ impl Node for NodeService {
|
|||||||
|
|
||||||
let LoadRebalanceMetaRequest { start_rebalance } = request.into_inner();
|
let LoadRebalanceMetaRequest { start_rebalance } = request.into_inner();
|
||||||
|
|
||||||
|
warn!("handle LoadRebalanceMetaRequest");
|
||||||
|
|
||||||
store.load_rebalance_meta().await.map_err(|err| {
|
store.load_rebalance_meta().await.map_err(|err| {
|
||||||
error!("load_rebalance_meta err {:?}", err);
|
error!("load_rebalance_meta err {:?}", err);
|
||||||
Status::internal(err.to_string())
|
Status::internal(err.to_string())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
warn!("load_rebalance_meta success");
|
||||||
|
|
||||||
if start_rebalance {
|
if start_rebalance {
|
||||||
|
warn!("start rebalance");
|
||||||
let store = store.clone();
|
let store = store.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
store.start_rebalance().await;
|
store.start_rebalance().await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
unimplemented!()
|
Ok(tonic::Response::new(LoadRebalanceMetaResponse {
|
||||||
|
success: true,
|
||||||
|
error_info: None,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn load_transition_tier_config(
|
async fn load_transition_tier_config(
|
||||||
|
|||||||
+47
-27
@@ -27,7 +27,7 @@ use ecstore::config as ecconfig;
|
|||||||
use ecstore::config::GLOBAL_ConfigSys;
|
use ecstore::config::GLOBAL_ConfigSys;
|
||||||
use ecstore::heal::background_heal_ops::init_auto_heal;
|
use ecstore::heal::background_heal_ops::init_auto_heal;
|
||||||
use ecstore::store_api::BucketOptions;
|
use ecstore::store_api::BucketOptions;
|
||||||
use ecstore::utils::net::{self, get_available_port};
|
use ecstore::utils::net;
|
||||||
use ecstore::StorageAPI;
|
use ecstore::StorageAPI;
|
||||||
use ecstore::{
|
use ecstore::{
|
||||||
endpoints::EndpointServerPools,
|
endpoints::EndpointServerPools,
|
||||||
@@ -136,14 +136,8 @@ async fn run(opt: config::Opt) -> Result<()> {
|
|||||||
|
|
||||||
debug!("opt: {:?}", &opt);
|
debug!("opt: {:?}", &opt);
|
||||||
|
|
||||||
let mut server_addr = net::check_local_server_addr(opt.address.as_str())?;
|
let server_addr = net::parse_and_resolve_address(opt.address.as_str())?;
|
||||||
|
|
||||||
if server_addr.port() == 0 {
|
|
||||||
server_addr.set_port(get_available_port());
|
|
||||||
}
|
|
||||||
|
|
||||||
let server_port = server_addr.port();
|
let server_port = server_addr.port();
|
||||||
|
|
||||||
let server_address = server_addr.to_string();
|
let server_address = server_addr.to_string();
|
||||||
|
|
||||||
debug!("server_address {}", &server_address);
|
debug!("server_address {}", &server_address);
|
||||||
@@ -263,32 +257,58 @@ async fn run(opt: config::Opt) -> Result<()> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let rpc_service = NodeServiceServer::with_interceptor(make_server(), check_auth);
|
|
||||||
|
|
||||||
let tls_path = opt.tls_path.clone().unwrap_or_default();
|
let tls_path = opt.tls_path.clone().unwrap_or_default();
|
||||||
let key_path = format!("{}/{}", tls_path, RUSTFS_TLS_KEY);
|
let has_tls_certs = tokio::fs::metadata(&tls_path).await.is_ok();
|
||||||
let cert_path = format!("{}/{}", tls_path, RUSTFS_TLS_CERT);
|
|
||||||
let has_tls_certs = tokio::try_join!(tokio::fs::metadata(key_path.clone()), tokio::fs::metadata(cert_path.clone())).is_ok();
|
|
||||||
debug!("Main TLS certs: {:?}", has_tls_certs);
|
|
||||||
let tls_acceptor = if has_tls_certs {
|
let tls_acceptor = if has_tls_certs {
|
||||||
debug!("Found TLS certificates, starting with HTTPS");
|
debug!("Found TLS directory, checking for certificates");
|
||||||
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
|
||||||
let certs = utils::load_certs(cert_path.as_str()).map_err(|e| error(e.to_string()))?;
|
// 1. Try to load all certificates directly (including root and subdirectories)
|
||||||
let key = utils::load_private_key(key_path.as_str()).map_err(|e| error(e.to_string()))?;
|
match utils::load_all_certs_from_directory(&tls_path) {
|
||||||
let mut server_config = ServerConfig::builder()
|
Ok(cert_key_pairs) if !cert_key_pairs.is_empty() => {
|
||||||
.with_no_client_auth()
|
debug!("Found {} certificates, starting with HTTPS", cert_key_pairs.len());
|
||||||
.with_single_cert(certs, key)
|
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
||||||
.map_err(|e| error(e.to_string()))?;
|
|
||||||
server_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()];
|
// create a multi certificate configuration
|
||||||
Some(TlsAcceptor::from(Arc::new(server_config)))
|
let mut server_config = ServerConfig::builder()
|
||||||
|
.with_no_client_auth()
|
||||||
|
.with_cert_resolver(Arc::new(utils::create_multi_cert_resolver(cert_key_pairs)?));
|
||||||
|
|
||||||
|
server_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()];
|
||||||
|
Some(TlsAcceptor::from(Arc::new(server_config)))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// 2. If the synthesis fails, fall back to the traditional document certificate mode (backward compatible)
|
||||||
|
let key_path = format!("{}/{}", tls_path, RUSTFS_TLS_KEY);
|
||||||
|
let cert_path = format!("{}/{}", tls_path, RUSTFS_TLS_CERT);
|
||||||
|
let has_single_cert =
|
||||||
|
tokio::try_join!(tokio::fs::metadata(key_path.clone()), tokio::fs::metadata(cert_path.clone())).is_ok();
|
||||||
|
|
||||||
|
if has_single_cert {
|
||||||
|
debug!("Found legacy single TLS certificate, starting with HTTPS");
|
||||||
|
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
||||||
|
let certs = utils::load_certs(cert_path.as_str()).map_err(|e| error(e.to_string()))?;
|
||||||
|
let key = utils::load_private_key(key_path.as_str()).map_err(|e| error(e.to_string()))?;
|
||||||
|
let mut server_config = ServerConfig::builder()
|
||||||
|
.with_no_client_auth()
|
||||||
|
.with_single_cert(certs, key)
|
||||||
|
.map_err(|e| error(e.to_string()))?;
|
||||||
|
server_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()];
|
||||||
|
Some(TlsAcceptor::from(Arc::new(server_config)))
|
||||||
|
} else {
|
||||||
|
debug!("No valid TLS certificates found, starting with HTTP");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
debug!("TLS certificates not found, starting with HTTP");
|
debug!("TLS certificates not found, starting with HTTP");
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let rpc_service = NodeServiceServer::with_interceptor(make_server(), check_auth);
|
||||||
let state_manager = ServiceStateManager::new();
|
let state_manager = ServiceStateManager::new();
|
||||||
let worker_state_manager = state_manager.clone();
|
let worker_state_manager = state_manager.clone();
|
||||||
// 更新服务状态为启动中
|
// Update service status to Starting
|
||||||
state_manager.update(ServiceState::Starting);
|
state_manager.update(ServiceState::Starting);
|
||||||
|
|
||||||
// Create shutdown channel
|
// Create shutdown channel
|
||||||
@@ -296,7 +316,7 @@ async fn run(opt: config::Opt) -> Result<()> {
|
|||||||
let shutdown_tx_clone = shutdown_tx.clone();
|
let shutdown_tx_clone = shutdown_tx.clone();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
// 错误处理改进
|
// error handling improvements
|
||||||
let sigterm_inner = match signal(SignalKind::terminate()) {
|
let sigterm_inner = match signal(SignalKind::terminate()) {
|
||||||
Ok(signal) => signal,
|
Ok(signal) => signal,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -367,7 +387,7 @@ async fn run(opt: config::Opt) -> Result<()> {
|
|||||||
let graceful = Arc::new(GracefulShutdown::new());
|
let graceful = Arc::new(GracefulShutdown::new());
|
||||||
debug!("graceful initiated");
|
debug!("graceful initiated");
|
||||||
|
|
||||||
// 服务准备就绪
|
// service ready
|
||||||
worker_state_manager.update(ServiceState::Ready);
|
worker_state_manager.update(ServiceState::Ready);
|
||||||
let value = hybrid_service.clone();
|
let value = hybrid_service.clone();
|
||||||
loop {
|
loop {
|
||||||
|
|||||||
+158
-3
@@ -1,8 +1,19 @@
|
|||||||
|
use crate::config::{RUSTFS_TLS_CERT, RUSTFS_TLS_KEY};
|
||||||
|
use rustls::server::{ClientHello, ResolvesServerCert, ResolvesServerCertUsingSni};
|
||||||
|
use rustls::sign::CertifiedKey;
|
||||||
use rustls_pemfile::{certs, private_key};
|
use rustls_pemfile::{certs, private_key};
|
||||||
use rustls_pki_types::{CertificateDer, PrivateKeyDer};
|
use rustls_pki_types::{CertificateDer, PrivateKeyDer};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::io::Error;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::{fs, io};
|
use std::{fs, io};
|
||||||
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
|
/// Get the local IP address.
|
||||||
|
/// This function retrieves the local IP address of the machine.
|
||||||
pub(crate) fn get_local_ip() -> Option<std::net::Ipv4Addr> {
|
pub(crate) fn get_local_ip() -> Option<std::net::Ipv4Addr> {
|
||||||
match local_ip_address::local_ip() {
|
match local_ip_address::local_ip() {
|
||||||
Ok(IpAddr::V4(ip)) => Some(ip),
|
Ok(IpAddr::V4(ip)) => Some(ip),
|
||||||
@@ -12,17 +23,24 @@ pub(crate) fn get_local_ip() -> Option<std::net::Ipv4Addr> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Load public certificate from file.
|
/// Load public certificate from file.
|
||||||
|
/// This function loads a public certificate from the specified file.
|
||||||
pub(crate) fn load_certs(filename: &str) -> io::Result<Vec<CertificateDer<'static>>> {
|
pub(crate) fn load_certs(filename: &str) -> io::Result<Vec<CertificateDer<'static>>> {
|
||||||
// Open certificate file.
|
// Open certificate file.
|
||||||
let cert_file = fs::File::open(filename).map_err(|e| error(format!("failed to open {}: {}", filename, e)))?;
|
let cert_file = fs::File::open(filename).map_err(|e| error(format!("failed to open {}: {}", filename, e)))?;
|
||||||
let mut reader = io::BufReader::new(cert_file);
|
let mut reader = io::BufReader::new(cert_file);
|
||||||
|
|
||||||
// Load and return certificate.
|
// Load and return certificate.
|
||||||
let certs = certs(&mut reader).collect::<Result<Vec<_>, _>>()?;
|
let certs = certs(&mut reader)
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
.map_err(|_| error(format!("certificate file {} format error", filename)))?;
|
||||||
|
if certs.is_empty() {
|
||||||
|
return Err(error(format!("No valid certificate was found in the certificate file {}", filename)));
|
||||||
|
}
|
||||||
Ok(certs)
|
Ok(certs)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load private key from file.
|
/// Load private key from file.
|
||||||
|
/// This function loads a private key from the specified file.
|
||||||
pub(crate) fn load_private_key(filename: &str) -> io::Result<PrivateKeyDer<'static>> {
|
pub(crate) fn load_private_key(filename: &str) -> io::Result<PrivateKeyDer<'static>> {
|
||||||
// Open keyfile.
|
// Open keyfile.
|
||||||
let keyfile = fs::File::open(filename).map_err(|e| error(format!("failed to open {}: {}", filename, e)))?;
|
let keyfile = fs::File::open(filename).map_err(|e| error(format!("failed to open {}: {}", filename, e)))?;
|
||||||
@@ -32,6 +50,143 @@ pub(crate) fn load_private_key(filename: &str) -> io::Result<PrivateKeyDer<'stat
|
|||||||
private_key(&mut reader)?.ok_or_else(|| error(format!("no private key found in {}", filename)))
|
private_key(&mut reader)?.ok_or_else(|| error(format!("no private key found in {}", filename)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn error(err: String) -> io::Error {
|
/// error function
|
||||||
io::Error::new(io::ErrorKind::Other, err)
|
pub(crate) fn error(err: String) -> Error {
|
||||||
|
Error::new(io::ErrorKind::Other, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load all certificates and private keys in the directory
|
||||||
|
/// This function loads all certificate and private key pairs from the specified directory.
|
||||||
|
/// It looks for files named `rustfs_cert.pem` and `rustfs_key.pem` in each subdirectory.
|
||||||
|
/// The root directory can also contain a default certificate/private key pair.
|
||||||
|
pub(crate) fn load_all_certs_from_directory(
|
||||||
|
dir_path: &str,
|
||||||
|
) -> io::Result<HashMap<String, (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)>> {
|
||||||
|
let mut cert_key_pairs = HashMap::new();
|
||||||
|
let dir = Path::new(dir_path);
|
||||||
|
|
||||||
|
if !dir.exists() || !dir.is_dir() {
|
||||||
|
return Err(error(format!(
|
||||||
|
"The certificate directory does not exist or is not a directory: {}",
|
||||||
|
dir_path
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. First check whether there is a certificate/private key pair in the root directory
|
||||||
|
let root_cert_path = dir.join(RUSTFS_TLS_CERT);
|
||||||
|
let root_key_path = dir.join(RUSTFS_TLS_KEY);
|
||||||
|
|
||||||
|
if root_cert_path.exists() && root_key_path.exists() {
|
||||||
|
debug!("find the root directory certificate: {:?}", root_cert_path);
|
||||||
|
let root_cert_str = root_cert_path
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| error(format!("Invalid UTF-8 in root certificate path: {:?}", root_cert_path)))?;
|
||||||
|
let root_key_str = root_key_path
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| error(format!("Invalid UTF-8 in root key path: {:?}", root_key_path)))?;
|
||||||
|
match load_cert_key_pair(root_cert_str, root_key_str) {
|
||||||
|
Ok((certs, key)) => {
|
||||||
|
// The root directory certificate is used as the default certificate and is stored using special keys.
|
||||||
|
cert_key_pairs.insert("default".to_string(), (certs, key));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("unable to load root directory certificate: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.iterate through all folders in the directory
|
||||||
|
for entry in fs::read_dir(dir)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
|
||||||
|
if path.is_dir() {
|
||||||
|
let domain_name = path
|
||||||
|
.file_name()
|
||||||
|
.and_then(|name| name.to_str())
|
||||||
|
.ok_or_else(|| error(format!("invalid domain name directory:{:?}", path)))?;
|
||||||
|
|
||||||
|
// find certificate and private key files
|
||||||
|
let cert_path = path.join(RUSTFS_TLS_CERT); // e.g., rustfs_cert.pem
|
||||||
|
let key_path = path.join(RUSTFS_TLS_KEY); // e.g., rustfs_key.pem
|
||||||
|
|
||||||
|
if cert_path.exists() && key_path.exists() {
|
||||||
|
debug!("find the domain name certificate: {} in {:?}", domain_name, cert_path);
|
||||||
|
match load_cert_key_pair(cert_path.to_str().unwrap(), key_path.to_str().unwrap()) {
|
||||||
|
Ok((certs, key)) => {
|
||||||
|
cert_key_pairs.insert(domain_name.to_string(), (certs, key));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("unable to load the certificate for {} domain name: {}", domain_name, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cert_key_pairs.is_empty() {
|
||||||
|
return Err(error(format!("No valid certificate/private key pair found in directory {}", dir_path)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(cert_key_pairs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// loading a single certificate private key pair
|
||||||
|
/// This function loads a certificate and private key from the specified paths.
|
||||||
|
/// It returns a tuple containing the certificate and private key.
|
||||||
|
fn load_cert_key_pair(cert_path: &str, key_path: &str) -> io::Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
|
||||||
|
let certs = load_certs(cert_path)?;
|
||||||
|
let key = load_private_key(key_path)?;
|
||||||
|
Ok((certs, key))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a multi-cert resolver
|
||||||
|
/// This function loads all certificates and private keys from the specified directory.
|
||||||
|
/// It uses the first certificate/private key pair found in the root directory as the default certificate.
|
||||||
|
/// The rest of the certificates/private keys are used for SNI resolution.
|
||||||
|
///
|
||||||
|
pub fn create_multi_cert_resolver(
|
||||||
|
cert_key_pairs: HashMap<String, (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)>,
|
||||||
|
) -> io::Result<impl ResolvesServerCert> {
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct MultiCertResolver {
|
||||||
|
cert_resolver: ResolvesServerCertUsingSni,
|
||||||
|
default_cert: Option<Arc<CertifiedKey>>,
|
||||||
|
}
|
||||||
|
impl ResolvesServerCert for MultiCertResolver {
|
||||||
|
fn resolve(&self, client_hello: ClientHello) -> Option<Arc<CertifiedKey>> {
|
||||||
|
// try matching certificates with sni
|
||||||
|
if let Some(cert) = self.cert_resolver.resolve(client_hello) {
|
||||||
|
return Some(cert);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no matching SNI certificate, use the default certificate
|
||||||
|
self.default_cert.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut resolver = ResolvesServerCertUsingSni::new();
|
||||||
|
let mut default_cert = None;
|
||||||
|
|
||||||
|
for (domain, (certs, key)) in cert_key_pairs {
|
||||||
|
// create a signature
|
||||||
|
let signing_key = rustls::crypto::aws_lc_rs::sign::any_supported_type(&key)
|
||||||
|
.map_err(|_| error(format!("unsupported private key types:{}", domain)))?;
|
||||||
|
|
||||||
|
// create a CertifiedKey
|
||||||
|
let certified_key = CertifiedKey::new(certs, signing_key);
|
||||||
|
if domain == "default" {
|
||||||
|
default_cert = Some(Arc::new(certified_key.clone()));
|
||||||
|
} else {
|
||||||
|
// add certificate to resolver
|
||||||
|
resolver
|
||||||
|
.add(&domain, certified_key)
|
||||||
|
.map_err(|e| error(format!("failed to add a domain name certificate:{},err: {:?}", domain, e)))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(MultiCertResolver {
|
||||||
|
cert_resolver: resolver,
|
||||||
|
default_cert,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-4
@@ -28,12 +28,12 @@ fi
|
|||||||
|
|
||||||
export RUSTFS_VOLUMES="./target/volume/test{0...4}"
|
export RUSTFS_VOLUMES="./target/volume/test{0...4}"
|
||||||
# export RUSTFS_VOLUMES="./target/volume/test"
|
# export RUSTFS_VOLUMES="./target/volume/test"
|
||||||
export RUSTFS_ADDRESS="[::]:9000"
|
export RUSTFS_ADDRESS=":9000"
|
||||||
export RUSTFS_CONSOLE_ENABLE=true
|
export RUSTFS_CONSOLE_ENABLE=true
|
||||||
export RUSTFS_CONSOLE_ADDRESS="[::]:9002"
|
export RUSTFS_CONSOLE_ADDRESS=":9002"
|
||||||
# export RUSTFS_SERVER_DOMAINS="localhost:9000"
|
# export RUSTFS_SERVER_DOMAINS="localhost:9000"
|
||||||
# HTTPS 证书目录
|
# HTTPS 证书目录
|
||||||
# export RUSTFS_TLS_PATH="./deploy/certs"
|
export RUSTFS_TLS_PATH="./deploy/certs"
|
||||||
|
|
||||||
# 具体路径修改为配置文件真实路径,obs.example.toml 仅供参考 其中`RUSTFS_OBS_CONFIG` 和下面变量二选一
|
# 具体路径修改为配置文件真实路径,obs.example.toml 仅供参考 其中`RUSTFS_OBS_CONFIG` 和下面变量二选一
|
||||||
export RUSTFS_OBS_CONFIG="./deploy/config/obs.example.toml"
|
export RUSTFS_OBS_CONFIG="./deploy/config/obs.example.toml"
|
||||||
@@ -47,7 +47,7 @@ export RUSTFS__OBSERVABILITY__SERVICE_NAME=rustfs
|
|||||||
export RUSTFS__OBSERVABILITY__SERVICE_VERSION=0.1.0
|
export RUSTFS__OBSERVABILITY__SERVICE_VERSION=0.1.0
|
||||||
export RUSTFS__OBSERVABILITY__ENVIRONMENT=develop
|
export RUSTFS__OBSERVABILITY__ENVIRONMENT=develop
|
||||||
export RUSTFS__OBSERVABILITY__LOGGER_LEVEL=debug
|
export RUSTFS__OBSERVABILITY__LOGGER_LEVEL=debug
|
||||||
export RUSTFS__OBSERVABILITY__LOCAL_LOGGER_ENABLED=true
|
export RUSTFS__OBSERVABILITY__LOCAL_LOGGING_ENABLED=true
|
||||||
export RUSTFS__SINKS__FILE__ENABLED=true
|
export RUSTFS__SINKS__FILE__ENABLED=true
|
||||||
export RUSTFS__SINKS__FILE__PATH="./deploy/logs/rustfs.log"
|
export RUSTFS__SINKS__FILE__PATH="./deploy/logs/rustfs.log"
|
||||||
export RUSTFS__SINKS__WEBHOOK__ENABLED=false
|
export RUSTFS__SINKS__WEBHOOK__ENABLED=false
|
||||||
@@ -58,6 +58,11 @@ export RUSTFS__SINKS__KAFKA__BOOTSTRAP_SERVERS=""
|
|||||||
export RUSTFS__SINKS__KAFKA__TOPIC=""
|
export RUSTFS__SINKS__KAFKA__TOPIC=""
|
||||||
export RUSTFS__LOGGER__QUEUE_CAPACITY=10
|
export RUSTFS__LOGGER__QUEUE_CAPACITY=10
|
||||||
|
|
||||||
|
export OTEL_INSTRUMENTATION_NAME="rustfs"
|
||||||
|
export OTEL_INSTRUMENTATION_VERSION="0.1.1"
|
||||||
|
export OTEL_INSTRUMENTATION_SCHEMA_URL="https://opentelemetry.io/schemas/1.31.0"
|
||||||
|
export OTEL_INSTRUMENTATION_ATTRIBUTES="env=production"
|
||||||
|
|
||||||
# 事件消息配置
|
# 事件消息配置
|
||||||
export RUSTFS_EVENT_CONFIG="./deploy/config/event.example.toml"
|
export RUSTFS_EVENT_CONFIG="./deploy/config/event.example.toml"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user