# Ozone Docker Compose 啟動與 OpenDAL S3 介面使用指南
# Ozone Docker Compose Setup and OpenDAL S3 Interface Guide
本文件說明如何使用 Docker Compose 啟動 Apache Ozone,並透過 OpenDAL 的 S3 介面連線本地環境。
This document describes how to start Apache Ozone using Docker Compose and connect it to your local environment through OpenDAL's S3 interface.
---
## 目錄 / Table of Contents
- [前置需求 / Prerequisites](#前置需求--prerequisites)
- [系統需求 / System Requirements](#系統需求--system-requirements)
- [安裝 OpenDAL / Install OpenDAL](#安裝-opendal--install-opendal)
- [啟動 Ozone 叢集 / Starting Ozone Cluster](#啟動-ozone-叢集--starting-ozone-cluster)
- [驗證 Ozone 服務 / Verifying Ozone Services](#驗證-ozone-服務--verifying-ozone-services)
- [設定 S3 存取憑證 / Configuring S3 Access Credentials](#設定-s3-存取憑證--configuring-s3-access-credentials)
- [使用 OpenDAL S3 介面 / Using OpenDAL S3 Interface](#使用-opendal-s3-介面--using-opendal-s3-interface)
- [快速測試連線 / Quick Connection Test](#快速測試連線--quick-connection-test)
- [OpenDAL 傳輸機制與檔案移動條件 / OpenDAL Transfer Mechanism and File Movement Conditions](#opendal-傳輸機制與檔案移動條件--opendal-transfer-mechanism-and-file-movement-conditions)
- [上傳本地檔案到 Ozone / Upload Local Files to Ozone](#上傳本地檔案到-ozone--upload-local-files-to-ozone)
- [常見操作範例 / Common Operations Examples](#常見操作範例--common-operations-examples)
- [故障排除 / Troubleshooting](#故障排除--troubleshooting)
- [完整測試腳本程式碼 / Complete Test Scripts Code](#完整測試腳本程式碼--complete-test-scripts-code)
---
## 前置需求 / Prerequisites
### 系統需求 / System Requirements
- **Docker**: 最新穩定版本 / Latest stable version
- **Docker Compose**: 至少 v2.30 / At least v2.30
- **Python 3** (可選,用於 Python 範例) / Python 3 (Optional, for Python examples)
- **OpenDAL**: 需要安裝的 OpenDAL 庫 / OpenDAL library (needs to be installed)
### 檢查 Docker 環境 / Check Docker Environment
```bash
# 檢查 Docker 版本 / Check Docker version
docker --version
# 檢查 Docker Compose 版本 / Check Docker Compose version
docker compose version
```
### 安裝 OpenDAL / Install OpenDAL
根據您使用的語言,選擇對應的安裝方式:
Depending on your programming language, choose the corresponding installation method:
#### Python / Python
```bash
# 使用 pip 安裝 / Install using pip
pip3 install opendal
# 或使用 pip / Or use pip
pip install opendal
```
#### Rust / Rust
```bash
# 在 Cargo.toml 中添加依賴 / Add dependency in Cargo.toml
cargo add opendal
```
#### Node.js / Node.js
```bash
# 使用 npm 安裝 / Install using npm
npm install opendal
# 或使用 yarn / Or use yarn
yarn add opendal
```
#### Java / Java
在您的 `pom.xml` 或 `build.gradle` 中添加 OpenDAL 依賴。
Add OpenDAL dependency in your `pom.xml` or `build.gradle`.
**驗證安裝 / Verify Installation:**
```bash
# Python / Python
python3 -c "import opendal; print('OpenDAL version:', opendal.__version__)"
# Rust / Rust
cargo --version # 確保 Cargo 已安裝 / Ensure Cargo is installed
# Node.js / Node.js
node -e "console.log(require('opendal').version)"
```
---
## 啟動 Ozone 叢集 / Starting Ozone Cluster
### 方法一:使用官方預建置的 Docker Compose 設定(推薦)/ Method 1: Using Official Pre-built Docker Compose Configuration (Recommended)
**推薦使用此方法**,因為它使用預建置的 Docker 映像檔,無需先建置 Ozone 專案。
**This method is recommended** as it uses pre-built Docker images and doesn't require building the Ozone project first.
#### 步驟 1: 下載官方 Docker Compose 設定 / Step 1: Download Official Docker Compose Configuration
```bash
# 在專案根目錄下載設定檔 / Download configuration file in project root
# 請替換為您的實際專案路徑 / Please replace with your actual project path
cd /path/to/ozone
curl -O https://raw.githubusercontent.com/apache/ozone-docker/refs/heads/latest/docker-compose.yaml
```
#### 步驟 2: 啟動 Ozone 集群 / Step 2: Start Ozone Cluster
**單一 DataNode 啟動 / Single DataNode:**
```bash
docker compose up -d
```
**多個 DataNode 啟動(建議用於測試複製功能)/ Multiple DataNodes (Recommended for testing replication):**
```bash
# 啟動 3 個 DataNode / Start 3 DataNodes
docker compose up -d --scale datanode=3
```
此命令會自動從 Docker Hub 拉取所需的 `apache/ozone:2.0.0` 映像檔並啟動所有服務。
This command will automatically pull the required `apache/ozone:2.0.0` images from Docker Hub and start all services.
### 方法二:使用專案內建的 Docker Compose 設定 / Method 2: Using Built-in Docker Compose Configuration
**注意 / Note:** 此方法需要先建置 Ozone 專案,或者修復 `.env` 檔案中的 Maven 變數。
**Note:** This method requires building the Ozone project first, or fixing Maven variables in the `.env` file.
Ozone 專案已經包含了 Docker Compose 設定檔,位於 `hadoop-ozone/dist/src/main/compose/ozone/` 目錄。
The Ozone project already includes Docker Compose configuration files located in the `hadoop-ozone/dist/src/main/compose/ozone/` directory.
#### 步驟 1: 進入 Docker Compose 目錄 / Step 1: Navigate to Docker Compose Directory
```bash
cd hadoop-ozone/dist/src/main/compose/ozone/
```
#### 步驟 2: 修復 .env 檔案(如果需要)/ Step 2: Fix .env File (If Needed)
如果遇到 `.env` 檔案中的 Maven 變數錯誤,需要手動修復:
If you encounter Maven variable errors in the `.env` file, you need to fix it manually:
```bash
# 備份原始檔案 / Backup original file
cp .env .env.backup
# 建立修復後的 .env 檔案 / Create fixed .env file
cat > .env << 'EOF'
HDDS_VERSION=2.2.0-SNAPSHOT
HADOOP_IMAGE=apache/hadoop:3
OZONE_RUNNER_VERSION=latest
OZONE_RUNNER_IMAGE=apache/ozone-runner
OZONE_OPTS=
EOF
```
#### 步驟 3: 啟動 Ozone 集群 / Step 3: Start Ozone Cluster
**使用專案提供的啟動腳本 / Using Project Startup Script:**
```bash
# 單一 DataNode / Single DataNode
./run.sh -d
# 多個 DataNode / Multiple DataNodes
OZONE_DATANODES=3 ./run.sh -d
```
**或直接使用 docker compose / Or use docker compose directly:**
```bash
# 設定必要的環境變數 / Set required environment variables
export OZONE_RUNNER_IMAGE=apache/ozone-runner
export OZONE_RUNNER_VERSION=latest
# 啟動叢集 / Start cluster
docker compose up -d --scale datanode=1
```
---
## 驗證 Ozone 服務 / Verifying Ozone Services
### 檢查服務狀態 / Check Service Status
```bash
# 查看所有服務狀態 / View all service status
docker compose ps
# 查看特定服務日誌 / View specific service logs
docker compose logs s3g
docker compose logs om
docker compose logs scm
docker compose logs datanode
# 即時追蹤日誌 / Follow logs in real-time
docker compose logs -f s3g
```
### 存取 Web UI / Access Web UIs
啟動成功後,您可以透過以下網址存取 Ozone 的管理介面:
After successful startup, you can access Ozone's management interfaces at the following addresses:
- **Ozone Manager UI**: http://localhost:9874
- **Storage Container Manager (SCM) UI**: http://localhost:9876
- **Recon UI**: http://localhost:9888
- **S3 Gateway**: http://localhost:9878
### 測試 S3 Gateway 連線 / Test S3 Gateway Connection
```bash
# 測試 S3 Gateway 是否正常執行 / Test if S3 Gateway is running
curl http://localhost:9878
```
**預期結果 / Expected Result:**
- 如果返回 403 錯誤或 XML 錯誤訊息,這是正常的,表示 S3 Gateway 正在執行,只是需要認證。
- If you get a 403 error or XML error message, this is normal - it means the S3 Gateway is running but requires authentication.
**驗證所有服務的 HTTP 狀態碼 / Verify HTTP Status Codes for All Services:**
```bash
# 檢查所有服務的 HTTP 響應 / Check HTTP responses for all services
curl -s -o /dev/null -w "Ozone Manager: %{http_code}\n" http://localhost:9874
curl -s -o /dev/null -w "SCM: %{http_code}\n" http://localhost:9876
curl -s -o /dev/null -w "Recon: %{http_code}\n" http://localhost:9888
curl -s -o /dev/null -w "S3 Gateway: %{http_code}\n" http://localhost:9878
```
**預期的狀態碼 / Expected Status Codes:**
- Ozone Manager: `200` (正常 / OK)
- SCM: `200` (正常 / OK)
- Recon: `200` (正常 / OK)
- S3 Gateway: `403` 或 `400` (正常,需要認證 / OK, requires authentication)
---
## 設定 S3 存取憑證 / Configuring S3 Access Credentials
### 重要說明 / Important Note
**在非安全模式下,您不需要申請 token 或生成憑證!**
**In non-secure mode, you do NOT need to apply for tokens or generate credentials!**
預設的 Ozone Docker Compose 設定是**非安全模式**(security disabled),這意味著:
The default Ozone Docker Compose configuration is in **non-secure mode** (security disabled), which means:
- ✅ 可以使用**任意的**存取金鑰和秘密金鑰
- ✅ You can use **any arbitrary** access key and secret key
- ❌ 不需要 Kerberos 認證
- ❌ No Kerberos authentication required
- ❌ 不需要執行 `ozone s3 getsecret` 命令
- ❌ No need to run `ozone s3 getsecret` command
**只有在啟用安全模式(Kerberos)時,才需要透過 `ozone s3 getsecret` 命令取得憑證。**
**Only when security mode (Kerberos) is enabled, you need to obtain credentials via the `ozone s3 getsecret` command.**
### 設定環境變數 / Set Environment Variables
在非安全模式下,直接設定任意值即可:
In non-secure mode, you can directly set any values:
```bash
# 設定 AWS 存取憑證(使用文件內推薦的測試憑證)/ Set AWS access credentials (using recommended test credentials)
export AWS_ACCESS_KEY_ID=testuser/scm@EXAMPLE.COM
export AWS_SECRET_ACCESS_KEY=c261b6ecabf7d37d5f9ded654b1c724adac9bd9f13e247a235e567e8296d2999
# 或使用其他任意值 / Or use any other arbitrary values
export AWS_ACCESS_KEY_ID=ozone
export AWS_SECRET_ACCESS_KEY=ozone123
# 或使用更簡單的值 / Or use simpler values
export AWS_ACCESS_KEY_ID=test
export AWS_SECRET_ACCESS_KEY=test123
```
### 安全模式下的憑證取得(可選)/ Obtaining Credentials in Secure Mode (Optional)
如果您啟用了安全模式,則需要透過以下方式取得憑證:
If you have enabled secure mode, you need to obtain credentials through the following methods:
#### 方法 1: 使用命令行 / Method 1: Using Command Line
```bash
# 在 Ozone Manager 容器中執行 / Execute in Ozone Manager container
docker compose exec om ozone s3 getsecret
# 輸出示例 / Example output:
# awsAccessKey=testuser/scm@EXAMPLE.COM
# awsSecret=c261b6ecabf7d37d5f9ded654b1c724adac9bd9f13e247a235e567e8296d2999
```
#### 方法 2: 使用 REST API / Method 2: Using REST API
```bash
# 需要先進行 Kerberos 認證 / Requires Kerberos authentication first
curl -X PUT --negotiate -u : https://localhost:9879/secret
```
### 快速測試連線 / Quick Connection Test
我們提供了一個測試腳本來驗證 S3 Gateway 連線:
We provide a test script to verify S3 Gateway connection:
```bash
# 執行測試腳本 / Run test script
./test-s3-connection.sh
```
### 使用 AWS CLI 測試連線 / Test Connection with AWS CLI
如果您已安裝 AWS CLI,可以使用以下命令測試:
If you have AWS CLI installed, you can use the following commands to test:
```bash
# 設定環境變數 / Set environment variables
export AWS_ACCESS_KEY_ID=testuser/scm@EXAMPLE.COM
export AWS_SECRET_ACCESS_KEY=c261b6ecabf7d37d5f9ded654b1c724adac9bd9f13e247a235e567e8296d2999
# 建立一個測試桶 / Create a test bucket
aws s3api --endpoint http://localhost:9878 create-bucket --bucket test-bucket
# 列出所有桶 / List all buckets
aws s3api --endpoint http://localhost:9878 list-buckets
# 上傳檔案 / Upload a file
echo "Hello Ozone" > /tmp/test.txt
aws s3 --endpoint http://localhost:9878 cp /tmp/test.txt s3://test-bucket/test.txt
# 列出桶中的檔案 / List files in bucket
aws s3 --endpoint http://localhost:9878 ls s3://test-bucket/
```
**注意 / Note:** 如果沒有安裝 AWS CLI,可以直接跳到下一步使用 OpenDAL 進行連線。
**Note:** If AWS CLI is not installed, you can skip to the next step and use OpenDAL to connect.
---
## 使用 OpenDAL S3 介面 / Using OpenDAL S3 Interface
OpenDAL 是一個統一的資料存取層,支援多種後端儲存系統,包括 S3 相容的服務。
OpenDAL is a unified data access layer that supports multiple backend storage systems, including S3-compatible services.
### 快速測試連線 / Quick Connection Test
在開始使用 OpenDAL 之前,我們提供了一個測試腳本來驗證連線:
Before using OpenDAL, we provide a test script to verify the connection:
```bash
# 設定環境變數 / Set environment variables
export AWS_ACCESS_KEY_ID=testuser/scm@EXAMPLE.COM
export AWS_SECRET_ACCESS_KEY=c261b6ecabf7d37d5f9ded654b1c724adac9bd9f13e247a235e567e8296d2999
# 執行測試腳本 / Run test script
python3 test-opendal-ozone.py
```
**測試腳本功能 / Test Script Features:**
- ✅ 建立 OpenDAL S3 操作符 / Create OpenDAL S3 operator
- ✅ 檢查桶是否存在 / Check if bucket exists
- ✅ 測試寫入操作 / Test write operation
- ✅ 測試讀取操作 / Test read operation
- ✅ 測試列出檔案 / Test list operation
- ✅ 測試刪除操作 / Test delete operation
**重要提示 / Important Note:**
在執行測試腳本之前,請確保桶已建立。如果桶不存在,測試腳本會提供建立桶的說明。
Before running the test script, make sure the bucket is created. If the bucket doesn't exist, the test script will provide instructions on how to create it.
**建立桶的方法 / Methods to Create Bucket:**
```bash
# 方法 1: 使用 Ozone 命令 / Method 1: Using Ozone command
docker compose exec om bash -c "ozone sh bucket create /s3v/test-bucket"
# 方法 2: 使用 Ozone Manager REST API / Method 2: Using Ozone Manager REST API
curl -X PUT http://localhost:9874/api/v1/bucket/s3v/test-bucket
# 方法 3: 使用 AWS CLI (如果已安裝) / Method 3: Using AWS CLI (if installed)
aws s3api --endpoint http://localhost:9878 create-bucket --bucket test-bucket
```
如果測試成功,您將看到所有操作都通過的訊息。
If the test succeeds, you will see a message that all operations passed.
---
## OpenDAL 傳輸機制與檔案移動條件 / OpenDAL Transfer Mechanism and File Movement Conditions
### OpenDAL 與 Ozone 的綁定方式 / How OpenDAL Binds to Ozone
**重要概念 / Important Concept:**
OpenDAL 與 Ozone 的綁定是在**程式執行時動態設定**的,不是預先綁定的。OpenDAL 是一個程式庫(Library),需要在應用程式中匯入並在程式碼中設定連線資訊。
The binding between OpenDAL and Ozone is **dynamically configured at runtime in the program**, not pre-bound. OpenDAL is a library that needs to be imported in the application and configured in code.
#### 綁定過程 / Binding Process
```python
import opendal # 1. 匯入 OpenDAL 庫 / Import OpenDAL library
# 2. 在程式執行時建立操作符並設定連線 / Create operator and configure connection at runtime
op = opendal.Operator(
"s3", # 指定後端類型為 S3 / Specify backend type as S3
endpoint="http://localhost:9878", # Ozone S3 Gateway 端點 / Ozone S3 Gateway endpoint
bucket="test-bucket", # 桶名 / Bucket name
access_key_id="...", # 認證資訊 / Authentication info
secret_access_key="...", # 認證資訊 / Authentication info
region="us-east-1", # 區域 / Region
)
# 3. 此時才建立與 Ozone 的連線 / Connection to Ozone is established at this point
```
#### 綁定特點 / Binding Characteristics
- **動態綁定 / Dynamic Binding**: 每次執行程式時都會重新建立連線
- **設定驅動 / Configuration-Driven**: 所有連線資訊都在程式碼中設定
- **無需預先註冊 / No Pre-registration**: 不需要在 Ozone 或系統層面預先綁定
- **彈性設定 / Flexible Configuration**: 可以透過程式碼、環境變數、設定檔等方式設定
#### 多個連線範例 / Multiple Connections Example
```python
import opendal
# 可以建立多個操作符連線到不同的儲存 / Can create multiple operators for different storage
ozone_op = opendal.Operator("s3", endpoint="http://localhost:9878", bucket="bucket1", ...)
aws_op = opendal.Operator("s3", endpoint="https://s3.amazonaws.com", bucket="bucket2", ...)
# 每個操作符都是獨立的連線 / Each operator is an independent connection
ozone_op.write("file1.txt", b"data1")
aws_op.write("file2.txt", b"data2")
```
### OpenDAL 傳輸機制概述 / OpenDAL Transfer Mechanism Overview
OpenDAL 是一個統一的資料存取層,它透過標準化的 API 來處理不同後端儲存系統的資料傳輸。當使用 OpenDAL 與 Ozone S3 Gateway 進行檔案傳輸時,以下是其工作原理:
OpenDAL is a unified data access layer that handles data transfer for different backend storage systems through standardized APIs. When using OpenDAL with Ozone S3 Gateway for file transfer, here's how it works:
#### 1. 傳輸流程 / Transfer Process
```
本地檔案系統 / Local File System
↓ (讀取 / Read)
應用程式記憶體 / Application Memory
↓ (OpenDAL 操作 / OpenDAL Operation)
OpenDAL S3 操作符 / OpenDAL S3 Operator
↓ (HTTP/HTTPS 請求 / HTTP/HTTPS Request)
Ozone S3 Gateway (Port 9878)
↓ (S3 協定 / S3 Protocol)
Ozone 儲存層 / Ozone Storage Layer
```
#### 2. 檔案上傳流程 / File Upload Process
當您使用 `op.write()` 上傳檔案時,OpenDAL 執行以下步驟:
When you use `op.write()` to upload a file, OpenDAL performs the following steps:
1. **讀取本地檔案到記憶體 / Read Local File to Memory**
- 應用程式先將本地檔案讀取到記憶體中
- The application first reads the local file into memory
- 檔案內容以 `bytes` 或 `memoryview` 形式存在
- File content exists as `bytes` or `memoryview`
2. **建立 S3 API 請求 / Build S3 API Request**
- OpenDAL 根據設定建立 S3 PUT 請求
- OpenDAL builds S3 PUT request based on configuration
- 包含必要的認證資訊(AWS Signature Version 4)
- Includes necessary authentication information (AWS Signature Version 4)
- 設定正確的 HTTP 標頭(Content-Type, Content-Length 等)
- Sets correct HTTP headers (Content-Type, Content-Length, etc.)
3. **發送 HTTP 請求 / Send HTTP Request**
- 透過 HTTP/HTTPS 發送到 Ozone S3 Gateway
- Sent to Ozone S3 Gateway via HTTP/HTTPS
- 端點:`http://localhost:9878`
- Endpoint: `http://localhost:9878`
4. **Ozone 處理 / Ozone Processing**
- S3 Gateway 接收請求並驗證認證
- S3 Gateway receives request and validates authentication
- 將資料儲存到 Ozone 的儲存層
- Stores data to Ozone's storage layer
#### 3. 檔案下載流程 / File Download Process
當您使用 `op.read()` 下載檔案時,OpenDAL 執行以下步驟:
When you use `op.read()` to download a file, OpenDAL performs the following steps:
1. **建立 S3 GET 請求 / Build S3 GET Request**
- OpenDAL 建立 S3 GET 請求
- OpenDAL builds S3 GET request
- 包含認證和目標檔案路徑
- Includes authentication and target file path
2. **發送請求並接收資料 / Send Request and Receive Data**
- 發送 HTTP GET 請求到 Ozone S3 Gateway
- Sends HTTP GET request to Ozone S3 Gateway
- 接收回應資料流
- Receives response data stream
3. **返回資料 / Return Data**
- 將接收到的資料返回給應用程式
- Returns received data to application
- 資料格式可能是 `bytes` 或 `memoryview`
- Data format may be `bytes` or `memoryview`
### 檔案移動的條件與限制 / File Movement Conditions and Limitations
#### 1. 移動檔案的本質 / Nature of File Movement
**重要概念 / Important Concept:**
OpenDAL 和 S3 協定本身**不支援原子性的檔案移動操作**。在 S3 相容的儲存系統中,"移動"檔案實際上是兩個操作的組合:
OpenDAL and the S3 protocol itself **do not support atomic file move operations**. In S3-compatible storage systems, "moving" a file is actually a combination of two operations:
1. **複製檔案 / Copy File**: 從來源路徑複製到目標路徑
2. **刪除來源檔案 / Delete Source File**: 刪除原始檔案
#### 2. 移動檔案的條件 / Conditions for Moving Files
要成功移動檔案,必須滿足以下條件:
To successfully move a file, the following conditions must be met:
##### 條件 1: 來源檔案必須存在 / Condition 1: Source File Must Exist
```python
# 檢查來源檔案是否存在 / Check if source file exists
try:
source_data = op.read("source-file.txt")
# 來源檔案存在,可以繼續移動 / Source file exists, can proceed with move
except Exception as e:
# 來源檔案不存在,無法移動 / Source file does not exist, cannot move
print(f"錯誤:來源檔案不存在 / Error: Source file does not exist")
```
##### 條件 2: 目標路徑不應已存在 / Condition 2: Target Path Should Not Exist
雖然 S3 允許覆蓋,但為了避免意外資料遺失,建議先檢查:
Although S3 allows overwriting, to avoid accidental data loss, it's recommended to check first:
```python
# 檢查目標檔案是否存在 / Check if target file exists
try:
target_data = op.read("target-file.txt")
# 目標檔案已存在,需要決定是否覆蓋 / Target file exists, need to decide whether to overwrite
print("警告:目標檔案已存在 / Warning: Target file already exists")
except:
# 目標檔案不存在,可以安全移動 / Target file does not exist, safe to move
pass
```
##### 條件 3: 足夠的儲存空間 / Condition 3: Sufficient Storage Space
移動操作需要臨時儲存空間(在複製階段):
Move operation requires temporary storage space (during copy phase):
- 來源檔案和目標檔案會同時存在(在刪除來源檔案之前)
- Source and target files will exist simultaneously (before deleting source file)
- 確保有足夠的儲存空間容納兩個檔案
- Ensure sufficient storage space to accommodate both files
##### 條件 4: 正確的認證和權限 / Condition 4: Correct Authentication and Permissions
- 必須有讀取來源檔案的權限
- Must have permission to read source file
- 必須有寫入目標路徑的權限
- Must have permission to write to target path
- 必須有刪除來源檔案的權限
- Must have permission to delete source file
#### 3. 實作檔案移動的完整範例 / Complete Example of File Movement
```python
import opendal
def move_file_in_ozone(op, source_key, target_key, overwrite=False):
"""
在 Ozone 中移動文件 / Move file in Ozone
參數 / Parameters:
- op: OpenDAL 操作符 / OpenDAL operator
- source_key: 來源檔案路徑 / Source file path
- target_key: 目標檔案路徑 / Target file path
- overwrite: 是否覆蓋已存在的目標檔案 / Whether to overwrite existing target file
返回 / Returns:
- True: 移動成功 / Move successful
- False: 移動失敗 / Move failed
"""
# 條件檢查 1: 檢查來源檔案是否存在 / Condition 1: Check if source file exists
try:
source_data = op.read(source_key)
# 確保是 bytes 類型 / Ensure it's bytes type
if isinstance(source_data, memoryview):
source_data = bytes(source_data)
elif not isinstance(source_data, bytes):
source_data = bytes(source_data)
except Exception as e:
print(f"❌ 錯誤:無法讀取來源檔案 / Error: Cannot read source file: {source_key}")
print(f" 原因 / Reason: {e}")
return False
# 條件檢查 2: 檢查目標檔案是否存在 / Condition 2: Check if target file exists
if not overwrite:
try:
op.read(target_key)
print(f"⚠️ 警告:目標檔案已存在 / Warning: Target file already exists: {target_key}")
print(f" 使用 overwrite=True 來覆蓋 / Use overwrite=True to overwrite")
return False
except:
# 目標檔案不存在,可以繼續 / Target file does not exist, can proceed
pass
# 步驟 1: 複製檔案到目標位置 / Step 1: Copy file to target location
try:
op.write(target_key, source_data)
print(f"✅ 檔案已複製到目標位置 / File copied to target: {target_key}")
except Exception as e:
print(f"❌ 錯誤:無法寫入目標檔案 / Error: Cannot write target file: {target_key}")
print(f" 原因 / Reason: {e}")
return False
# 步驟 2: 刪除來源檔案 / Step 2: Delete source file
try:
op.delete(source_key)
print(f"✅ 來源檔案已刪除 / Source file deleted: {source_key}")
except Exception as e:
print(f"⚠️ 警告:無法刪除來源檔案 / Warning: Cannot delete source file: {source_key}")
print(f" 原因 / Reason: {e}")
print(f" 目標檔案已建立,但來源檔案仍存在 / Target file created, but source file still exists")
# 可以選擇回滾(刪除目標檔案)/ Can choose to rollback (delete target file)
return False
print(f"✅ 檔案移動成功!/ File move successful!")
print(f" 從 / From: {source_key}")
print(f" 到 / To: {target_key}")
return True
# 使用範例 / Usage Example
op = opendal.Operator(
"s3",
endpoint="http://localhost:9878",
bucket="test-bucket",
access_key_id="testuser/scm@EXAMPLE.COM",
secret_access_key="c261b6ecabf7d37d5f9ded654b1c724adac9bd9f13e247a235e567e8296d2999",
region="us-east-1",
)
# 移動檔案 / Move file
move_file_in_ozone(op, "old-file.txt", "new-file.txt", overwrite=False)
```
#### 4. 傳輸過程中的技術細節 / Technical Details During Transfer
##### 記憶體使用 / Memory Usage
- **小檔案 (< 幾 MB)**: 整個檔案會載入到記憶體中
- **Small files (< few MB)**: Entire file is loaded into memory
- **大檔案**: OpenDAL 可能會使用串流傳輸或分塊上傳
- **Large files**: OpenDAL may use streaming or chunked upload
##### 錯誤處理 / Error Handling
在傳輸過程中可能遇到的錯誤:
Errors that may occur during transfer:
1. **網路錯誤 / Network Errors**
- 連線逾時 / Connection timeout
- 網路中斷 / Network interruption
- 解決方案:實作重試機制 / Solution: Implement retry mechanism
2. **認證錯誤 / Authentication Errors**
- 無效的 access key 或 secret key
- Invalid access key or secret key
- 解決方案:檢查環境變數和設定 / Solution: Check environment variables and configuration
3. **儲存錯誤 / Storage Errors**
- 儲存空間不足 / Insufficient storage space
- 權限不足 / Insufficient permissions
- 解決方案:檢查 Ozone 叢集狀態和權限 / Solution: Check Ozone cluster status and permissions
##### 效能考量 / Performance Considerations
1. **並行上傳 / Concurrent Uploads**
- OpenDAL 支援並行操作
- OpenDAL supports concurrent operations
- 可以同時上傳多個檔案以提高效率
- Can upload multiple files simultaneously for better efficiency
2. **批次操作 / Batch Operations**
- 對於多個檔案,考慮使用批次操作
- For multiple files, consider using batch operations
- 減少網路往返次數
- Reduce network round trips
3. **緩衝區大小 / Buffer Size**
- 可以根據檔案大小調整緩衝區
- Can adjust buffer size based on file size
- 大檔案可能需要更大的緩衝區
- Large files may require larger buffers
#### 5. 最佳實務 / Best Practices
1. **始終驗證傳輸結果 / Always Verify Transfer Results**
```python
# 上傳後驗證 / Verify after upload
uploaded_data = op.read("file.txt")
if uploaded_data == original_data:
print("✅ 檔案驗證成功 / File verification successful")
```
2. **實作錯誤重試機制 / Implement Error Retry Mechanism**
```python
import time
def upload_with_retry(op, key, data, max_retries=3):
for attempt in range(max_retries):
try:
op.write(key, data)
return True
except Exception as e:
if attempt < max_retries - 1:
time.sleep(2 ** attempt) # 指數退避 / Exponential backoff
continue
else:
raise e
```
3. **使用交易性操作(如果可能)/ Use Transactional Operations (If Possible)**
- 對於關鍵操作,考慮先複製,驗證成功後再刪除來源檔案
- For critical operations, consider copying first, then delete source file after verification
4. **監控傳輸進度 / Monitor Transfer Progress**
- 對於大檔案,可以實作進度回呼
- For large files, can implement progress callbacks
- 記錄傳輸日誌以便除錯
- Log transfer logs for debugging
#### 6. 測試檔案移動 / Test File Movement
我們提供了一個測試腳本來示範檔案移動操作:
We provide a test script to demonstrate file movement operations:
```bash
# 設定環境變數 / Set environment variables
export AWS_ACCESS_KEY_ID=testuser/scm@EXAMPLE.COM
export AWS_SECRET_ACCESS_KEY=c261b6ecabf7d37d5f9ded654b1c724adac9bd9f13e247a235e567e8296d2999
# 執行檔案移動測試 / Run file movement test
python3 test-move-file.py
```
**測試腳本功能 / Test Script Features:**
- ✅ 建立測試檔案 / Create test file
- ✅ 檢查來源檔案是否存在 / Check if source file exists
- ✅ 檢查目標檔案是否存在 / Check if target file exists
- ✅ 複製檔案到目標位置 / Copy file to target location
- ✅ 驗證目標檔案內容 / Verify target file content
- ✅ 刪除來源檔案 / Delete source file
- ✅ 列出桶中的所有檔案 / List all files in bucket
**範例輸出 / Example Output:**
```
=== 移動檔案 / Moving File ===
來源檔案 / Source: file-to-move.txt
目標檔案 / Target: moved-file.txt
1. 檢查來源檔案是否存在...
✅ 來源檔案存在,大小: 65 bytes
2. 檢查目標檔案是否存在...
✅ 目標檔案不存在,可以繼續
3. 複製檔案到目標位置...
✅ 檔案已複製到目標位置
4. 驗證目標檔案...
✅ 目標檔案驗證成功,內容匹配
5. 刪除來源檔案...
✅ 來源檔案已刪除
=== ✅ 檔案移動成功!===
```
---
## 上傳本地檔案到 Ozone / Upload Local Files to Ozone
### 使用測試腳本上傳 / Upload Using Test Script
我們提供了一個專門的測試腳本 `test-upload-local-file.py` 來將本地檔案上傳到 Ozone:
We provide a dedicated test script `test-upload-local-file.py` to upload local files to Ozone:
```bash
# 設定環境變數 / Set environment variables
export AWS_ACCESS_KEY_ID=testuser/scm@EXAMPLE.COM
export AWS_SECRET_ACCESS_KEY=c261b6ecabf7d37d5f9ded654b1c724adac9bd9f13e247a235e567e8296d2999
# 上傳本地檔案 / Upload local file
# 語法 / Syntax: python3 test-upload-local-file.py <本地檔案路徑> [S3鍵名]
# Syntax: python3 test-upload-local-file.py <local-file-path> [s3-key-name]
python3 test-upload-local-file.py /path/to/your/file.txt my-file-in-ozone.txt
# 如果不指定 S3 key,會使用原檔案名稱 / If S3 key is not specified, original filename will be used
python3 test-upload-local-file.py /path/to/your/file.txt
# 如果不提供任何參數,會建立一個測試檔案並上傳 / If no arguments provided, creates a test file and uploads it
python3 test-upload-local-file.py
```
**測試腳本功能 / Test Script Features:**
- ✅ 讀取本地檔案 / Read local file
- ✅ 上傳到 Ozone S3 Gateway / Upload to Ozone S3 Gateway
- ✅ 驗證上傳的檔案內容 / Verify uploaded file content
- ✅ 列出桶中的所有檔案 / List all files in bucket
- ✅ 顯示詳細的上傳資訊 / Display detailed upload information
**範例輸出 / Example Output:**
```
=== 測試將本地檔案上傳到 Ozone ===
本地檔案 / Local file: /tmp/my-file.txt
檔案大小 / File size: 154 bytes
S3 Key (目標路徑) / S3 Key (target path): my-file.txt
1. 建立 OpenDAL S3 操作符...
✅ 操作符建立成功
2. 讀取本地檔案...
✅ 檔案讀取成功,大小: 154 bytes
3. 上傳檔案到 Ozone...
✅ 檔案上傳成功!
4. 驗證上傳的檔案...
✅ 檔案驗證成功!內容完全匹配
5. 列出桶中的檔案...
✅ 找到 1 個檔案
- my-file.txt
=== ✅ 測試完成!===
檔案已成功上傳到 Ozone S3 Gateway:
s3://test-bucket/my-file.txt
```
### 使用 Python 程式碼上傳 / Upload Using Python Code
```python
import opendal
# 建立操作符 / Create operator
op = opendal.Operator(
"s3",
endpoint="http://localhost:9878",
bucket="test-bucket",
access_key_id="testuser/scm@EXAMPLE.COM",
secret_access_key="c261b6ecabf7d37d5f9ded654b1c724adac9bd9f13e247a235e567e8296d2999",
region="us-east-1",
)
# 讀取本地檔案 / Read local file
local_file_path = "/path/to/your/local-file.txt"
with open(local_file_path, "rb") as f:
file_content = f.read()
# 上傳到 Ozone / Upload to Ozone
s3_key = "my-file-in-ozone.txt"
op.write(s3_key, file_content)
print(f"檔案已上傳到 s3://test-bucket/{s3_key}")
print(f"File uploaded to s3://test-bucket/{s3_key}")
```
---
### Rust 範例 / Rust Example
```rust
use opendal::services::S3;
use opendal::Operator;
use opendal::Result;
#[tokio::main]
async fn main() -> Result<()> {
// 建立 S3 服務建置器 / Create S3 service builder
let mut builder = S3::default();
// 設定 Ozone S3 Gateway 端點 / Configure Ozone S3 Gateway endpoint
builder.endpoint("http://localhost:9878");
builder.bucket("test-bucket");
builder.access_key_id("testuser/scm@EXAMPLE.COM");
builder.secret_access_key("c261b6ecabf7d37d5f9ded654b1c724adac9bd9f13e247a235e567e8296d2999");
builder.region("us-east-1"); // Ozone 可以使用任意 region
// 建置操作符 / Build operator
let op: Operator = Operator::new(builder)?.finish();
// 寫入資料 / Write data
op.write("test.txt", "Hello from OpenDAL!").await?;
// 讀取資料 / Read data
let data = op.read("test.txt").await?;
println!("Content: {}", String::from_utf8_lossy(&data));
// 列出檔案 / List files
let entries = op.list("/").await?;
for entry in entries {
println!("File: {}", entry.path());
}
Ok(())
}
```
### Python 範例 / Python Example
```python
import opendal
# 建立 S3 操作符 / Create S3 operator
op = opendal.Operator(
"s3",
endpoint="http://localhost:9878",
bucket="test-bucket",
access_key_id="testuser/scm@EXAMPLE.COM",
secret_access_key="c261b6ecabf7d37d5f9ded654b1c724adac9bd9f13e247a235e567e8296d2999",
region="us-east-1", # Ozone 可以使用任意 region
)
# 寫入資料 / Write data
op.write("test.txt", b"Hello from OpenDAL!")
# 讀取資料 / Read data
data = op.read("test.txt")
print(f"Content: {data.decode('utf-8')}")
# 列出檔案 / List files
entries = op.list("/")
for entry in entries:
print(f"File: {entry.path()}")
```
### Node.js 範例 / Node.js Example
```javascript
const { Operator } = require("opendal");
async function main() {
// 建立 S3 操作符 / Create S3 operator
const op = new Operator("s3", {
endpoint: "http://localhost:9878",
bucket: "test-bucket",
access_key_id: "testuser/scm@EXAMPLE.COM",
secret_access_key: "c261b6ecabf7d37d5f9ded654b1c724adac9bd9f13e247a235e567e8296d2999",
region: "us-east-1", // Ozone 可以使用任意 region
});
// 寫入資料 / Write data
await op.write("test.txt", Buffer.from("Hello from OpenDAL!"));
// 讀取資料 / Read data
const data = await op.read("test.txt");
console.log(`Content: ${data.toString()}`);
// 列出檔案 / List files
const entries = await op.list("/");
for (const entry of entries) {
console.log(`File: ${entry.path()}`);
}
}
main().catch(console.error);
```
### Java 範例 / Java Example
```java
import io.opendal.BlockingOperator;
import io.opendal.Options;
public class OzoneExample {
public static void main(String[] args) {
// 建立選項 / Create options
Options options = new Options();
options.set("endpoint", "http://localhost:9878");
options.set("bucket", "test-bucket");
options.set("access_key_id", "testuser/scm@EXAMPLE.COM");
options.set("secret_access_key", "c261b6ecabf7d37d5f9ded654b1c724adac9bd9f13e247a235e567e8296d2999");
options.set("region", "us-east-1"); // Ozone 可以使用任意 region
// 建立操作符 / Create operator
BlockingOperator op = new BlockingOperator("s3", options);
// 寫入資料 / Write data
op.write("test.txt", "Hello from OpenDAL!".getBytes());
// 讀取資料 / Read data
byte[] data = op.read("test.txt");
System.out.println("Content: " + new String(data));
// 列出檔案 / List files
op.list("/").forEach(entry -> {
System.out.println("File: " + entry.path());
});
}
}
```
---
## 常見操作範例 / Common Operations Examples
### 建立桶 / Create Bucket
**使用 AWS CLI / Using AWS CLI:**
```bash
aws s3api --endpoint http://localhost:9878 create-bucket --bucket my-bucket
```
**使用 OpenDAL (Rust) / Using OpenDAL (Rust):**
```rust
// OpenDAL 會自動處理桶的建立(如果不存在)/ OpenDAL automatically handles bucket creation (if not exists)
// 只需確保在設定中指定了正確的桶名 / Just ensure the correct bucket name is specified in config
```
### 上傳檔案 / Upload File
**使用測試腳本(推薦)/ Using Test Script (Recommended):**
我們提供了一個專門的測試腳本來上傳本地檔案:
We provide a dedicated test script to upload local files:
```bash
# 設定環境變數 / Set environment variables
export AWS_ACCESS_KEY_ID=testuser/scm@EXAMPLE.COM
export AWS_SECRET_ACCESS_KEY=c261b6ecabf7d37d5f9ded654b1c724adac9bd9f13e247a235e567e8296d2999
# 上傳本地檔案 / Upload local file
python3 test-upload-local-file.py /path/to/your/local-file.txt s3-key-name.txt
# 如果不指定 S3 key,會使用檔案名稱 / If S3 key is not specified, filename will be used
python3 test-upload-local-file.py /path/to/your/local-file.txt
```
**使用 AWS CLI / Using AWS CLI:**
```bash
aws s3 --endpoint http://localhost:9878 cp /path/to/local/file.txt s3://my-bucket/file.txt
```
**使用 OpenDAL (Python) / Using OpenDAL (Python):**
```python
import opendal
# 建立操作符 / Create operator
op = opendal.Operator(
"s3",
endpoint="http://localhost:9878",
bucket="test-bucket",
access_key_id="testuser/scm@EXAMPLE.COM",
secret_access_key="c261b6ecabf7d37d5f9ded654b1c724adac9bd9f13e247a235e567e8296d2999",
region="us-east-1",
)
# 讀取本地檔案並上傳 / Read local file and upload
with open("/path/to/local/file.txt", "rb") as f:
file_content = f.read()
op.write("file.txt", file_content)
print("檔案上傳成功!/ File uploaded successfully!")
```
**使用 OpenDAL (Rust) / Using OpenDAL (Rust):**
```rust
let content = std::fs::read("/path/to/local/file.txt")?;
op.write("file.txt", content).await?;
```
### 下載檔案 / Download File
**使用 AWS CLI / Using AWS CLI:**
```bash
aws s3 --endpoint http://localhost:9878 cp s3://my-bucket/file.txt /path/to/local/file.txt
```
**使用 OpenDAL (Python) / Using OpenDAL (Python):**
```python
# 從 Ozone 下載檔案到本地 / Download file from Ozone to local
data = op.read("file.txt")
# 確保內容是 bytes 類型 / Ensure content is bytes type
if isinstance(data, memoryview):
data = bytes(data)
elif not isinstance(data, bytes):
data = bytes(data)
# 寫入本地檔案 / Write to local file
with open("/path/to/local/file.txt", "wb") as f:
f.write(data)
print("檔案下載成功!/ File downloaded successfully!")
```
**使用 OpenDAL (Rust) / Using OpenDAL (Rust):**
```rust
let data = op.read("file.txt").await?;
std::fs::write("/path/to/local/file.txt", data)?;
```
### 列出檔案 / List Files
**使用 AWS CLI / Using AWS CLI:**
```bash
aws s3 --endpoint http://localhost:9878 ls s3://my-bucket/
```
**使用 OpenDAL (Rust) / Using OpenDAL (Rust):**
```rust
let entries = op.list("/").await?;
for entry in entries {
println!("{}", entry.path());
}
```
### 刪除檔案 / Delete File
**使用 AWS CLI / Using AWS CLI:**
```bash
aws s3 --endpoint http://localhost:9878 rm s3://my-bucket/file.txt
```
**使用 OpenDAL (Rust) / Using OpenDAL (Rust):**
```rust
op.delete("file.txt").await?;
```
---
## 故障排除 / Troubleshooting
### 問題 1: 無法連線到 S3 Gateway / Issue 1: Cannot Connect to S3 Gateway
**症狀 / Symptoms:**
```
Connection refused
```
**解決方案 / Solution:**
1. 檢查 S3 Gateway 是否正在執行 / Check if S3 Gateway is running:
```bash
docker compose ps s3g
```
2. 檢查日誌 / Check logs:
```bash
docker compose logs s3g
```
3. 確認端口映射 / Verify port mapping:
```bash
docker compose port s3g 9878
```
### 問題 2: 認證失敗 / Issue 2: Authentication Failed
**症狀 / Symptoms:**
```
Access Denied
```
**解決方案 / Solution:**
1. 確認存取憑證正確設定 / Verify access credentials are correctly set:
```bash
echo $AWS_ACCESS_KEY_ID
echo $AWS_SECRET_ACCESS_KEY
```
2. 檢查 Ozone 日誌 / Check Ozone logs:
```bash
docker compose logs om | grep -i auth
```
### 問題 3: 桶不存在 / Issue 3: Bucket Not Found
**症狀 / Symptoms:**
```
NoSuchBucket
```
**解決方案 / Solution:**
1. 先建立桶 / Create bucket first:
```bash
aws s3api --endpoint http://localhost:9878 create-bucket --bucket your-bucket-name
```
2. 確認桶名正確 / Verify bucket name is correct:
```bash
aws s3api --endpoint http://localhost:9878 list-buckets
```
### 問題 4: 服務啟動失敗 / Issue 4: Service Startup Failed
**症狀 / Symptoms:**
```
Container exits immediately
```
**解決方案 / Solution:**
1. 檢查完整日誌 / Check full logs:
```bash
docker compose logs --tail=100
```
2. 檢查磁盤空間 / Check disk space:
```bash
df -h
```
3. 重新啟動服務 / Restart services:
```bash
docker compose down -v
docker compose up -d
```
### 問題 5: OpenDAL 連線問題 / Issue 5: OpenDAL Connection Issues
**症狀 / Symptoms:**
```
Connection timeout or endpoint not found
```
**解決方案 / Solution:**
1. 確認端點 URL 正確 / Verify endpoint URL is correct:
- 應該使用 `http://localhost:9878`(本地)或 `http://s3g:9878`(容器內)
- Should use `http://localhost:9878` (local) or `http://s3g:9878` (inside container)
2. 檢查網路連線 / Check network connectivity:
```bash
curl http://localhost:9878
```
3. 確認 OpenDAL 版本支援 S3 服務 / Verify OpenDAL version supports S3 service:
```bash
# Rust
cargo tree | grep opendal
# Python
pip show opendal
python3 -c "import opendal; print(opendal.__version__)"
```
4. 確認 OpenDAL 已正確安裝 / Verify OpenDAL is correctly installed:
```bash
# Python
python3 -c "import opendal; print('OpenDAL installed successfully')"
# 如果出現 ImportError,重新安裝 / If ImportError occurs, reinstall:
pip3 install --upgrade opendal
```
5. 檢查桶是否存在 / Check if bucket exists:
- OpenDAL 可能會自動建立桶,但如果遇到權限問題,請先手動建立桶
- OpenDAL may auto-create buckets, but if you encounter permission issues, create the bucket manually first
---
## 停止和清理 / Stopping and Cleanup
### 停止叢集 / Stop Cluster
```bash
# 停止所有服務(保留資料)/ Stop all services (preserve data)
docker compose stop
# 停止並刪除容器(保留資料卷)/ Stop and remove containers (preserve volumes)
docker compose down
# 停止並刪除所有內容(包括資料卷)/ Stop and remove everything (including volumes)
docker compose down -v
```
### 清理資料 / Clean Data
```bash
# 刪除所有資料卷 / Remove all volumes
docker compose down -v
# 刪除特定服務的資料 / Remove specific service data
docker volume ls | grep ozone
docker volume rm <volume-name>
```
---
## 進階配置 / Advanced Configuration
### 自定義配置 / Custom Configuration
您可以透過修改 `docker-config` 檔案來自訂 Ozone 設定:
You can customize Ozone configuration by modifying the `docker-config` file:
```bash
cd hadoop-ozone/dist/src/main/compose/ozone/
vim docker-config
```
### 啟用監控 / Enable Monitoring
```bash
# 設定環境變數 / Set environment variable
export COMPOSE_FILE=docker-compose.yaml:monitoring.yaml
# 啟動叢集 / Start cluster
docker compose up -d
```
### 調整複製因子 / Adjust Replication Factor
```bash
# 設定複製因子 / Set replication factor
export OZONE_REPLICATION_FACTOR=3
# 啟動叢集 / Start cluster
docker compose up -d --scale datanode=3
```
---
## 參考資源 / Reference Resources
- **Apache Ozone 官方文檔 / Apache Ozone Official Documentation**: https://ozone.apache.org/
- **OpenDAL 官方文檔 / OpenDAL Official Documentation**: https://opendal.apache.org/
- **Docker Compose 文檔 / Docker Compose Documentation**: https://docs.docker.com/compose/
- **Ozone S3 介面文檔 / Ozone S3 Interface Documentation**: https://ozone.apache.org/docs/edge/interface/s3.html
---
## 總結 / Summary
本指南涵蓋了:
This guide covers:
1. ✅ 安裝 OpenDAL 庫 / Installing OpenDAL library
2. ✅ 使用 Docker Compose 啟動 Ozone 叢集 / Starting Ozone cluster using Docker Compose
3. ✅ 設定 S3 存取憑證 / Configuring S3 access credentials
4. ✅ 使用 OpenDAL 連線 Ozone S3 Gateway / Connecting to Ozone S3 Gateway using OpenDAL
5. ✅ 測試連線腳本 / Test connection scripts
6. ✅ 常見操作範例 / Common operation examples
7. ✅ 故障排除指南 / Troubleshooting guide
現在您已經可以開始使用 Ozone 和 OpenDAL 進行開發了!
Now you're ready to start developing with Ozone and OpenDAL!
---
## 完整測試腳本程式碼 / Complete Test Scripts Code
本節包含所有測試腳本的完整程式碼,您可以直接複製使用。
This section contains the complete code for all test scripts that you can copy and use directly.
### 1. test-s3-connection.sh - S3 Gateway 連線測試腳本
這個腳本用於測試 Ozone S3 Gateway 的基本連線。
This script is used to test basic connection to Ozone S3 Gateway.
```bash
#!/bin/bash
# 測試 Ozone S3 Gateway 連接的簡單腳本
# Simple script to test Ozone S3 Gateway connection
set -e
echo "=== 測試 Ozone S3 Gateway 連接 / Testing Ozone S3 Gateway Connection ==="
echo ""
# 設置憑證(非安全模式可以使用任意值)
export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-testuser/scm@EXAMPLE.COM}
export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-c261b6ecabf7d37d5f9ded654b1c724adac9bd9f13e247a235e567e8296d2999}
ENDPOINT="http://localhost:9878"
echo "使用端點 / Using endpoint: $ENDPOINT"
echo "使用 Access Key / Using Access Key: $AWS_ACCESS_KEY_ID"
echo ""
# 檢查 S3 Gateway 是否運行
echo "1. 檢查 S3 Gateway 是否運行 / Checking if S3 Gateway is running..."
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$ENDPOINT" || echo "000")
if [ "$HTTP_CODE" = "403" ] || [ "$HTTP_CODE" = "400" ]; then
echo " ✅ S3 Gateway 正在運行 (HTTP $HTTP_CODE) / S3 Gateway is running (HTTP $HTTP_CODE)"
else
echo " ⚠️ S3 Gateway 響應異常 (HTTP $HTTP_CODE) / S3 Gateway response abnormal (HTTP $HTTP_CODE)"
echo " 請確認服務是否正常啟動 / Please verify if the service started correctly"
fi
echo ""
# 檢查是否安裝了 AWS CLI
if command -v aws &> /dev/null; then
echo "2. 使用 AWS CLI 測試連接 / Testing connection with AWS CLI..."
echo ""
# 列出桶
echo " 列出所有桶 / Listing all buckets..."
aws s3api --endpoint "$ENDPOINT" list-buckets 2>&1 || echo " 無法列出桶 / Cannot list buckets"
echo ""
# 建立測試桶
TEST_BUCKET="test-bucket-$(date +%s)"
echo " 建立測試桶: $TEST_BUCKET / Creating test bucket: $TEST_BUCKET"
if aws s3api --endpoint "$ENDPOINT" create-bucket --bucket "$TEST_BUCKET" 2>&1; then
echo " ✅ 桶建立成功 / Bucket created successfully"
# 上傳測試檔案
TEST_FILE="/tmp/test-ozone-$(date +%s).txt"
echo "Hello from Ozone S3 Gateway!" > "$TEST_FILE"
echo " 上傳測試檔案 / Uploading test file..."
if aws s3 --endpoint "$ENDPOINT" cp "$TEST_FILE" "s3://$TEST_BUCKET/test.txt" 2>&1; then
echo " ✅ 檔案上傳成功 / File uploaded successfully"
# 列出桶中的檔案
echo " 列出桶中的檔案 / Listing files in bucket..."
aws s3 --endpoint "$ENDPOINT" ls "s3://$TEST_BUCKET/" 2>&1 || true
# 清理
echo " 清理測試檔案 / Cleaning up test file..."
rm -f "$TEST_FILE"
fi
else
echo " ⚠️ 桶建立失敗 / Bucket creation failed"
fi
echo ""
else
echo "2. AWS CLI 未安裝 / AWS CLI not installed"
echo " 請安裝 AWS CLI 或使用 OpenDAL 進行測試 / Please install AWS CLI or use OpenDAL for testing"
echo ""
fi
echo "=== 測試完成 / Test Complete ==="
echo ""
echo "下一步 / Next Steps:"
echo "1. 設置環境變數 / Set environment variables:"
echo " export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID"
echo " export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY"
echo ""
echo "2. 使用 OpenDAL 連接 Ozone S3 Gateway / Use OpenDAL to connect to Ozone S3 Gateway"
echo " 請參考文件中的 OpenDAL 範例 / Please refer to OpenDAL examples in the documentation"
```
**使用方法 / Usage:**
```bash
chmod +x test-s3-connection.sh
./test-s3-connection.sh
```
---
### 2. test-opendal-ozone.py - OpenDAL 連線測試腳本
這個腳本用於測試 OpenDAL 與 Ozone S3 Gateway 的連線和基本操作。
This script is used to test OpenDAL connection to Ozone S3 Gateway and basic operations.
```python
#!/usr/bin/env python3
"""
測試 OpenDAL 連接 Ozone S3 Gateway
Test OpenDAL connection to Ozone S3 Gateway
"""
import os
import sys
# 設置環境變數(如果尚未設置)
os.environ.setdefault('AWS_ACCESS_KEY_ID', 'testuser/scm@EXAMPLE.COM')
os.environ.setdefault('AWS_SECRET_ACCESS_KEY', 'c261b6ecabf7d37d5f9ded654b1c724adac9bd9f13e247a235e567e8296d2999')
try:
import opendal
print("✅ OpenDAL 已安裝 / OpenDAL is installed")
print(f" 版本 / Version: {opendal.__version__ if hasattr(opendal, '__version__') else 'unknown'}\n")
except ImportError:
print("❌ OpenDAL 未安裝 / OpenDAL is not installed")
print("\n請先安裝 OpenDAL / Please install OpenDAL first:")
print(" pip3 install opendal")
sys.exit(1)
def test_opendal_connection():
"""測試 OpenDAL 連接 Ozone S3 Gateway"""
print("=== 測試 OpenDAL 連接 Ozone S3 Gateway ===")
print("=== Testing OpenDAL Connection to Ozone S3 Gateway ===\n")
# Ozone S3 Gateway 設定
endpoint = "http://localhost:9878"
bucket = "test-bucket"
access_key_id = os.environ.get('AWS_ACCESS_KEY_ID')
secret_access_key = os.environ.get('AWS_SECRET_ACCESS_KEY')
print(f"端點 / Endpoint: {endpoint}")
print(f"桶名 / Bucket: {bucket}")
print(f"Access Key ID: {access_key_id}\n")
try:
# 建立 S3 操作符
print("1. 建立 OpenDAL S3 操作符 / Creating OpenDAL S3 operator...")
op = opendal.Operator(
"s3",
endpoint=endpoint,
bucket=bucket,
access_key_id=access_key_id,
secret_access_key=secret_access_key,
region="us-east-1", # Ozone 可以使用任意 region
)
print(" ✅ 操作符建立成功 / Operator created successfully\n")
# 檢查桶是否存在,如果不存在則建立
print("2. 檢查桶是否存在 / Checking if bucket exists...")
try:
# 嘗試列出桶中的檔案來檢查桶是否存在
list(op.list("/"))
print(" ✅ 桶已存在 / Bucket exists\n")
except Exception as e:
print(f" ⚠️ 桶不存在,需要先建立桶 / Bucket does not exist, need to create bucket first")
print(f" 錯誤訊息 / Error message: {str(e)[:100]}")
print(f"\n 請使用以下方法之一建立桶 / Please use one of the following methods to create bucket:")
print(f" 1. 使用 Docker 容器內的命令 / Use command inside Docker container:")
print(f" docker compose exec om bash -c 'ozone sh bucket create /s3v/{bucket}'")
print(f" 2. 使用 Ozone Manager REST API / Use Ozone Manager REST API:")
print(f" curl -X PUT http://localhost:9874/api/v1/bucket/s3v/{bucket}")
print(f" 3. 安裝 AWS CLI 後使用 / After installing AWS CLI:")
print(f" aws s3api --endpoint {endpoint} create-bucket --bucket {bucket}\n")
print(f" 建立桶後,請重新執行此測試腳本 / After creating bucket, please run this test script again.\n")
return False
# 測試寫入
print("3. 測試寫入資料 / Testing write operation...")
test_content = b"Hello from OpenDAL! This is a test file."
test_key = "test-file.txt"
op.write(test_key, test_content)
print(f" ✅ 檔案寫入成功: {test_key} / File written successfully: {test_key}\n")
# 測試讀取
print("4. 測試讀取資料 / Testing read operation...")
read_content = op.read(test_key)
# 確保內容是 bytes 類型
if isinstance(read_content, memoryview):
read_content = bytes(read_content)
elif not isinstance(read_content, bytes):
read_content = bytes(read_content)
print(f" ✅ 檔案讀取成功 / File read successfully")
print(f" 內容 / Content: {read_content.decode('utf-8')}\n")
# 測試列出文件
print("5. 測試列出檔案 / Testing list operation...")
entries = op.list("/")
file_list = list(entries)
print(f" ✅ 找到 {len(file_list)} 個檔案 / Found {len(file_list)} file(s)")
for entry in file_list:
# 取得路徑,可能是 path 屬性或 path() 方法
try:
path = entry.path() if callable(entry.path) else entry.path
except:
path = str(entry)
print(f" - {path}")
print()
# 測試刪除
print("6. 測試刪除檔案 / Testing delete operation...")
op.delete(test_key)
print(f" ✅ 檔案刪除成功: {test_key} / File deleted successfully: {test_key}\n")
print("=== ✅ 所有測試通過!/ All tests passed! ===")
return True
except Exception as e:
print(f"\n❌ 錯誤 / Error: {type(e).__name__}")
print(f" 訊息 / Message: {str(e)}\n")
# 提供故障排除建議
print("故障排除建議 / Troubleshooting suggestions:")
print("1. 確認 Ozone S3 Gateway 正在執行 / Verify Ozone S3 Gateway is running:")
print(" curl http://localhost:9878")
print("2. 確認環境變數已設定 / Verify environment variables are set:")
print(f" AWS_ACCESS_KEY_ID={access_key_id}")
print(f" AWS_SECRET_ACCESS_KEY={'*' * 20}")
print("3. 確認桶是否存在(如果不存在,OpenDAL 可能會自動建立)/")
print(" Verify bucket exists (OpenDAL may auto-create if it doesn't exist)")
return False
if __name__ == "__main__":
success = test_opendal_connection()
sys.exit(0 if success else 1)
```
**使用方法 / Usage:**
```bash
chmod +x test-opendal-ozone.py
python3 test-opendal-ozone.py
```
---
### 3. test-upload-local-file.py - 本地檔案上傳測試腳本
這個腳本用於將本地檔案透過 OpenDAL 上傳到 Ozone。
This script is used to upload local files to Ozone via OpenDAL.
```python
#!/usr/bin/env python3
"""
測試將本地檔案透過 OpenDAL 上傳到 Ozone S3 Gateway
Test uploading local files to Ozone S3 Gateway via OpenDAL
"""
import os
import sys
import tempfile
from pathlib import Path
# 設置環境變數(如果尚未設置)
os.environ.setdefault('AWS_ACCESS_KEY_ID', 'testuser/scm@EXAMPLE.COM')
os.environ.setdefault('AWS_SECRET_ACCESS_KEY', 'c261b6ecabf7d37d5f9ded654b1c724adac9bd9f13e247a235e567e8296d2999')
try:
import opendal
print("✅ OpenDAL 已安裝 / OpenDAL is installed\n")
except ImportError:
print("❌ OpenDAL 未安裝 / OpenDAL is not installed")
print("\n請先安裝 OpenDAL / Please install OpenDAL first:")
print(" pip3 install opendal")
sys.exit(1)
def create_test_file(content, filename=None):
"""建立一個測試檔案 / Create a test file"""
if filename is None:
# 建立臨時檔案
fd, filename = tempfile.mkstemp(suffix='.txt', prefix='ozone-test-', text=True)
os.close(fd)
with open(filename, 'w', encoding='utf-8') as f:
f.write(content)
return filename
def upload_local_file_to_ozone(local_file_path, s3_key=None):
"""將本地檔案上傳到 Ozone / Upload local file to Ozone"""
print("=== 測試將本地檔案上傳到 Ozone ===")
print("=== Testing Upload Local File to Ozone ===\n")
# 檢查本地檔案是否存在
if not os.path.exists(local_file_path):
print(f"❌ 錯誤:本地檔案不存在 / Error: Local file does not exist: {local_file_path}")
return False
# 取得檔案資訊
file_size = os.path.getsize(local_file_path)
file_name = os.path.basename(local_file_path)
print(f"本地檔案 / Local file: {local_file_path}")
print(f"檔案名稱 / File name: {file_name}")
print(f"檔案大小 / File size: {file_size} bytes\n")
# Ozone S3 Gateway 設定
endpoint = "http://localhost:9878"
bucket = "test-bucket"
access_key_id = os.environ.get('AWS_ACCESS_KEY_ID')
secret_access_key = os.environ.get('AWS_SECRET_ACCESS_KEY')
# 如果沒有指定 S3 key,使用文件名
if s3_key is None:
s3_key = file_name
print(f"端點 / Endpoint: {endpoint}")
print(f"桶名 / Bucket: {bucket}")
print(f"S3 Key (目標路徑) / S3 Key (target path): {s3_key}\n")
try:
# 建立 OpenDAL S3 操作符
print("1. 建立 OpenDAL S3 操作符 / Creating OpenDAL S3 operator...")
op = opendal.Operator(
"s3",
endpoint=endpoint,
bucket=bucket,
access_key_id=access_key_id,
secret_access_key=secret_access_key,
region="us-east-1",
)
print(" ✅ 操作符建立成功 / Operator created successfully\n")
# 讀取本地檔案
print("2. 讀取本地檔案 / Reading local file...")
with open(local_file_path, 'rb') as f:
file_content = f.read()
print(f" ✅ 檔案讀取成功,大小 / File read successfully, size: {len(file_content)} bytes\n")
# 上傳檔案到 Ozone
print(f"3. 上傳檔案到 Ozone / Uploading file to Ozone...")
print(f" 目標路徑 / Target path: s3://{bucket}/{s3_key}")
op.write(s3_key, file_content)
print(f" ✅ 檔案上傳成功!/ File uploaded successfully!\n")
# 驗證上傳
print("4. 驗證上傳的檔案 / Verifying uploaded file...")
uploaded_content = op.read(s3_key)
# 確保內容是 bytes 類型
if isinstance(uploaded_content, memoryview):
uploaded_content = bytes(uploaded_content)
elif not isinstance(uploaded_content, bytes):
uploaded_content = bytes(uploaded_content)
if uploaded_content == file_content:
print(f" ✅ 檔案驗證成功!內容完全匹配 / File verification successful! Content matches perfectly")
print(f" 上傳大小 / Uploaded size: {len(uploaded_content)} bytes\n")
else:
print(f" ⚠️ 檔案內容不匹配 / File content mismatch")
print(f" 原始大小 / Original size: {len(file_content)} bytes")
print(f" 上傳大小 / Uploaded size: {len(uploaded_content)} bytes\n")
# 列出桶中的文件
print("5. 列出桶中的檔案 / Listing files in bucket...")
entries = op.list("/")
file_list = list(entries)
print(f" ✅ 找到 {len(file_list)} 個檔案 / Found {len(file_list)} file(s)")
for entry in file_list:
try:
path = entry.path() if callable(entry.path) else entry.path
except:
path = str(entry)
print(f" - {path}")
print()
print("=== ✅ 測試完成!/ Test Complete! ===")
print(f"\n檔案已成功上傳到 Ozone S3 Gateway:")
print(f" s3://{bucket}/{s3_key}")
print(f"\nFile successfully uploaded to Ozone S3 Gateway:")
print(f" s3://{bucket}/{s3_key}\n")
return True
except Exception as e:
print(f"\n❌ 錯誤 / Error: {type(e).__name__}")
print(f" 訊息 / Message: {str(e)}\n")
# 提供故障排除建議
print("故障排除建議 / Troubleshooting suggestions:")
print("1. 確認 Ozone S3 Gateway 正在執行 / Verify Ozone S3 Gateway is running:")
print(" curl http://localhost:9878")
print("2. 確認桶是否存在 / Verify bucket exists:")
print(" docker compose exec om bash -c 'ozone sh bucket create /s3v/test-bucket'")
print("3. 確認環境變數已設置 / Verify environment variables are set:")
print(f" AWS_ACCESS_KEY_ID={access_key_id}")
return False
def main():
"""主函數 / Main function"""
# 如果提供了命令行參數,使用該文件
if len(sys.argv) > 1:
local_file = sys.argv[1]
s3_key = sys.argv[2] if len(sys.argv) > 2 else None
else:
# 否則建立一個測試檔案
print("未提供檔案路徑,建立測試檔案...")
print("No file path provided, creating test file...\n")
test_content = """這是一個測試檔案 / This is a test file
測試將本地檔案上傳到 Ozone / Test uploading local file to Ozone
檔案內容 / File content:
- 中文測試 / Chinese test: 你好,世界!
- English test: Hello, World!
- 數字測試 / Number test: 1234567890
- 特殊字符 / Special characters: !@#$%^&*()
時間戳 / Timestamp: 2025-11-15
"""
local_file = create_test_file(test_content, "test-upload-file.txt")
s3_key = "uploaded-test-file.txt"
print(f"已建立測試檔案 / Test file created: {local_file}\n")
# 上傳檔案
success = upload_local_file_to_ozone(local_file, s3_key)
# 如果是臨時檔案,可選清理
if local_file.startswith(tempfile.gettempdir()):
try:
os.remove(local_file)
print(f"已清理臨時檔案 / Cleaned up temporary file: {local_file}")
except:
pass
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()
```
**使用方法 / Usage:**
```bash
chmod +x test-upload-local-file.py
# 上傳指定檔案 / Upload specified file
python3 test-upload-local-file.py /path/to/your/file.txt [s3-key-name]
# 建立測試檔案並上傳 / Create test file and upload
python3 test-upload-local-file.py
```
---
### 4. test-move-file.py - 檔案移動測試腳本
這個腳本用於測試在 Ozone 中使用 OpenDAL 移動檔案。
This script is used to test moving files in Ozone using OpenDAL.
```python
#!/usr/bin/env python3
"""
測試在 Ozone 中使用 OpenDAL 移動文件
Test moving files in Ozone using OpenDAL
"""
import os
import sys
# 設置環境變數(如果尚未設置)
os.environ.setdefault('AWS_ACCESS_KEY_ID', 'testuser/scm@EXAMPLE.COM')
os.environ.setdefault('AWS_SECRET_ACCESS_KEY', 'c261b6ecabf7d37d5f9ded654b1c724adac9bd9f13e247a235e567e8296d2999')
try:
import opendal
print("✅ OpenDAL 已安裝 / OpenDAL is installed\n")
except ImportError:
print("❌ OpenDAL 未安裝 / OpenDAL is not installed")
print("\n請先安裝 OpenDAL / Please install OpenDAL first:")
print(" pip3 install opendal")
sys.exit(1)
def move_file_in_ozone(op, source_key, target_key, overwrite=False):
"""
在 Ozone 中移動文件 / Move file in Ozone
參數 / Parameters:
- op: OpenDAL 操作符 / OpenDAL operator
- source_key: 來源檔案路徑 / Source file path
- target_key: 目標檔案路徑 / Target file path
- overwrite: 是否覆蓋已存在的目標檔案 / Whether to overwrite existing target file
返回 / Returns:
- True: 移動成功 / Move successful
- False: 移動失敗 / Move failed
"""
print(f"=== 移動文件 / Moving File ===")
print(f"源文件 / Source: {source_key}")
print(f"目標文件 / Target: {target_key}\n")
# 條件檢查 1: 檢查來源檔案是否存在 / Condition 1: Check if source file exists
print("1. 檢查源文件是否存在 / Checking if source file exists...")
try:
source_data = op.read(source_key)
# 確保是 bytes 類型 / Ensure it's bytes type
if isinstance(source_data, memoryview):
source_data = bytes(source_data)
elif not isinstance(source_data, bytes):
source_data = bytes(source_data)
print(f" ✅ 源文件存在,大小 / Source file exists, size: {len(source_data)} bytes\n")
except Exception as e:
print(f" ❌ 錯誤:無法讀取源文件 / Error: Cannot read source file: {source_key}")
print(f" 原因 / Reason: {e}\n")
return False
# 條件檢查 2: 檢查目標檔案是否存在 / Condition 2: Check if target file exists
print("2. 檢查目標文件是否存在 / Checking if target file exists...")
if not overwrite:
try:
op.read(target_key)
print(f" ⚠️ 警告:目標文件已存在 / Warning: Target file already exists: {target_key}")
print(f" 使用 overwrite=True 來覆蓋 / Use overwrite=True to overwrite\n")
return False
except:
print(f" ✅ 目標文件不存在,可以繼續 / Target file does not exist, can proceed\n")
else:
print(f" ℹ️ 覆蓋模式已啟用 / Overwrite mode enabled\n")
# 步驟 1: 複製文件到目標位置 / Step 1: Copy file to target location
print("3. 複製文件到目標位置 / Copying file to target location...")
try:
op.write(target_key, source_data)
print(f" ✅ 文件已複製到目標位置 / File copied to target: {target_key}\n")
except Exception as e:
print(f" ❌ 錯誤:無法寫入目標文件 / Error: Cannot write target file: {target_key}")
print(f" 原因 / Reason: {e}\n")
return False
# 驗證目標文件 / Verify target file
print("4. 驗證目標文件 / Verifying target file...")
try:
target_data = op.read(target_key)
if isinstance(target_data, memoryview):
target_data = bytes(target_data)
elif not isinstance(target_data, bytes):
target_data = bytes(target_data)
if target_data == source_data:
print(f" ✅ 目標文件驗證成功,內容匹配 / Target file verified, content matches\n")
else:
print(f" ⚠️ 警告:目標文件內容不匹配 / Warning: Target file content mismatch\n")
except Exception as e:
print(f" ⚠️ 警告:無法驗證目標文件 / Warning: Cannot verify target file: {e}\n")
# 步驟 2: 刪除源文件 / Step 2: Delete source file
print("5. 刪除源文件 / Deleting source file...")
try:
op.delete(source_key)
print(f" ✅ 源文件已刪除 / Source file deleted: {source_key}\n")
except Exception as e:
print(f" ⚠️ 警告:無法刪除源文件 / Warning: Cannot delete source file: {source_key}")
print(f" 原因 / Reason: {e}")
print(f" 目標文件已創建,但源文件仍存在 / Target file created, but source file still exists\n")
return False
print("=== ✅ 文件移動成功!/ File move successful! ===")
print(f" 從 / From: {source_key}")
print(f" 到 / To: {target_key}\n")
return True
def main():
"""主函數 / Main function"""
# Ozone S3 Gateway 設定
endpoint = "http://localhost:9878"
bucket = "test-bucket"
access_key_id = os.environ.get('AWS_ACCESS_KEY_ID')
secret_access_key = os.environ.get('AWS_SECRET_ACCESS_KEY')
print("=== 測試在 Ozone 中使用 OpenDAL 移動檔案 ===")
print("=== Testing File Movement in Ozone using OpenDAL ===\n")
# 建立 OpenDAL S3 操作符
print("建立 OpenDAL S3 操作符 / Creating OpenDAL S3 operator...")
op = opendal.Operator(
"s3",
endpoint=endpoint,
bucket=bucket,
access_key_id=access_key_id,
secret_access_key=secret_access_key,
region="us-east-1",
)
print("✅ 操作符建立成功 / Operator created successfully\n")
# 建立一個測試檔案
print("建立測試檔案 / Creating test file...")
test_content = b"This is a test file for moving operation.\nCreated at: 2025-11-15\n"
test_key = "file-to-move.txt"
try:
op.write(test_key, test_content)
print(f"✅ 測試檔案已建立: {test_key}\n")
except Exception as e:
print(f"❌ 無法建立測試檔案 / Cannot create test file: {e}\n")
return False
# 移動檔案
target_key = "moved-file.txt"
success = move_file_in_ozone(op, test_key, target_key, overwrite=False)
# 列出桶中的檔案
print("\n列出桶中的檔案 / Listing files in bucket...")
try:
entries = op.list("/")
file_list = list(entries)
print(f"找到 {len(file_list)} 個檔案 / Found {len(file_list)} file(s):")
for entry in file_list:
try:
path = entry.path() if callable(entry.path) else entry.path
except:
path = str(entry)
print(f" - {path}")
except Exception as e:
print(f"無法列出檔案 / Cannot list files: {e}")
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()
```
**使用方法 / Usage:**
```bash
chmod +x test-move-file.py
python3 test-move-file.py
```
---
### 5. 檔案移動函數(可重用程式碼)/ File Movement Function (Reusable Code)
這是一個可以在您的應用程式中重用的檔案移動函數。
This is a reusable file movement function that can be used in your applications.
```python
import opendal
def move_file_in_ozone(op, source_key, target_key, overwrite=False):
"""
在 Ozone 中移動檔案 / Move file in Ozone
參數 / Parameters:
- op: OpenDAL 操作符 / OpenDAL operator
- source_key: 來源檔案路徑 / Source file path
- target_key: 目標檔案路徑 / Target file path
- overwrite: 是否覆蓋已存在的目標檔案 / Whether to overwrite existing target file
返回 / Returns:
- True: 移動成功 / Move successful
- False: 移動失敗 / Move failed
"""
# 條件檢查 1: 檢查來源檔案是否存在 / Condition 1: Check if source file exists
try:
source_data = op.read(source_key)
# 確保是 bytes 類型 / Ensure it's bytes type
if isinstance(source_data, memoryview):
source_data = bytes(source_data)
elif not isinstance(source_data, bytes):
source_data = bytes(source_data)
except Exception as e:
print(f"❌ 錯誤:無法讀取來源檔案 / Error: Cannot read source file: {source_key}")
print(f" 原因 / Reason: {e}")
return False
# 條件檢查 2: 檢查目標檔案是否存在 / Condition 2: Check if target file exists
if not overwrite:
try:
op.read(target_key)
print(f"⚠️ 警告:目標檔案已存在 / Warning: Target file already exists: {target_key}")
print(f" 使用 overwrite=True 來覆蓋 / Use overwrite=True to overwrite")
return False
except:
# 目標檔案不存在,可以繼續 / Target file does not exist, can proceed
pass
# 步驟 1: 複製檔案到目標位置 / Step 1: Copy file to target location
try:
op.write(target_key, source_data)
print(f"✅ 檔案已複製到目標位置 / File copied to target: {target_key}")
except Exception as e:
print(f"❌ 錯誤:無法寫入目標檔案 / Error: Cannot write target file: {target_key}")
print(f" 原因 / Reason: {e}")
return False
# 步驟 2: 刪除來源檔案 / Step 2: Delete source file
try:
op.delete(source_key)
print(f"✅ 來源檔案已刪除 / Source file deleted: {source_key}")
except Exception as e:
print(f"⚠️ 警告:無法刪除來源檔案 / Warning: Cannot delete source file: {source_key}")
print(f" 原因 / Reason: {e}")
print(f" 目標檔案已建立,但來源檔案仍存在 / Target file created, but source file still exists")
# 可以選擇回滾(刪除目標檔案)/ Can choose to rollback (delete target file)
return False
print(f"✅ 檔案移動成功!/ File move successful!")
print(f" 從 / From: {source_key}")
print(f" 到 / To: {target_key}")
return True
# 使用範例 / Usage Example
op = opendal.Operator(
"s3",
endpoint="http://localhost:9878",
bucket="test-bucket",
access_key_id="testuser/scm@EXAMPLE.COM",
secret_access_key="c261b6ecabf7d37d5f9ded654b1c724adac9bd9f13e247a235e567e8296d2999",
region="us-east-1",
)
# 移動文件 / Move file
move_file_in_ozone(op, "old-file.txt", "new-file.txt", overwrite=False)
```
---
### 6. 帶重試機制的上傳函數 / Upload Function with Retry Mechanism
這是一個帶有錯誤重試機制的上傳函數,適用於生產環境。
This is an upload function with error retry mechanism, suitable for production environments.
```python
import opendal
import time
def upload_with_retry(op, key, data, max_retries=3, initial_delay=1):
"""
帶重試機制的上傳函數 / Upload function with retry mechanism
參數 / Parameters:
- op: OpenDAL 操作符 / OpenDAL operator
- key: S3 鍵名 / S3 key name
- data: 要上傳的資料 / Data to upload
- max_retries: 最大重試次數 / Maximum number of retries
- initial_delay: 初始延遲時間(秒)/ Initial delay time (seconds)
返回 / Returns:
- True: 上傳成功 / Upload successful
- False: 上傳失敗 / Upload failed
"""
for attempt in range(max_retries):
try:
op.write(key, data)
if attempt > 0:
print(f"✅ 上傳成功(第 {attempt + 1} 次嘗試)/ Upload successful (attempt {attempt + 1})")
return True
except Exception as e:
if attempt < max_retries - 1:
delay = initial_delay * (2 ** attempt) # 指數退避 / Exponential backoff
print(f"⚠️ 上傳失敗(嘗試 {attempt + 1}/{max_retries}),{delay} 秒後重試...")
print(f" Warning: Upload failed (attempt {attempt + 1}/{max_retries}), retrying in {delay} seconds...")
print(f" 錯誤 / Error: {str(e)[:100]}")
time.sleep(delay)
continue
else:
print(f"❌ 上傳失敗,已達到最大重試次數 / Upload failed, maximum retries reached")
print(f" 錯誤 / Error: {e}")
return False
return False
# 使用範例 / Usage Example
op = opendal.Operator(
"s3",
endpoint="http://localhost:9878",
bucket="test-bucket",
access_key_id="testuser/scm@EXAMPLE.COM",
secret_access_key="c261b6ecabf7d37d5f9ded654b1c724adac9bd9f13e247a235e567e8296d2999",
region="us-east-1",
)
# 讀取本地文件並上傳 / Read local file and upload
with open("/path/to/local/file.txt", "rb") as f:
file_content = f.read()
# 使用重試機制上傳 / Upload with retry mechanism
upload_with_retry(op, "file.txt", file_content, max_retries=3)
```
---
### 7. 批次上傳函數 / Batch Upload Function
這是一個批次上傳多個檔案的函數。
This is a function for batch uploading multiple files.
```python
import opendal
import os
from pathlib import Path
def batch_upload_files(op, local_dir, s3_prefix="", max_workers=5):
"""
批次上傳本地目錄中的檔案到 Ozone / Batch upload files from local directory to Ozone
參數 / Parameters:
- op: OpenDAL 操作符 / OpenDAL operator
- local_dir: 本地目錄路徑 / Local directory path
- s3_prefix: S3 前綴(可選)/ S3 prefix (optional)
- max_workers: 最大並行數 / Maximum concurrency
返回 / Returns:
- dict: 上傳結果統計 / Upload result statistics
"""
results = {
"success": 0,
"failed": 0,
"total": 0,
"errors": []
}
local_path = Path(local_dir)
if not local_path.exists() or not local_path.is_dir():
print(f"❌ 錯誤:目錄不存在 / Error: Directory does not exist: {local_dir}")
return results
# 取得所有檔案 / Get all files
files = list(local_path.rglob("*"))
files = [f for f in files if f.is_file()]
results["total"] = len(files)
print(f"找到 {results['total']} 個檔案 / Found {results['total']} files")
print(f"開始批次上傳... / Starting batch upload...\n")
for file_path in files:
try:
# 計算相對路徑 / Calculate relative path
relative_path = file_path.relative_to(local_path)
s3_key = f"{s3_prefix}/{relative_path}".lstrip("/") if s3_prefix else str(relative_path)
# 讀取檔案 / Read file
with open(file_path, "rb") as f:
file_content = f.read()
# 上傳檔案 / Upload file
op.write(s3_key, file_content)
results["success"] += 1
print(f"✅ [{results['success']}/{results['total']}] {file_path.name} -> {s3_key}")
except Exception as e:
results["failed"] += 1
error_msg = f"❌ [{results['failed']}/{results['total']}] {file_path.name}: {str(e)}"
results["errors"].append(error_msg)
print(error_msg)
print(f"\n=== 批次上傳完成 / Batch upload complete ===")
print(f"成功 / Success: {results['success']}")
print(f"失敗 / Failed: {results['failed']}")
print(f"總計 / Total: {results['total']}")
return results
# 使用範例 / Usage Example
op = opendal.Operator(
"s3",
endpoint="http://localhost:9878",
bucket="test-bucket",
access_key_id="testuser/scm@EXAMPLE.COM",
secret_access_key="c261b6ecabf7d37d5f9ded654b1c724adac9bd9f13e247a235e567e8296d2999",
region="us-east-1",
)
# 批次上傳 / Batch upload
results = batch_upload_files(op, "/path/to/local/directory", s3_prefix="uploads")
```
---
### 腳本檔案位置 / Script File Locations
所有測試腳本都位於專案根目錄:
All test scripts are located in the project root directory:
- `test-s3-connection.sh` - S3 Gateway 連線測試
- `test-opendal-ozone.py` - OpenDAL 連線和基本操作測試
- `test-upload-local-file.py` - 本地檔案上傳測試
- `test-move-file.py` - 檔案移動測試