Files
galaxy/test/unit/objectstore/test_objectstore.py
T
John Chilton c106a87a90 Implement device source ID for object stores.
Allow attaching a device ID on object stores that indicates they produce equivalent paths. Implement an API endpoint that allows swapping datsets between equivalent object stores if they have not been copied by new users.
2024-02-07 11:39:51 -05:00

1287 lines
45 KiB
Python

import os
import time
from tempfile import (
mkdtemp,
mkstemp,
)
from unittest.mock import patch
from uuid import uuid4
import pytest
from galaxy.exceptions import ObjectInvalid
from galaxy.objectstore.azure_blob import AzureBlobObjectStore
from galaxy.objectstore.caching import (
CacheTarget,
check_cache,
InProcessCacheMonitor,
)
from galaxy.objectstore.cloud import Cloud
from galaxy.objectstore.pithos import PithosObjectStore
from galaxy.objectstore.s3 import S3ObjectStore
from galaxy.objectstore.unittest_utils import (
Config as TestConfig,
DISK_TEST_CONFIG,
DISK_TEST_CONFIG_YAML,
)
from galaxy.util import (
directory_hash_id,
unlink,
)
# Unit testing the cloud and advanced infrastructure object stores is difficult, but
# we can at least stub out initializing and test the configuration of these things from
# XML and dicts.
class UninitializedPithosObjectStore(PithosObjectStore):
def _initialize(self):
pass
class UninitializedS3ObjectStore(S3ObjectStore):
def _initialize(self):
pass
class UninitializedAzureBlobObjectStore(AzureBlobObjectStore):
def _initialize(self):
pass
class UninitializedCloudObjectStore(Cloud):
def _initialize(self):
pass
def test_unlink_path():
with pytest.raises(FileNotFoundError):
unlink(uuid4().hex)
unlink(uuid4().hex, ignore_errors=True)
fd, path = mkstemp()
os.close(fd)
assert os.path.exists(path)
unlink(path)
assert not os.path.exists(path)
def test_disk_store():
for config_str in [DISK_TEST_CONFIG, DISK_TEST_CONFIG_YAML]:
with TestConfig(config_str) as (directory, object_store):
# Test no dataset with id 1 exists.
absent_dataset = MockDataset(1)
assert not object_store.exists(absent_dataset)
# Write empty dataset 2 in second backend, ensure it is empty and
# exists.
empty_dataset = MockDataset(2)
directory.write("", "files1/000/dataset_2.dat")
assert object_store.exists(empty_dataset)
assert object_store.empty(empty_dataset)
# Write non-empty dataset in backend 1, test it is not emtpy & exists.
hello_world_dataset = MockDataset(3)
directory.write("Hello World!", "files1/000/dataset_3.dat")
assert object_store.exists(hello_world_dataset)
assert not object_store.empty(hello_world_dataset)
# Test get_data
data = object_store.get_data(hello_world_dataset)
assert data == "Hello World!"
data = object_store.get_data(hello_world_dataset, start=1, count=6)
assert data == "ello W"
# Test Size
# Test absent and empty datasets yield size of 0.
assert object_store.size(absent_dataset) == 0
assert object_store.size(empty_dataset) == 0
# Elsewise
assert object_store.size(hello_world_dataset) > 0 # Should this always be the number of bytes?
# Test percent used (to some degree)
percent_store_used = object_store.get_store_usage_percent()
assert percent_store_used > 0.0
assert percent_store_used < 100.0
# Test update_from_file test
output_dataset = MockDataset(4)
output_real_path = os.path.join(directory.temp_directory, "files1", "000", "dataset_4.dat")
assert not os.path.exists(output_real_path)
output_working_path = directory.write("NEW CONTENTS", "job_working_directory1/example_output")
object_store.update_from_file(output_dataset, file_name=output_working_path, create=True)
assert os.path.exists(output_real_path)
# Test delete
to_delete_dataset = MockDataset(5)
to_delete_real_path = directory.write("content to be deleted!", "files1/000/dataset_5.dat")
assert object_store.exists(to_delete_dataset)
assert object_store.delete(to_delete_dataset)
assert not object_store.exists(to_delete_dataset)
assert not os.path.exists(to_delete_real_path)
DISK_TEST_CONFIG_BY_UUID_YAML = """
type: disk
files_dir: "${temp_directory}/files1"
store_by: uuid
extra_dirs:
- type: temp
path: "${temp_directory}/tmp1"
- type: job_work
path: "${temp_directory}/job_working_directory1"
"""
def test_disk_store_by_uuid():
for config_str in [DISK_TEST_CONFIG_BY_UUID_YAML]:
with TestConfig(config_str) as (directory, object_store):
# Test no dataset with id 1 exists.
absent_dataset = MockDataset(1)
assert not object_store.exists(absent_dataset)
# Write empty dataset 2 in second backend, ensure it is empty and
# exists.
empty_dataset = MockDataset(2)
directory.write("", f"files1/{empty_dataset.rel_path_for_uuid_test()}/dataset_{empty_dataset.uuid}.dat")
assert object_store.exists(empty_dataset)
assert object_store.empty(empty_dataset)
# Write non-empty dataset in backend 1, test it is not emtpy & exists.
hello_world_dataset = MockDataset(3)
directory.write(
"Hello World!",
f"files1/{hello_world_dataset.rel_path_for_uuid_test()}/dataset_{hello_world_dataset.uuid}.dat",
)
assert object_store.exists(hello_world_dataset)
assert not object_store.empty(hello_world_dataset)
# Test get_data
data = object_store.get_data(hello_world_dataset)
assert data == "Hello World!"
data = object_store.get_data(hello_world_dataset, start=1, count=6)
assert data == "ello W"
# Test Size
# Test absent and empty datasets yield size of 0.
assert object_store.size(absent_dataset) == 0
assert object_store.size(empty_dataset) == 0
# Elsewise
assert object_store.size(hello_world_dataset) > 0 # Should this always be the number of bytes?
# Test percent used (to some degree)
percent_store_used = object_store.get_store_usage_percent()
assert percent_store_used > 0.0
assert percent_store_used < 100.0
# Test update_from_file test
output_dataset = MockDataset(4)
output_real_path = os.path.join(
directory.temp_directory,
"files1",
output_dataset.rel_path_for_uuid_test(),
"dataset_%s.dat" % output_dataset.uuid,
)
assert not os.path.exists(output_real_path)
output_working_path = directory.write("NEW CONTENTS", "job_working_directory1/example_output")
object_store.update_from_file(output_dataset, file_name=output_working_path, create=True)
assert os.path.exists(output_real_path)
# Test delete
to_delete_dataset = MockDataset(5)
to_delete_real_path = directory.write(
"content to be deleted!",
f"files1/{to_delete_dataset.rel_path_for_uuid_test()}/dataset_{to_delete_dataset.uuid}.dat",
)
assert object_store.exists(to_delete_dataset)
assert object_store.delete(to_delete_dataset)
assert not object_store.exists(to_delete_dataset)
assert not os.path.exists(to_delete_real_path)
def test_disk_store_alt_name_relpath():
"""Test that alt_name cannot be used to access arbitrary paths using a
relative path
"""
with TestConfig(DISK_TEST_CONFIG) as (directory, object_store):
empty_dataset = MockDataset(1)
directory.write("", "files1/000/dataset_1.dat")
directory.write("foo", "foo.txt")
try:
assert (
object_store.get_data(empty_dataset, extra_dir="dataset_1_files", alt_name="../../../foo.txt") != "foo"
)
except ObjectInvalid:
pass
def test_disk_store_alt_name_abspath():
"""Test that alt_name cannot be used to access arbitrary paths using a
absolute path
"""
with TestConfig(DISK_TEST_CONFIG) as (directory, object_store):
empty_dataset = MockDataset(1)
directory.write("", "files1/000/dataset_1.dat")
absfoo = os.path.abspath(os.path.join(directory.temp_directory, "foo.txt"))
with open(absfoo, "w") as f:
f.write("foo")
try:
assert object_store.get_data(empty_dataset, extra_dir="dataset_1_files", alt_name=absfoo) != "foo"
except ObjectInvalid:
pass
HIERARCHICAL_TEST_CONFIG = """<?xml version="1.0"?>
<object_store type="hierarchical">
<backends>
<backend id="files1" type="disk" weight="1" order="0" name="Newer Cool Storage">
<description>
This is our new storage cluster, check out the storage
on our institute's system page for [Fancy New Storage](http://computecenter.example.com/systems/fancystorage).
</description>
<files_dir path="${temp_directory}/files1"/>
<extra_dir type="temp" path="${temp_directory}/tmp1"/>
<extra_dir type="job_work" path="${temp_directory}/job_working_directory1"/>
</backend>
<backend id="files2" type="disk" weight="1" order="1" name="Older Legacy Storage">
<description>
This is our older legacy storage cluster, check out the storage
on our institute's system page for [Legacy Storage](http://computecenter.example.com/systems/legacystorage).
</description>
<files_dir path="${temp_directory}/files2"/>
<extra_dir type="temp" path="${temp_directory}/tmp2"/>
<extra_dir type="job_work" path="${temp_directory}/job_working_directory2"/>
</backend>
</backends>
</object_store>
"""
HIERARCHICAL_TEST_CONFIG_YAML = """
type: hierarchical
backends:
- id: files1
name: Newer Cool Storage
description: |
This is our new storage cluster, check out the storage
on our institute's system page for [Fancy New Storage](http://computecenter.example.com/systems/fancystorage).
type: disk
weight: 1
files_dir: "${temp_directory}/files1"
extra_dirs:
- type: temp
path: "${temp_directory}/tmp1"
- type: job_work
path: "${temp_directory}/job_working_directory1"
- id: files2
name: Older Legacy Storage
description: |
This is our older legacy storage cluster, check out the storage
on our institute's system page for [Legacy Storage](http://computecenter.example.com/systems/legacystorage).
type: disk
weight: 1
files_dir: "${temp_directory}/files2"
extra_dirs:
- type: temp
path: "${temp_directory}/tmp2"
- type: job_work
path: "${temp_directory}/job_working_directory2"
"""
def test_hierarchical_store():
for config_str in [HIERARCHICAL_TEST_CONFIG, HIERARCHICAL_TEST_CONFIG_YAML]:
with TestConfig(config_str) as (directory, object_store):
# Test no dataset with id 1 exists.
assert not object_store.exists(MockDataset(1))
# Write empty dataset 2 in second backend, ensure it is empty and
# exists.
directory.write("", "files2/000/dataset_2.dat")
assert object_store.exists(MockDataset(2))
assert object_store.empty(MockDataset(2))
# Write non-empty dataset in backend 1, test it is not empty & exists.
directory.write("Hello World!", "files1/000/dataset_3.dat")
assert object_store.exists(MockDataset(3))
assert not object_store.empty(MockDataset(3))
# check and description routed correctly
files1_desc = object_store.get_concrete_store_description_markdown(MockDataset(3))
files1_name = object_store.get_concrete_store_name(MockDataset(3))
files2_desc = object_store.get_concrete_store_description_markdown(MockDataset(2))
files2_name = object_store.get_concrete_store_name(MockDataset(2))
assert "fancy" in files1_desc
assert "Newer Cool" in files1_name
assert "older" in files2_desc
assert "Legacy" in files2_name
# Assert creation always happens in first backend.
for i in range(100):
dataset = MockDataset(100 + i)
object_store.create(dataset)
assert object_store.get_filename(dataset).find("files1") > 0
as_dict = object_store.to_dict()
_assert_has_keys(as_dict, ["backends", "extra_dirs", "type"])
_assert_key_has_value(as_dict, "type", "hierarchical")
def test_concrete_name_without_objectstore_id():
for config_str in [HIERARCHICAL_TEST_CONFIG, HIERARCHICAL_TEST_CONFIG_YAML]:
with TestConfig(config_str) as (directory, object_store):
files1_desc = object_store.get_concrete_store_description_markdown(MockDataset(3))
files1_name = object_store.get_concrete_store_name(MockDataset(3))
assert files1_desc is None
assert files1_name is None
MIXED_STORE_BY_DISTRIBUTED_TEST_CONFIG = """<?xml version="1.0"?>
<object_store type="distributed">
<backends>
<backend id="files1" type="disk" weight="1" order="0" store_by="id">
<files_dir path="${temp_directory}/files1"/>
<extra_dir type="temp" path="${temp_directory}/tmp1"/>
<extra_dir type="job_work" path="${temp_directory}/job_working_directory1"/>
</backend>
<backend id="files2" type="disk" weight="1" order="1" store_by="uuid" private="true">
<files_dir path="${temp_directory}/files2"/>
<extra_dir type="temp" path="${temp_directory}/tmp2"/>
<extra_dir type="job_work" path="${temp_directory}/job_working_directory2"/>
</backend>
</backends>
</object_store>
"""
MIXED_STORE_BY_HIERARCHICAL_TEST_CONFIG = """<?xml version="1.0"?>
<object_store type="hierarchical" private="true">
<backends>
<backend id="files1" type="disk" weight="1" order="0" store_by="id">
<files_dir path="${temp_directory}/files1"/>
<extra_dir type="temp" path="${temp_directory}/tmp1"/>
<extra_dir type="job_work" path="${temp_directory}/job_working_directory1"/>
</backend>
<backend id="files2" type="disk" weight="1" order="1" store_by="uuid">
<files_dir path="${temp_directory}/files2"/>
<extra_dir type="temp" path="${temp_directory}/tmp2"/>
<extra_dir type="job_work" path="${temp_directory}/job_working_directory2"/>
</backend>
</backends>
</object_store>
"""
def test_mixed_store_by():
with TestConfig(MIXED_STORE_BY_DISTRIBUTED_TEST_CONFIG) as (directory, object_store):
as_dict = object_store.to_dict()
assert as_dict["backends"][0]["store_by"] == "id"
assert as_dict["backends"][1]["store_by"] == "uuid"
with TestConfig(MIXED_STORE_BY_HIERARCHICAL_TEST_CONFIG) as (directory, object_store):
as_dict = object_store.to_dict()
assert as_dict["backends"][0]["store_by"] == "id"
assert as_dict["backends"][1]["store_by"] == "uuid"
def test_mixed_private():
# Distributed object store can combine private and non-private concrete objectstores
with TestConfig(MIXED_STORE_BY_DISTRIBUTED_TEST_CONFIG) as (directory, object_store):
ids = object_store.object_store_ids()
print(ids)
assert len(ids) == 2
ids = object_store.object_store_ids(private=True)
assert len(ids) == 1
assert ids[0] == "files2"
ids = object_store.object_store_ids(private=False)
assert len(ids) == 1
assert ids[0] == "files1"
as_dict = object_store.to_dict()
assert not as_dict["backends"][0]["private"]
assert as_dict["backends"][1]["private"]
with TestConfig(MIXED_STORE_BY_HIERARCHICAL_TEST_CONFIG) as (directory, object_store):
as_dict = object_store.to_dict()
assert as_dict["backends"][0]["private"]
assert as_dict["backends"][1]["private"]
assert object_store.private
assert as_dict["private"] is True
def test_empty_cache_targets_for_disk_nested_stores():
with TestConfig(MIXED_STORE_BY_DISTRIBUTED_TEST_CONFIG) as (directory, object_store):
assert len(object_store.cache_targets()) == 0
with TestConfig(MIXED_STORE_BY_HIERARCHICAL_TEST_CONFIG) as (directory, object_store):
assert len(object_store.cache_targets()) == 0
BADGES_TEST_1_CONFIG_XML = """<?xml version="1.0"?>
<object_store type="disk">
<files_dir path="${temp_directory}/files1"/>
<extra_dir type="temp" path="${temp_directory}/tmp1"/>
<extra_dir type="job_work" path="${temp_directory}/job_working_directory1"/>
<badges>
<short_term />
<faster>Fast interconnects.</faster>
<less_stable />
<more_secure />
<backed_up>Storage is backed up to tape nightly.</backed_up>
</badges>
</object_store>
"""
BADGES_TEST_1_CONFIG_YAML = """
type: disk
files_dir: "${temp_directory}/files1"
store_by: uuid
extra_dirs:
- type: temp
path: "${temp_directory}/tmp1"
- type: job_work
path: "${temp_directory}/job_working_directory1"
badges:
- type: short_term
- type: faster
message: Fast interconnects.
- type: less_stable
- type: more_secure
- type: backed_up
message: Storage is backed up to tape nightly.
"""
def test_badges_parsing():
for config_str in [BADGES_TEST_1_CONFIG_XML, BADGES_TEST_1_CONFIG_YAML]:
with TestConfig(config_str) as (directory, object_store):
badges = object_store.to_dict()["badges"]
assert len(badges) == 6
badge_1 = badges[0]
assert badge_1["type"] == "short_term"
assert badge_1["message"] is None
badge_2 = badges[1]
assert badge_2["type"] == "faster"
assert badge_2["message"] == "Fast interconnects."
badge_3 = badges[2]
assert badge_3["type"] == "less_stable"
assert badge_3["message"] is None
badge_4 = badges[3]
assert badge_4["type"] == "more_secure"
assert badge_4["message"] is None
BADGES_TEST_CONFLICTS_1_CONFIG_YAML = """
type: disk
files_dir: "${temp_directory}/files1"
badges:
- type: slower
- type: faster
"""
BADGES_TEST_CONFLICTS_2_CONFIG_YAML = """
type: disk
files_dir: "${temp_directory}/files1"
badges:
- type: more_secure
- type: less_secure
"""
def test_badges_parsing_conflicts():
for config_str in [BADGES_TEST_CONFLICTS_1_CONFIG_YAML]:
exception_raised = False
try:
with TestConfig(config_str) as (directory, object_store):
pass
except Exception as e:
assert "faster" in str(e)
assert "slower" in str(e)
exception_raised = True
assert exception_raised
for config_str in [BADGES_TEST_CONFLICTS_2_CONFIG_YAML]:
exception_raised = False
try:
with TestConfig(config_str) as (directory, object_store):
pass
except Exception as e:
assert "more_secure" in str(e)
assert "less_secure" in str(e)
exception_raised = True
assert exception_raised
DISTRIBUTED_TEST_CONFIG = """<?xml version="1.0"?>
<object_store type="distributed">
<backends>
<backend id="files1" type="disk" weight="2" device="primary_disk">
<quota source="1files" />
<files_dir path="${temp_directory}/files1"/>
<extra_dir type="temp" path="${temp_directory}/tmp1"/>
<extra_dir type="job_work" path="${temp_directory}/job_working_directory1"/>
</backend>
<backend id="files2" type="disk" weight="1" device="primary_disk">
<quota source="2files" />
<files_dir path="${temp_directory}/files2"/>
<extra_dir type="temp" path="${temp_directory}/tmp2"/>
<extra_dir type="job_work" path="${temp_directory}/job_working_directory2"/>
</backend>
</backends>
</object_store>
"""
DISTRIBUTED_TEST_CONFIG_YAML = """
type: distributed
backends:
- id: files1
quota:
source: 1files
type: disk
weight: 2
device: primary_disk
files_dir: "${temp_directory}/files1"
extra_dirs:
- type: temp
path: "${temp_directory}/tmp1"
- type: job_work
path: "${temp_directory}/job_working_directory1"
- id: files2
quota:
source: 2files
type: disk
weight: 1
device: primary_disk
files_dir: "${temp_directory}/files2"
extra_dirs:
- type: temp
path: "${temp_directory}/tmp2"
- type: job_work
path: "${temp_directory}/job_working_directory2"
"""
def test_distributed_store():
for config_str in [DISTRIBUTED_TEST_CONFIG, DISTRIBUTED_TEST_CONFIG_YAML]:
with TestConfig(config_str) as (directory, object_store):
persisted_ids = []
for i in range(100):
dataset = MockDataset(100 + i)
object_store.create(dataset)
persisted_ids.append(dataset.object_store_id)
# Test distributes datasets between backends according to weights
backend_1_count = sum(1 for v in persisted_ids if v == "files1")
backend_2_count = sum(1 for v in persisted_ids if v == "files2")
assert backend_1_count > 0
assert backend_2_count > 0
assert backend_1_count > backend_2_count
as_dict = object_store.to_dict()
_assert_has_keys(as_dict, ["backends", "extra_dirs", "type"])
_assert_key_has_value(as_dict, "type", "distributed")
backends = as_dict["backends"]
assert len(backends)
assert backends[0]["quota"]["source"] == "1files"
assert backends[1]["quota"]["source"] == "2files"
extra_dirs = as_dict["extra_dirs"]
assert len(extra_dirs) == 2
device_source_map = object_store.get_device_source_map()
assert device_source_map
print(device_source_map.backends)
assert device_source_map.get_device_id("files1") == "primary_disk"
assert device_source_map.get_device_id("files2") == "primary_disk"
def test_distributed_store_empty_cache_targets():
for config_str in [DISTRIBUTED_TEST_CONFIG, DISTRIBUTED_TEST_CONFIG_YAML]:
with TestConfig(config_str) as (directory, object_store):
assert len(object_store.cache_targets()) == 0
DISTRIBUTED_TEST_S3_CONFIG_YAML = """
type: distributed
backends:
- id: files1
weight: 1
type: s3
auth:
access_key: access_moo
secret_key: secret_cow
bucket:
name: unique_bucket_name_all_lowercase
use_reduced_redundancy: false
extra_dirs:
- type: job_work
path: ${temp_directory}/job_working_directory_s3
- type: temp
path: ${temp_directory}/tmp_s3
- id: files2
weight: 1
type: s3
auth:
access_key: access_moo
secret_key: secret_cow
bucket:
name: unique_bucket_name_all_lowercase_2
use_reduced_redundancy: false
extra_dirs:
- type: job_work
path: ${temp_directory}/job_working_directory_s3_2
- type: temp
path: ${temp_directory}/tmp_s3_2
"""
@patch("galaxy.objectstore.s3.S3ObjectStore", UninitializedS3ObjectStore)
def test_distributed_store_with_cache_targets():
for config_str in [DISTRIBUTED_TEST_S3_CONFIG_YAML]:
with TestConfig(config_str) as (directory, object_store):
assert len(object_store.cache_targets()) == 2
HIERARCHICAL_MUST_HAVE_UNIFIED_QUOTA_SOURCE = """<?xml version="1.0"?>
<object_store type="hierarchical" private="true">
<backends>
<backend type="disk" weight="1" order="0" store_by="id">
<quota source="1files" /> <!-- Cannot do this here, only in distributedobjectstore -->
<files_dir path="${temp_directory}/files1"/>
<extra_dir type="temp" path="${temp_directory}/tmp1"/>
<extra_dir type="job_work" path="${temp_directory}/job_working_directory1"/>
</backend>
<backend type="disk" weight="1" order="1" store_by="uuid">
<files_dir path="${temp_directory}/files2"/>
<extra_dir type="temp" path="${temp_directory}/tmp2"/>
<extra_dir type="job_work" path="${temp_directory}/job_working_directory2"/>
</backend>
</backends>
</object_store>
"""
def test_hiercachical_backend_must_share_quota_source():
the_exception = None
for config_str in [HIERARCHICAL_MUST_HAVE_UNIFIED_QUOTA_SOURCE]:
try:
with TestConfig(config_str) as (directory, object_store):
pass
except Exception as e:
the_exception = e
assert the_exception is not None
PITHOS_TEST_CONFIG = """<?xml version="1.0"?>
<object_store type="pithos">
<auth url="http://example.org/" token="extoken123" />
<container name="foo" project="cow" />
<extra_dir type="temp" path="database/tmp_pithos"/>
<extra_dir type="job_work" path="database/working_pithos"/>
</object_store>
"""
PITHOS_TEST_CONFIG_YAML = """
type: pithos
auth:
url: http://example.org/
token: extoken123
container:
name: foo
project: cow
extra_dirs:
- type: temp
path: database/tmp_pithos
- type: job_work
path: database/working_pithos
"""
def test_config_parse_pithos():
for config_str in [PITHOS_TEST_CONFIG, PITHOS_TEST_CONFIG_YAML]:
with TestConfig(config_str, clazz=UninitializedPithosObjectStore) as (directory, object_store):
configured_config_dict = object_store.config_dict
_assert_has_keys(configured_config_dict, ["auth", "container", "extra_dirs"])
auth_dict = configured_config_dict["auth"]
_assert_key_has_value(auth_dict, "url", "http://example.org/")
_assert_key_has_value(auth_dict, "token", "extoken123")
container_dict = configured_config_dict["container"]
_assert_key_has_value(container_dict, "name", "foo")
_assert_key_has_value(container_dict, "project", "cow")
assert object_store.extra_dirs["job_work"] == "database/working_pithos"
assert object_store.extra_dirs["temp"] == "database/tmp_pithos"
as_dict = object_store.to_dict()
_assert_has_keys(as_dict, ["auth", "container", "extra_dirs", "type"])
_assert_key_has_value(as_dict, "type", "pithos")
auth_dict = as_dict["auth"]
_assert_key_has_value(auth_dict, "url", "http://example.org/")
_assert_key_has_value(auth_dict, "token", "extoken123")
container_dict = as_dict["container"]
_assert_key_has_value(container_dict, "name", "foo")
_assert_key_has_value(container_dict, "project", "cow")
extra_dirs = as_dict["extra_dirs"]
assert len(extra_dirs) == 2
S3_TEST_CONFIG = """<object_store type="s3" private="true">
<auth access_key="access_moo" secret_key="secret_cow" />
<bucket name="unique_bucket_name_all_lowercase" use_reduced_redundancy="False" />
<cache path="database/object_store_cache" size="1000" />
<extra_dir type="job_work" path="database/job_working_directory_s3"/>
<extra_dir type="temp" path="database/tmp_s3"/>
</object_store>
"""
S3_TEST_CONFIG_YAML = """
type: s3
private: true
auth:
access_key: access_moo
secret_key: secret_cow
bucket:
name: unique_bucket_name_all_lowercase
use_reduced_redundancy: false
cache:
path: database/object_store_cache
size: 1000
extra_dirs:
- type: job_work
path: database/job_working_directory_s3
- type: temp
path: database/tmp_s3
"""
def test_config_parse_s3():
for config_str in [S3_TEST_CONFIG, S3_TEST_CONFIG_YAML]:
with TestConfig(config_str, clazz=UninitializedS3ObjectStore) as (directory, object_store):
assert object_store.private
assert object_store.access_key == "access_moo"
assert object_store.secret_key == "secret_cow"
assert object_store.bucket == "unique_bucket_name_all_lowercase"
assert object_store.use_rr is False
assert object_store.host is None
assert object_store.port == 6000
assert object_store.multipart is True
assert object_store.is_secure is True
assert object_store.conn_path == "/"
cache_target = object_store.cache_target
assert cache_target.size == 1000
assert cache_target.path == "database/object_store_cache"
assert object_store.extra_dirs["job_work"] == "database/job_working_directory_s3"
assert object_store.extra_dirs["temp"] == "database/tmp_s3"
as_dict = object_store.to_dict()
_assert_has_keys(as_dict, ["auth", "bucket", "connection", "cache", "extra_dirs", "type"])
_assert_key_has_value(as_dict, "type", "aws_s3")
auth_dict = as_dict["auth"]
bucket_dict = as_dict["bucket"]
connection_dict = as_dict["connection"]
cache_dict = as_dict["cache"]
_assert_key_has_value(auth_dict, "access_key", "access_moo")
_assert_key_has_value(auth_dict, "secret_key", "secret_cow")
_assert_key_has_value(bucket_dict, "name", "unique_bucket_name_all_lowercase")
_assert_key_has_value(bucket_dict, "use_reduced_redundancy", False)
_assert_key_has_value(connection_dict, "host", None)
_assert_key_has_value(connection_dict, "port", 6000)
_assert_key_has_value(connection_dict, "multipart", True)
_assert_key_has_value(connection_dict, "is_secure", True)
_assert_key_has_value(cache_dict, "size", 1000)
_assert_key_has_value(cache_dict, "path", "database/object_store_cache")
extra_dirs = as_dict["extra_dirs"]
assert len(extra_dirs) == 2
S3_DEFAULT_CACHE_TEST_CONFIG = """<object_store type="s3" private="true">
<auth access_key="access_moo" secret_key="secret_cow" />
<bucket name="unique_bucket_name_all_lowercase" use_reduced_redundancy="False" />
<extra_dir type="job_work" path="database/job_working_directory_s3"/>
<extra_dir type="temp" path="database/tmp_s3"/>
</object_store>
"""
S3_DEFAULT_CACHE_TEST_CONFIG_YAML = """
type: s3
private: true
auth:
access_key: access_moo
secret_key: secret_cow
bucket:
name: unique_bucket_name_all_lowercase
use_reduced_redundancy: false
extra_dirs:
- type: job_work
path: database/job_working_directory_s3
- type: temp
path: database/tmp_s3
"""
def test_config_parse_s3_with_default_cache():
for config_str in [S3_DEFAULT_CACHE_TEST_CONFIG, S3_DEFAULT_CACHE_TEST_CONFIG_YAML]:
with TestConfig(config_str, clazz=UninitializedS3ObjectStore) as (directory, object_store):
assert object_store.cache_size == -1
assert object_store.staging_path == directory.global_config.object_store_cache_path
CLOUD_AWS_TEST_CONFIG = """<object_store type="cloud" provider="aws">
<auth access_key="access_moo" secret_key="secret_cow" />
<bucket name="unique_bucket_name_all_lowercase" use_reduced_redundancy="False" />
<cache path="database/object_store_cache" size="1000" />
<extra_dir type="job_work" path="database/job_working_directory_cloud"/>
<extra_dir type="temp" path="database/tmp_cloud"/>
</object_store>
"""
CLOUD_AWS_TEST_CONFIG_YAML = """
type: cloud
provider: aws
auth:
access_key: access_moo
secret_key: secret_cow
bucket:
name: unique_bucket_name_all_lowercase
use_reduced_redundancy: false
cache:
path: database/object_store_cache
size: 1000
extra_dirs:
- type: job_work
path: database/job_working_directory_cloud
- type: temp
path: database/tmp_cloud
"""
CLOUD_AZURE_TEST_CONFIG = """<object_store type="cloud" provider="azure">
<auth subscription_id="a_sub_id" client_id="and_a_client_id" secret="and_a_secret_key"
tenant="and_some_tenant_info" />
<bucket name="unique_bucket_name_all_lowercase" use_reduced_redundancy="False" />
<cache path="database/object_store_cache" size="1000" />
<extra_dir type="job_work" path="database/job_working_directory_cloud"/>
<extra_dir type="temp" path="database/tmp_cloud"/>
</object_store>
"""
CLOUD_AZURE_TEST_CONFIG_YAML = """
type: cloud
provider: azure
auth:
subscription_id: a_sub_id
client_id: and_a_client_id
secret: and_a_secret_key
tenant: and_some_tenant_info
bucket:
name: unique_bucket_name_all_lowercase
use_reduced_redundancy: false
cache:
path: database/object_store_cache
size: 1000
extra_dirs:
- type: job_work
path: database/job_working_directory_cloud
- type: temp
path: database/tmp_cloud
"""
CLOUD_GOOGLE_TEST_CONFIG = """<object_store type="cloud" provider="google">
<auth credentials_file="gcp.config" />
<bucket name="unique_bucket_name_all_lowercase" use_reduced_redundancy="False" />
<cache path="database/object_store_cache" size="1000" />
<extra_dir type="job_work" path="database/job_working_directory_cloud"/>
<extra_dir type="temp" path="database/tmp_cloud"/>
</object_store>
"""
CLOUD_GOOGLE_TEST_CONFIG_YAML = """
type: cloud
provider: google
auth:
credentials_file: gcp.config
bucket:
name: unique_bucket_name_all_lowercase
use_reduced_redundancy: false
cache:
path: database/object_store_cache
size: 1000
extra_dirs:
- type: job_work
path: database/job_working_directory_cloud
- type: temp
path: database/tmp_cloud
"""
def test_config_parse_cloud():
for config_str in [
CLOUD_AWS_TEST_CONFIG,
CLOUD_AWS_TEST_CONFIG_YAML,
CLOUD_AZURE_TEST_CONFIG,
CLOUD_AZURE_TEST_CONFIG_YAML,
CLOUD_GOOGLE_TEST_CONFIG,
CLOUD_GOOGLE_TEST_CONFIG_YAML,
]:
if "google" in config_str:
tmpdir = mkdtemp()
if not os.path.exists(tmpdir):
os.makedirs(tmpdir)
path = os.path.join(tmpdir, "gcp.config")
open(path, "w").write("some_gcp_config")
config_str = config_str.replace("gcp.config", path)
with TestConfig(config_str, clazz=UninitializedCloudObjectStore) as (directory, object_store):
assert object_store.bucket_name == "unique_bucket_name_all_lowercase"
assert object_store.use_rr is False
assert object_store.host is None
assert object_store.port == 6000
assert object_store.multipart is True
assert object_store.is_secure is True
assert object_store.conn_path == "/"
cache_target = object_store.cache_target
assert cache_target.size == 1000.0
assert cache_target.path == "database/object_store_cache"
assert object_store.extra_dirs["job_work"] == "database/job_working_directory_cloud"
assert object_store.extra_dirs["temp"] == "database/tmp_cloud"
as_dict = object_store.to_dict()
_assert_has_keys(as_dict, ["provider", "auth", "bucket", "connection", "cache", "extra_dirs", "type"])
_assert_key_has_value(as_dict, "type", "cloud")
auth_dict = as_dict["auth"]
bucket_dict = as_dict["bucket"]
connection_dict = as_dict["connection"]
cache_dict = as_dict["cache"]
provider = as_dict["provider"]
if provider == "aws":
_assert_key_has_value(auth_dict, "access_key", "access_moo")
_assert_key_has_value(auth_dict, "secret_key", "secret_cow")
elif provider == "azure":
_assert_key_has_value(auth_dict, "subscription_id", "a_sub_id")
_assert_key_has_value(auth_dict, "client_id", "and_a_client_id")
_assert_key_has_value(auth_dict, "secret", "and_a_secret_key")
_assert_key_has_value(auth_dict, "tenant", "and_some_tenant_info")
elif provider == "google":
_assert_key_has_value(auth_dict, "credentials_file", path)
_assert_key_has_value(bucket_dict, "name", "unique_bucket_name_all_lowercase")
_assert_key_has_value(bucket_dict, "use_reduced_redundancy", False)
_assert_key_has_value(connection_dict, "host", None)
_assert_key_has_value(connection_dict, "port", 6000)
_assert_key_has_value(connection_dict, "multipart", True)
_assert_key_has_value(connection_dict, "is_secure", True)
_assert_key_has_value(cache_dict, "size", 1000.0)
_assert_key_has_value(cache_dict, "path", "database/object_store_cache")
extra_dirs = as_dict["extra_dirs"]
assert len(extra_dirs) == 2
CLOUD_AWS_NO_AUTH_TEST_CONFIG = """<object_store type="cloud" provider="aws">
<auth />
<bucket name="unique_bucket_name_all_lowercase" use_reduced_redundancy="False" />
<cache path="database/object_store_cache" size="1000" />
<extra_dir type="job_work" path="database/job_working_directory_cloud"/>
<extra_dir type="temp" path="database/tmp_cloud"/>
</object_store>
"""
def test_config_parse_cloud_noauth_for_aws():
for config_str in [CLOUD_AWS_NO_AUTH_TEST_CONFIG]:
with TestConfig(config_str, clazz=UninitializedCloudObjectStore) as (directory, object_store):
assert object_store.bucket_name == "unique_bucket_name_all_lowercase"
assert object_store.use_rr is False
assert object_store.host is None
assert object_store.port == 6000
assert object_store.multipart is True
assert object_store.is_secure is True
assert object_store.conn_path == "/"
cache_target = object_store.cache_target
assert cache_target.size == 1000.0
assert cache_target.path == "database/object_store_cache"
assert object_store.extra_dirs["job_work"] == "database/job_working_directory_cloud"
assert object_store.extra_dirs["temp"] == "database/tmp_cloud"
as_dict = object_store.to_dict()
_assert_has_keys(as_dict, ["provider", "auth", "bucket", "connection", "cache", "extra_dirs", "type"])
_assert_key_has_value(as_dict, "type", "cloud")
auth_dict = as_dict["auth"]
bucket_dict = as_dict["bucket"]
connection_dict = as_dict["connection"]
cache_dict = as_dict["cache"]
provider = as_dict["provider"]
assert provider == "aws"
print(auth_dict["access_key"])
_assert_key_has_value(auth_dict, "access_key", None)
_assert_key_has_value(auth_dict, "secret_key", None)
_assert_key_has_value(bucket_dict, "name", "unique_bucket_name_all_lowercase")
_assert_key_has_value(bucket_dict, "use_reduced_redundancy", False)
_assert_key_has_value(connection_dict, "host", None)
_assert_key_has_value(connection_dict, "port", 6000)
_assert_key_has_value(connection_dict, "multipart", True)
_assert_key_has_value(connection_dict, "is_secure", True)
_assert_key_has_value(cache_dict, "size", 1000.0)
_assert_key_has_value(cache_dict, "path", "database/object_store_cache")
extra_dirs = as_dict["extra_dirs"]
assert len(extra_dirs) == 2
CLOUD_AWS_NO_CACHE_TEST_CONFIG = """<object_store type="cloud" provider="aws">
<auth />
<bucket name="unique_bucket_name_all_lowercase" use_reduced_redundancy="False" />
<extra_dir type="job_work" path="database/job_working_directory_cloud"/>
<extra_dir type="temp" path="database/tmp_cloud"/>
</object_store>
"""
def test_config_parse_cloud_no_cache_for_aws():
for config_str in [CLOUD_AWS_NO_CACHE_TEST_CONFIG]:
with TestConfig(config_str, clazz=UninitializedCloudObjectStore) as (directory, object_store):
assert object_store.staging_path == directory.global_config.object_store_cache_path
assert object_store.cache_size == -1
AZURE_BLOB_TEST_CONFIG = """<object_store type="azure_blob">
<auth account_name="azureact" account_key="password123" />
<container name="unique_container_name" max_chunk_size="250"/>
<cache path="database/object_store_cache" size="100" />
<extra_dir type="job_work" path="database/job_working_directory_azure"/>
<extra_dir type="temp" path="database/tmp_azure"/>
</object_store>
"""
AZURE_BLOB_TEST_CONFIG_YAML = """
type: azure_blob
auth:
account_name: azureact
account_key: password123
container:
name: unique_container_name
max_chunk_size: 250
cache:
path: database/object_store_cache
size: 100
extra_dirs:
- type: job_work
path: database/job_working_directory_azure
- type: temp
path: database/tmp_azure
"""
def test_config_parse_azure():
for config_str in [AZURE_BLOB_TEST_CONFIG, AZURE_BLOB_TEST_CONFIG_YAML]:
with TestConfig(config_str, clazz=UninitializedAzureBlobObjectStore) as (directory, object_store):
assert object_store.account_name == "azureact"
assert object_store.account_key == "password123"
assert object_store.container_name == "unique_container_name"
assert object_store.max_chunk_size == 250
cache_target = object_store.cache_target
assert cache_target.size == 100
assert cache_target.path == "database/object_store_cache"
assert object_store.extra_dirs["job_work"] == "database/job_working_directory_azure"
assert object_store.extra_dirs["temp"] == "database/tmp_azure"
as_dict = object_store.to_dict()
_assert_has_keys(as_dict, ["auth", "container", "cache", "extra_dirs", "type"])
_assert_key_has_value(as_dict, "type", "azure_blob")
auth_dict = as_dict["auth"]
container_dict = as_dict["container"]
cache_dict = as_dict["cache"]
_assert_key_has_value(auth_dict, "account_name", "azureact")
_assert_key_has_value(auth_dict, "account_key", "password123")
_assert_key_has_value(container_dict, "name", "unique_container_name")
_assert_key_has_value(container_dict, "max_chunk_size", 250)
_assert_key_has_value(cache_dict, "size", 100)
_assert_key_has_value(cache_dict, "path", "database/object_store_cache")
extra_dirs = as_dict["extra_dirs"]
assert len(extra_dirs) == 2
def test_cache_monitor_thread(tmp_path):
cache_dir = tmp_path
path = cache_dir / "a_file_0"
path.write_text("this is an example file")
cache_target = CacheTarget(cache_dir, 1, 0.000000001)
monitor = InProcessCacheMonitor(cache_target, 30, 0)
path_cleaned = False
for _ in range(100):
time.sleep(0.1)
path_cleaned = not path.exists()
if path_cleaned:
break
monitor.shutdown()
assert path_cleaned
# just verify things cleaned up okay also
assert not monitor.cache_monitor_thread.is_alive()
assert monitor.stop_cache_monitor_event.is_set()
def test_check_cache_sanity(tmp_path):
# sanity check the caching code - create a 1 gig cache with a single file.
# when the cache is allowed to be 20% full the file will exist but when the
# cache is only allowed to be a very small fraction full headed toward zero
# the file will be deleted
cache_dir = tmp_path
path = cache_dir / "a_file_0"
path.write_text("this is an example file")
big_cache_target = CacheTarget(cache_dir, 1, 0.2)
check_cache(big_cache_target)
assert path.exists()
small_cache_target = CacheTarget(cache_dir, 1, 0.000000001)
check_cache(small_cache_target)
assert not path.exists()
def test_fits_in_cache_check(tmp_path):
cache_dir = tmp_path
big_cache_target = CacheTarget(cache_dir, 1, 0.2)
assert not big_cache_target.fits_in_cache(int(1024 * 1024 * 1024 * 0.3))
assert big_cache_target.fits_in_cache(int(1024 * 1024 * 1024 * 0.1))
noop_cache_target = CacheTarget(cache_dir, -1, 0.2)
assert noop_cache_target.fits_in_cache(1024 * 1024 * 1024 * 100)
AZURE_BLOB_NO_CACHE_TEST_CONFIG = """<object_store type="azure_blob">
<auth account_name="azureact" account_key="password123" />
<container name="unique_container_name" max_chunk_size="250"/>
<extra_dir type="job_work" path="database/job_working_directory_azure"/>
<extra_dir type="temp" path="database/tmp_azure"/>
</object_store>
"""
AZURE_BLOB_NO_CACHE_TEST_CONFIG_YAML = """
type: azure_blob
auth:
account_name: azureact
account_key: password123
container:
name: unique_container_name
max_chunk_size: 250
extra_dirs:
- type: job_work
path: database/job_working_directory_azure
- type: temp
path: database/tmp_azure
"""
def test_config_parse_azure_no_cache():
for config_str in [AZURE_BLOB_NO_CACHE_TEST_CONFIG, AZURE_BLOB_NO_CACHE_TEST_CONFIG_YAML]:
with TestConfig(config_str, clazz=UninitializedAzureBlobObjectStore) as (directory, object_store):
assert object_store.cache_size == -1
assert object_store.staging_path == directory.global_config.object_store_cache_path
class MockDataset:
def __init__(self, id):
self.id = id
self.object_store_id = None
self.uuid = uuid4()
self.tags = []
def rel_path_for_uuid_test(self):
rel_path = os.path.join(*directory_hash_id(self.uuid))
return rel_path
def _assert_has_keys(the_dict, keys):
for key in keys:
assert key in the_dict, f"key [{key}] not in [{the_dict}]"
def _assert_key_has_value(the_dict, key, value):
assert key in the_dict, f"dict [{key}] doesn't container expected key [{the_dict}]"
assert the_dict[key] == value, f"{the_dict[key]} != {value}"