44
55import base64
66import hashlib
7+ import json
8+ import os
79import random
10+ import signal
11+ import subprocess
12+ import time
13+ from tempfile import TemporaryDirectory
814
915import boto3
1016import botocore
1521from signurlarity import Client
1622from signurlarity .aio import AsyncClient
1723
18- # logging.basicConfig(
19- # format="%(levelname)s [%(asctime)s] %(name)s - %(message)s",
20- # datefmt="%Y-%m-%d %H:%M:%S",
21- # level=logging.DEBUG,
22- # )
23-
24-
2524# Constants
2625BUCKET_NAME = "test-bucket"
2726OTHER_BUCKET_NAME = "other-bucket"
@@ -143,9 +142,159 @@ def minio_server():
143142 subprocess .run (cmd , check = True ) # noqa: S603
144143
145144
145+ @pytest .fixture (scope = "module" )
146+ def seaweedfs_server ():
147+ """Run a SeaweedFS server with S3 API enabled.
148+
149+ Because it creates volumes on the fly, we have to upload a file
150+ and wait for the initialization to be over, otherwise all the tests
151+ fail.
152+ """
153+ AWS_ACCESS_KEY_ID = "admin"
154+ AWS_SECRET_ACCESS_KEY = "key" # noqa: S105
155+
156+ def check_volume_status (max_retries = 10 , retry_delay = 5 ):
157+ cmd = ["weed" , "shell" ]
158+ # Use echo to send the command to weed shell
159+ input_cmd = "cluster.status\n "
160+
161+ for attempt in range (1 , max_retries + 1 ):
162+ try :
163+ process = subprocess .Popen ( # noqa: S603
164+ cmd ,
165+ stdin = subprocess .PIPE ,
166+ stdout = subprocess .PIPE ,
167+ stderr = subprocess .PIPE ,
168+ text = True ,
169+ )
170+ stdout , _stderr = process .communicate (input = input_cmd , timeout = 15 )
171+
172+ # Check if "7 volume" is in the output
173+ if "7 volume" in stdout :
174+ print ("Found '7 volume' in output!" )
175+ return
176+
177+ print (
178+ f"'7 volume' not found (attempt { attempt } /{ max_retries } ), "
179+ f"retrying in { retry_delay } seconds..."
180+ )
181+ except subprocess .TimeoutExpired :
182+ process .kill ()
183+ stdout , _stderr = process .communicate ()
184+ print (
185+ f"weed shell timed out (attempt { attempt } /{ max_retries } ), "
186+ f"retrying in { retry_delay } seconds..."
187+ )
188+ except Exception as exc :
189+ print (
190+ f"Error checking volume status (attempt { attempt } /{ max_retries } ): { exc } "
191+ )
192+
193+ if attempt < max_retries :
194+ time .sleep (retry_delay )
195+
196+ raise RuntimeError (
197+ f"SeaweedFS did not report '7 volume' after { max_retries } attempts"
198+ )
199+
200+ with TemporaryDirectory () as tmp_dir :
201+ os .mkdir (f"{ tmp_dir } /seaweedfs" )
202+ with open (f"{ tmp_dir } /seaweedfs_s3.json" , "wt" ) as f :
203+ json .dump (
204+ {
205+ "identities" : [
206+ {
207+ "name" : "admin" ,
208+ "credentials" : [
209+ {
210+ "accessKey" : AWS_ACCESS_KEY_ID ,
211+ "secretKey" : AWS_SECRET_ACCESS_KEY ,
212+ }
213+ ],
214+ "actions" : ["Admin" , "Read" , "Write" , "List" , "Tagging" ],
215+ }
216+ ]
217+ },
218+ f ,
219+ )
220+ cmd = [
221+ "weed" ,
222+ "mini" ,
223+ "-dir" ,
224+ f"{ tmp_dir } /seaweedfs" ,
225+ "-s3.config" ,
226+ f"{ tmp_dir } /seaweedfs_s3.json" ,
227+ ]
228+ with open (f"{ tmp_dir } /seaweedfs.log" , "w" ) as log_file :
229+ pid = None
230+ try :
231+ process = subprocess .Popen ( # noqa: S603
232+ cmd ,
233+ stdout = log_file ,
234+ stderr = subprocess .STDOUT , # Redirect stderr to stdout
235+ )
236+
237+ pid = process .pid
238+ print (f"Process PID: { pid } Working Directory { tmp_dir } " )
239+ upload_cmd = [
240+ "weed" ,
241+ "upload" ,
242+ "-master" ,
243+ "localhost:9333" ,
244+ f"{ tmp_dir } /seaweedfs.log" ,
245+ ]
246+ max_retries = 10
247+ retry_delay = 5
248+
249+ for attempt in range (1 , max_retries + 1 ):
250+ try :
251+ subprocess .run ( # noqa: S603
252+ upload_cmd , check = True , capture_output = True , text = True
253+ )
254+ print ("Upload successful!" )
255+ break
256+ except subprocess .CalledProcessError as e :
257+ if attempt >= max_retries :
258+ raise RuntimeError (
259+ f"Upload failed after { max_retries } attempts: { e .stderr } "
260+ ) from e
261+ print (
262+ f"Upload failed (attempt { attempt } /{ max_retries } ), "
263+ f"retrying in { retry_delay } seconds... (Error: { e .stderr } )"
264+ )
265+ time .sleep (retry_delay )
266+ check_volume_status ()
267+
268+ yield {
269+ "endpoint_url" : "http://localhost:8333" ,
270+ "aws_access_key_id" : AWS_ACCESS_KEY_ID ,
271+ "aws_secret_access_key" : AWS_SECRET_ACCESS_KEY ,
272+ }
273+ except RuntimeError as e :
274+ print (e )
275+ log_file .flush ()
276+ print ("=== SeaweedFS log start ===" )
277+ try :
278+ with open (
279+ f"{ tmp_dir } /seaweedfs.log" ,
280+ "rt" ,
281+ encoding = "utf-8" ,
282+ errors = "replace" ,
283+ ) as read_log :
284+ print (read_log .read ())
285+ except OSError as log_error :
286+ print (f"Failed to read SeaweedFS log file: { log_error } " )
287+ print ("=== SeaweedFS log end ===" )
288+ raise
289+ finally :
290+ if pid :
291+ os .kill (pid , signal .SIGKILL )
292+
293+
146294# Synchronous client fixtures
147295@pytest .fixture (
148- scope = "function" , params = ["minio_server" , "moto_server" , "rustfs_server" ]
296+ scope = "function" ,
297+ params = ["minio_server" , "moto_server" , "rustfs_server" , "seaweedfs_server" ],
149298)
150299def s3_clients (request ):
151300 """S3 clients for synchronous tests with multiple server backends.
@@ -171,7 +320,8 @@ def s3_clients(request):
171320
172321# Asynchronous client fixtures
173322@pytest .fixture (
174- scope = "function" , params = ["minio_server" , "moto_server" , "rustfs_server" ]
323+ scope = "function" ,
324+ params = ["minio_server" , "moto_server" , "rustfs_server" , "seaweedfs_server" ],
175325)
176326async def s3_clients_aio (request ):
177327 """S3 clients for asynchronous tests with multiple server backends.
0 commit comments