# 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` - 檔案移動測試