mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-04 23:09:44 +01:00
220 lines
5.9 KiB
Python
220 lines
5.9 KiB
Python
import importlib
|
|
import sys
|
|
import textwrap
|
|
from functools import wraps
|
|
|
|
import boto3
|
|
import moto.s3.models as s3model
|
|
import pytest
|
|
from moto import mock_s3
|
|
|
|
from dvc.fs.s3 import S3FileSystem
|
|
from dvc.objects.db.base import ObjectDB
|
|
from tests.remotes import S3
|
|
|
|
# from https://github.com/spulec/moto/blob/v1.3.5/tests/test_s3/test_s3.py#L40
|
|
REDUCED_PART_SIZE = 256
|
|
|
|
|
|
def reduced_min_part_size(f):
|
|
""" speed up tests by temporarily making the multipart minimum part size
|
|
small
|
|
"""
|
|
orig_size = s3model.UPLOAD_PART_MIN_SIZE
|
|
|
|
@wraps(f)
|
|
def wrapped(*args, **kwargs):
|
|
try:
|
|
s3model.UPLOAD_PART_MIN_SIZE = REDUCED_PART_SIZE
|
|
return f(*args, **kwargs)
|
|
finally:
|
|
s3model.UPLOAD_PART_MIN_SIZE = orig_size
|
|
|
|
return wrapped
|
|
|
|
|
|
def _get_src_dst():
|
|
base_info = S3FileSystem.PATH_CLS(S3.get_url())
|
|
return base_info / "from", base_info / "to"
|
|
|
|
|
|
@mock_s3
|
|
def test_copy_singlepart_preserve_etag():
|
|
from_info, to_info = _get_src_dst()
|
|
|
|
s3 = boto3.client("s3")
|
|
s3.create_bucket(Bucket=from_info.bucket)
|
|
s3.put_object(Bucket=from_info.bucket, Key=from_info.path, Body="data")
|
|
|
|
S3FileSystem._copy(s3, from_info, to_info, {})
|
|
|
|
|
|
@mock_s3
|
|
@pytest.mark.parametrize(
|
|
"base_info",
|
|
[
|
|
S3FileSystem.PATH_CLS("s3://bucket/"),
|
|
S3FileSystem.PATH_CLS("s3://bucket/ns/"),
|
|
],
|
|
)
|
|
def test_link_created_on_non_nested_path(base_info, tmp_dir, dvc, scm):
|
|
from dvc.checkout import _link
|
|
|
|
fs = S3FileSystem(dvc, {"url": str(base_info.parent)})
|
|
odb = ObjectDB(fs)
|
|
s3 = odb.fs.s3.meta.client
|
|
s3.create_bucket(Bucket=base_info.bucket)
|
|
s3.put_object(
|
|
Bucket=base_info.bucket, Key=(base_info / "from").path, Body="data"
|
|
)
|
|
_link(odb, base_info / "from", base_info / "to")
|
|
|
|
assert odb.fs.exists(base_info / "from")
|
|
assert odb.fs.exists(base_info / "to")
|
|
|
|
|
|
@mock_s3
|
|
def test_makedirs_doesnot_try_on_top_level_paths(tmp_dir, dvc, scm):
|
|
base_info = S3FileSystem.PATH_CLS("s3://bucket/")
|
|
fs = S3FileSystem(dvc, {"url": str(base_info)})
|
|
fs.makedirs(base_info)
|
|
|
|
|
|
def _upload_multipart(s3, Bucket, Key):
|
|
mpu = s3.create_multipart_upload(Bucket=Bucket, Key=Key)
|
|
mpu_id = mpu["UploadId"]
|
|
|
|
parts = []
|
|
n_parts = 10
|
|
for i in range(1, n_parts + 1):
|
|
# NOTE: Generation parts of variable size. Part size should be at
|
|
# least 5MB:
|
|
# https://docs.aws.amazon.com/AmazonS3/latest/API/mpUploadComplete.html
|
|
part_size = REDUCED_PART_SIZE + i
|
|
body = b"1" * part_size
|
|
part = s3.upload_part(
|
|
Bucket=Bucket,
|
|
Key=Key,
|
|
PartNumber=i,
|
|
UploadId=mpu_id,
|
|
Body=body,
|
|
ContentLength=len(body),
|
|
)
|
|
parts.append({"PartNumber": i, "ETag": part["ETag"]})
|
|
|
|
s3.complete_multipart_upload(
|
|
Bucket=Bucket,
|
|
Key=Key,
|
|
UploadId=mpu_id,
|
|
MultipartUpload={"Parts": parts},
|
|
)
|
|
|
|
|
|
@mock_s3
|
|
@reduced_min_part_size
|
|
def test_copy_multipart_preserve_etag():
|
|
from_info, to_info = _get_src_dst()
|
|
|
|
s3 = boto3.client("s3")
|
|
s3.create_bucket(Bucket=from_info.bucket)
|
|
_upload_multipart(s3, from_info.bucket, from_info.path)
|
|
S3FileSystem._copy(s3, from_info, to_info, {})
|
|
|
|
|
|
def test_s3_isdir(tmp_dir, dvc, s3):
|
|
s3.gen({"data": {"foo": "foo"}})
|
|
fs = S3FileSystem(dvc, s3.config)
|
|
|
|
assert not fs.isdir(s3 / "data" / "foo")
|
|
assert fs.isdir(s3 / "data")
|
|
|
|
|
|
def test_s3_upload_fobj(tmp_dir, dvc, s3):
|
|
s3.gen({"data": {"foo": "foo"}})
|
|
fs = S3FileSystem(dvc, s3.config)
|
|
|
|
to_info = s3 / "data" / "bar"
|
|
with fs.open(s3 / "data" / "foo", "rb") as stream:
|
|
fs.upload_fobj(stream, to_info, 1)
|
|
|
|
assert to_info.read_text() == "foo"
|
|
|
|
|
|
KB = 1024
|
|
MB = KB ** 2
|
|
GB = KB ** 3
|
|
|
|
|
|
def test_s3_aws_config(tmp_dir, dvc, s3, monkeypatch):
|
|
config_directory = tmp_dir / ".aws"
|
|
config_directory.mkdir()
|
|
(config_directory / "config").write_text(
|
|
textwrap.dedent(
|
|
"""\
|
|
[default]
|
|
s3 =
|
|
max_concurrent_requests = 20000
|
|
max_queue_size = 1000
|
|
multipart_threshold = 1000KiB
|
|
multipart_chunksize = 64MB
|
|
use_accelerate_endpoint = true
|
|
addressing_style = path
|
|
"""
|
|
)
|
|
)
|
|
|
|
if sys.platform == "win32":
|
|
var = "USERPROFILE"
|
|
else:
|
|
var = "HOME"
|
|
monkeypatch.setenv(var, str(tmp_dir))
|
|
|
|
# Fresh import to see the effects of changing HOME variable
|
|
s3_mod = importlib.reload(sys.modules[S3FileSystem.__module__])
|
|
fs = s3_mod.S3FileSystem(dvc, s3.config)
|
|
assert fs._transfer_config is None
|
|
|
|
with fs._get_s3() as s3:
|
|
s3_config = s3.meta.client.meta.config.s3
|
|
assert s3_config["use_accelerate_endpoint"]
|
|
assert s3_config["addressing_style"] == "path"
|
|
|
|
transfer_config = fs._transfer_config
|
|
assert transfer_config.max_io_queue_size == 1000
|
|
assert transfer_config.multipart_chunksize == 64 * MB
|
|
assert transfer_config.multipart_threshold == 1000 * KB
|
|
assert transfer_config.max_request_concurrency == 20000
|
|
|
|
|
|
def test_s3_aws_config_different_profile(tmp_dir, dvc, s3, monkeypatch):
|
|
config_file = tmp_dir / "aws_config.ini"
|
|
config_file.write_text(
|
|
textwrap.dedent(
|
|
"""\
|
|
[default]
|
|
extra = keys
|
|
s3 =
|
|
addressing_style = auto
|
|
use_accelerate_endpoint = true
|
|
multipart_threshold = ThisIsNotGoingToBeCasted!
|
|
[profile dev]
|
|
some_extra = keys
|
|
s3 =
|
|
addresing_style = virtual
|
|
multipart_threshold = 2GiB
|
|
"""
|
|
)
|
|
)
|
|
monkeypatch.setenv("AWS_CONFIG_FILE", config_file)
|
|
|
|
fs = S3FileSystem(dvc, {**s3.config, "profile": "dev"})
|
|
assert fs._transfer_config is None
|
|
|
|
with fs._get_s3() as s3:
|
|
s3_config = s3.meta.client.meta.config.s3
|
|
assert s3_config["addresing_style"] == "virtual"
|
|
assert "use_accelerate_endpoint" not in s3_config
|
|
|
|
transfer_config = fs._transfer_config
|
|
assert transfer_config.multipart_threshold == 2 * GB
|