You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							290 lines
						
					
					
						
							10 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							290 lines
						
					
					
						
							10 KiB
						
					
					
				
								#!/usr/bin/env python3
							 | 
						|
								"""
							 | 
						|
								Patch Ceph s3-tests helpers to avoid bucket name mismatches and make bucket
							 | 
						|
								creation idempotent when a fixed bucket name is provided.
							 | 
						|
								
							 | 
						|
								Why:
							 | 
						|
								- Some tests call get_new_bucket() to get a name, then call
							 | 
						|
								  get_new_bucket_resource(name=<that name>) which unconditionally calls
							 | 
						|
								  CreateBucket again. If the bucket already exists, boto3 raises a
							 | 
						|
								  ClientError. We want to treat that as idempotent and reuse the bucket.
							 | 
						|
								- We must NOT silently generate a different bucket name when a name is
							 | 
						|
								  explicitly provided, otherwise subsequent test steps still reference the
							 | 
						|
								  original string and read from the wrong (empty) bucket.
							 | 
						|
								
							 | 
						|
								What this does:
							 | 
						|
								- get_new_bucket_resource(name=...):
							 | 
						|
								  - Try to create the exact bucket name.
							 | 
						|
								  - If error code is BucketAlreadyOwnedByYou OR BucketAlreadyExists, simply
							 | 
						|
								    reuse and return the bucket object for that SAME name.
							 | 
						|
								  - Only when name is None, generate a new unique name with retries.
							 | 
						|
								- get_new_bucket(client=None, name=None):
							 | 
						|
								  - If name is None, generate unique names with retries until creation
							 | 
						|
								    succeeds, and return the actual name string to the caller.
							 | 
						|
								
							 | 
						|
								This keeps bucket names consistent across the test helper calls and prevents
							 | 
						|
								404s or KeyErrors later in the tests that depend on that bucket name.
							 | 
						|
								"""
							 | 
						|
								
							 | 
						|
								import os
							 | 
						|
								import sys
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								def patch_s3_tests_init_file(file_path: str) -> bool:
							 | 
						|
								    if not os.path.exists(file_path):
							 | 
						|
								        print(f"Error: File {file_path} not found")
							 | 
						|
								        return False
							 | 
						|
								
							 | 
						|
								    print(f"Patching {file_path}...")
							 | 
						|
								    with open(file_path, "r", encoding="utf-8") as f:
							 | 
						|
								        content = f.read()
							 | 
						|
								
							 | 
						|
								    # If already patched, skip
							 | 
						|
								    if "max_retries = 10" in content and "BucketAlreadyOwnedByYou" in content and "BucketAlreadyExists" in content:
							 | 
						|
								        print("Already patched. Skipping.")
							 | 
						|
								        return True
							 | 
						|
								
							 | 
						|
								    old_resource_func = '''def get_new_bucket_resource(name=None):
							 | 
						|
								    """
							 | 
						|
								    Get a bucket that exists and is empty.
							 | 
						|
								
							 | 
						|
								    Always recreates a bucket from scratch. This is useful to also
							 | 
						|
								    reset ACLs and such.
							 | 
						|
								    """
							 | 
						|
								    s3 = boto3.resource('s3',
							 | 
						|
								                        aws_access_key_id=config.main_access_key,
							 | 
						|
								                        aws_secret_access_key=config.main_secret_key,
							 | 
						|
								                        endpoint_url=config.default_endpoint,
							 | 
						|
								                        use_ssl=config.default_is_secure,
							 | 
						|
								                        verify=config.default_ssl_verify)
							 | 
						|
								    if name is None:
							 | 
						|
								        name = get_new_bucket_name()
							 | 
						|
								    bucket = s3.Bucket(name)
							 | 
						|
								    bucket_location = bucket.create()
							 | 
						|
								    return bucket'''
							 | 
						|
								
							 | 
						|
								    new_resource_func = '''def get_new_bucket_resource(name=None):
							 | 
						|
								    """
							 | 
						|
								    Get a bucket that exists and is empty.
							 | 
						|
								
							 | 
						|
								    Always recreates a bucket from scratch. This is useful to also
							 | 
						|
								    reset ACLs and such.
							 | 
						|
								    """
							 | 
						|
								    s3 = boto3.resource('s3',
							 | 
						|
								                        aws_access_key_id=config.main_access_key,
							 | 
						|
								                        aws_secret_access_key=config.main_secret_key,
							 | 
						|
								                        endpoint_url=config.default_endpoint,
							 | 
						|
								                        use_ssl=config.default_is_secure,
							 | 
						|
								                        verify=config.default_ssl_verify)
							 | 
						|
								
							 | 
						|
								    from botocore.exceptions import ClientError
							 | 
						|
								
							 | 
						|
								    # If a name is provided, do not change it. Reuse that exact bucket name.
							 | 
						|
								    if name is not None:
							 | 
						|
								        bucket = s3.Bucket(name)
							 | 
						|
								        try:
							 | 
						|
								            bucket.create()
							 | 
						|
								        except ClientError as e:
							 | 
						|
								            code = e.response.get('Error', {}).get('Code')
							 | 
						|
								            if code in ('BucketAlreadyOwnedByYou', 'BucketAlreadyExists'):
							 | 
						|
								                # Treat as idempotent create for an explicitly provided name.
							 | 
						|
								                # We must not change the name or tests will read from the wrong bucket.
							 | 
						|
								                return bucket
							 | 
						|
								            # Other errors should surface
							 | 
						|
								            raise
							 | 
						|
								        else:
							 | 
						|
								            return bucket
							 | 
						|
								
							 | 
						|
								    # Only generate unique names when no name was provided
							 | 
						|
								    max_retries = 10
							 | 
						|
								    for attempt in range(max_retries):
							 | 
						|
								        gen_name = get_new_bucket_name()
							 | 
						|
								        bucket = s3.Bucket(gen_name)
							 | 
						|
								        try:
							 | 
						|
								            bucket.create()
							 | 
						|
								            return bucket
							 | 
						|
								        except ClientError as e:
							 | 
						|
								            code = e.response.get('Error', {}).get('Code')
							 | 
						|
								            if code in ('BucketAlreadyExists', 'BucketAlreadyOwnedByYou'):
							 | 
						|
								                if attempt == max_retries - 1:
							 | 
						|
								                    raise Exception(f"Failed to create unique bucket after {max_retries} attempts")
							 | 
						|
								                continue
							 | 
						|
								            else:
							 | 
						|
								                raise'''
							 | 
						|
								
							 | 
						|
								    old_client_func = '''def get_new_bucket(client=None, name=None):
							 | 
						|
								    """
							 | 
						|
								    Get a bucket that exists and is empty.
							 | 
						|
								
							 | 
						|
								    Always recreates a bucket from scratch. This is useful to also
							 | 
						|
								    reset ACLs and such.
							 | 
						|
								    """
							 | 
						|
								    if client is None:
							 | 
						|
								        client = get_client()
							 | 
						|
								    if name is None:
							 | 
						|
								        name = get_new_bucket_name()
							 | 
						|
								
							 | 
						|
								    client.create_bucket(Bucket=name)
							 | 
						|
								    return name'''
							 | 
						|
								
							 | 
						|
								    new_client_func = '''def get_new_bucket(client=None, name=None):
							 | 
						|
								    """
							 | 
						|
								    Get a bucket that exists and is empty.
							 | 
						|
								
							 | 
						|
								    Always recreates a bucket from scratch. This is useful to also
							 | 
						|
								    reset ACLs and such.
							 | 
						|
								    """
							 | 
						|
								    if client is None:
							 | 
						|
								        client = get_client()
							 | 
						|
								
							 | 
						|
								    from botocore.exceptions import ClientError
							 | 
						|
								
							 | 
						|
								    # If a name is provided, just try to create it once and fall back to idempotent reuse
							 | 
						|
								    if name is not None:
							 | 
						|
								        try:
							 | 
						|
								            client.create_bucket(Bucket=name)
							 | 
						|
								        except ClientError as e:
							 | 
						|
								            code = e.response.get('Error', {}).get('Code')
							 | 
						|
								            if code in ('BucketAlreadyOwnedByYou', 'BucketAlreadyExists'):
							 | 
						|
								                return name
							 | 
						|
								            raise
							 | 
						|
								        else:
							 | 
						|
								            return name
							 | 
						|
								
							 | 
						|
								    # Otherwise, generate a unique name with retries and return the actual name string
							 | 
						|
								    max_retries = 10
							 | 
						|
								    for attempt in range(max_retries):
							 | 
						|
								        gen_name = get_new_bucket_name()
							 | 
						|
								        try:
							 | 
						|
								            client.create_bucket(Bucket=gen_name)
							 | 
						|
								            return gen_name
							 | 
						|
								        except ClientError as e:
							 | 
						|
								            code = e.response.get('Error', {}).get('Code')
							 | 
						|
								            if code in ('BucketAlreadyExists', 'BucketAlreadyOwnedByYou'):
							 | 
						|
								                if attempt == max_retries - 1:
							 | 
						|
								                    raise Exception(f"Failed to create unique bucket after {max_retries} attempts")
							 | 
						|
								                continue
							 | 
						|
								            else:
							 | 
						|
								                raise'''
							 | 
						|
								
							 | 
						|
								    updated = content
							 | 
						|
								    updated = updated.replace(old_resource_func, new_resource_func)
							 | 
						|
								    updated = updated.replace(old_client_func, new_client_func)
							 | 
						|
								
							 | 
						|
								    if updated == content:
							 | 
						|
								        print("Patterns not found; appending override implementations to end of file.")
							 | 
						|
								        append_patch = '''
							 | 
						|
								
							 | 
						|
								# --- SeaweedFS override start ---
							 | 
						|
								from botocore.exceptions import ClientError as _Sw_ClientError
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								# Idempotent create for provided name; generate unique only when no name given
							 | 
						|
								# Keep the bucket name stable when provided by the caller
							 | 
						|
								
							 | 
						|
								def _sw_get_new_bucket_resource(name=None):
							 | 
						|
								    s3 = boto3.resource('s3',
							 | 
						|
								                        aws_access_key_id=config.main_access_key,
							 | 
						|
								                        aws_secret_access_key=config.main_secret_key,
							 | 
						|
								                        endpoint_url=config.default_endpoint,
							 | 
						|
								                        use_ssl=config.default_is_secure,
							 | 
						|
								                        verify=config.default_ssl_verify)
							 | 
						|
								    if name is not None:
							 | 
						|
								        bucket = s3.Bucket(name)
							 | 
						|
								        try:
							 | 
						|
								            bucket.create()
							 | 
						|
								        except _Sw_ClientError as e:
							 | 
						|
								            code = e.response.get('Error', {}).get('Code')
							 | 
						|
								            if code in ('BucketAlreadyOwnedByYou', 'BucketAlreadyExists'):
							 | 
						|
								                return bucket
							 | 
						|
								            raise
							 | 
						|
								        else:
							 | 
						|
								            return bucket
							 | 
						|
								    # name not provided: generate unique
							 | 
						|
								    max_retries = 10
							 | 
						|
								    for attempt in range(max_retries):
							 | 
						|
								        gen_name = get_new_bucket_name()
							 | 
						|
								        bucket = s3.Bucket(gen_name)
							 | 
						|
								        try:
							 | 
						|
								            bucket.create()
							 | 
						|
								            return bucket
							 | 
						|
								        except _Sw_ClientError as e:
							 | 
						|
								            code = e.response.get('Error', {}).get('Code')
							 | 
						|
								            if code in ('BucketAlreadyExists', 'BucketAlreadyOwnedByYou'):
							 | 
						|
								                if attempt == max_retries - 1:
							 | 
						|
								                    raise Exception(f"Failed to create unique bucket after {max_retries} attempts")
							 | 
						|
								                continue
							 | 
						|
								            else:
							 | 
						|
								                raise
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								from botocore.exceptions import ClientError as _Sw2_ClientError
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								def _sw_get_new_bucket(client=None, name=None):
							 | 
						|
								    if client is None:
							 | 
						|
								        client = get_client()
							 | 
						|
								    if name is not None:
							 | 
						|
								        try:
							 | 
						|
								            client.create_bucket(Bucket=name)
							 | 
						|
								        except _Sw2_ClientError as e:
							 | 
						|
								            code = e.response.get('Error', {}).get('Code')
							 | 
						|
								            if code in ('BucketAlreadyOwnedByYou', 'BucketAlreadyExists'):
							 | 
						|
								                return name
							 | 
						|
								            raise
							 | 
						|
								        else:
							 | 
						|
								            return name
							 | 
						|
								    max_retries = 10
							 | 
						|
								    for attempt in range(max_retries):
							 | 
						|
								        gen_name = get_new_bucket_name()
							 | 
						|
								        try:
							 | 
						|
								            client.create_bucket(Bucket=gen_name)
							 | 
						|
								            return gen_name
							 | 
						|
								        except _Sw2_ClientError as e:
							 | 
						|
								            code = e.response.get('Error', {}).get('Code')
							 | 
						|
								            if code in ('BucketAlreadyExists', 'BucketAlreadyOwnedByYou'):
							 | 
						|
								                if attempt == max_retries - 1:
							 | 
						|
								                    raise Exception(f"Failed to create unique bucket after {max_retries} attempts")
							 | 
						|
								                continue
							 | 
						|
								            else:
							 | 
						|
								                raise
							 | 
						|
								
							 | 
						|
								# Override original helper functions
							 | 
						|
								get_new_bucket_resource = _sw_get_new_bucket_resource
							 | 
						|
								get_new_bucket = _sw_get_new_bucket
							 | 
						|
								# --- SeaweedFS override end ---
							 | 
						|
								'''
							 | 
						|
								        with open(file_path, "a", encoding="utf-8") as f:
							 | 
						|
								            f.write(append_patch)
							 | 
						|
								        print("Appended override implementations.")
							 | 
						|
								        return True
							 | 
						|
								
							 | 
						|
								    with open(file_path, "w", encoding="utf-8") as f:
							 | 
						|
								        f.write(updated)
							 | 
						|
								
							 | 
						|
								    print("Successfully patched s3-tests helpers.")
							 | 
						|
								    return True
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								def main() -> int:
							 | 
						|
								    s3_tests_path = os.environ.get("S3_TESTS_PATH", "s3-tests")
							 | 
						|
								    init_file_path = os.path.join(s3_tests_path, "s3tests", "functional", "__init__.py")
							 | 
						|
								    print("Applying s3-tests patch for bucket creation idempotency...")
							 | 
						|
								    print(f"Target repo path: {s3_tests_path}")
							 | 
						|
								    if not os.path.exists(s3_tests_path):
							 | 
						|
								        print(f"Warning: s3-tests directory not found at {s3_tests_path}")
							 | 
						|
								        print("Skipping patch - directory structure may have changed in the upstream repository")
							 | 
						|
								        return 0  # Return success to not break CI
							 | 
						|
								    if not os.path.exists(init_file_path):
							 | 
						|
								        print(f"Warning: Target file {init_file_path} not found")
							 | 
						|
								        print("This may indicate the s3-tests repository structure has changed.")
							 | 
						|
								        print("Skipping patch - tests may still work without it")
							 | 
						|
								        return 0  # Return success to not break CI
							 | 
						|
								    ok = patch_s3_tests_init_file(init_file_path)
							 | 
						|
								    return 0 if ok else 1
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								if __name__ == "__main__":
							 | 
						|
								    sys.exit(main())
							 | 
						|
								
							 | 
						|
								
							 |