Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES/6926.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Taught pulp-import-export to work in a domain-enabled environment.

All combinations of domain-state between upstream and downstream are handled.
4 changes: 0 additions & 4 deletions docs/user/guides/create-domains.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,6 @@ pulp user role-assignment add --username <username> --role <role_name> --domain
e.g. You can not add content from one domain to the repository of another domain even if you own both domains.


!!! warning
Pulp Export and Import are currently not supported with domains enabled.


There are notable objects in Pulp like `Roles`, `Users`, and `Groups`, that are not a part of domains and remain global across the system.
These objects are closely intertwined with the RBAC system and currently do not make sense to be unique on the domain level.
Objects that are not a part of a domain are readable from any domain (with the correct permissions),
Expand Down
220 changes: 220 additions & 0 deletions pulp_file/tests/functional/api/test_filesystem_export.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import json
import pytest
import uuid

from pulpcore.client.pulpcore.exceptions import ApiException, BadRequestException
from pulpcore.app import settings
from pulpcore.constants import TASK_STATES


pytestmark = [
pytest.mark.skipif(
"/tmp" not in settings.ALLOWED_EXPORT_PATHS,
reason="Cannot run export-tests unless /tmp is in ALLOWED_EXPORT_PATHS "
f"({settings.ALLOWED_EXPORT_PATHS}).",
),
]


@pytest.fixture
def fs_exporter_factory(
tmpdir,
pulpcore_bindings,
gen_object_with_cleanup,
add_to_filesystem_cleanup,
):
def _fs_exporter_factory(method="write", pulp_domain=None):
name = str(uuid.uuid4())
path = "{}/{}/".format(tmpdir, name)
body = {
"name": name,
"path": path,
"method": method,
}
kwargs = {}
if pulp_domain:
kwargs["pulp_domain"] = pulp_domain
exporter = gen_object_with_cleanup(pulpcore_bindings.ExportersFilesystemApi, body, **kwargs)
add_to_filesystem_cleanup(path)
assert exporter.name == name
assert exporter.path == path
assert exporter.method == method
return exporter

return _fs_exporter_factory


@pytest.fixture
def fs_export_factory(pulpcore_bindings, monitor_task):
def _fs_export_factory(exporter, body):
task = monitor_task(
pulpcore_bindings.ExportersFilesystemExportsApi.create(
exporter.pulp_href, body or {}
).task
)
assert len(task.created_resources) == 1
export = pulpcore_bindings.ExportersFilesystemExportsApi.read(task.created_resources[0])
for report in task.progress_reports:
assert report.state == TASK_STATES.COMPLETED
return export

return _fs_export_factory


@pytest.fixture
def pub_and_repo(
file_repository_factory,
file_bindings,
gen_object_with_cleanup,
random_artifact_factory,
monitor_task,
):
def _pub_and_repo(pulp_domain=None):
random_artifact = random_artifact_factory(pulp_domain=pulp_domain)
repository = file_repository_factory(pulp_domain=pulp_domain)
kwargs = {}
if pulp_domain:
kwargs["pulp_domain"] = pulp_domain
for i in range(2):
monitor_task(
file_bindings.ContentFilesApi.create(
artifact=random_artifact.pulp_href,
relative_path=f"{i}.dat",
repository=repository.pulp_href,
**kwargs,
).task
)
publish_data = file_bindings.FileFilePublication(repository=repository.pulp_href)
publication = gen_object_with_cleanup(
file_bindings.PublicationsFileApi, publish_data, **kwargs
)
return publication, repository

return _pub_and_repo


@pytest.mark.parallel
def test_crud_fsexporter(fs_exporter_factory, pulpcore_bindings, monitor_task):
# READ
exporter = fs_exporter_factory()
exporter_read = pulpcore_bindings.ExportersFilesystemApi.read(exporter.pulp_href)
assert exporter_read.name == exporter.name
assert exporter_read.path == exporter.path

# UPDATE
body = {"path": "/tmp/{}".format(str(uuid.uuid4()))}
result = pulpcore_bindings.ExportersFilesystemApi.partial_update(exporter.pulp_href, body)
monitor_task(result.task)
exporter_read = pulpcore_bindings.ExportersFilesystemApi.read(exporter.pulp_href)
assert exporter_read.path != exporter.path
assert exporter_read.path == body["path"]

# LIST
exporters = pulpcore_bindings.ExportersFilesystemApi.list(name=exporter.name).results
assert exporter.name in [e.name for e in exporters]

# DELETE
result = pulpcore_bindings.ExportersFilesystemApi.delete(exporter.pulp_href)
monitor_task(result.task)
with pytest.raises(ApiException):
pulpcore_bindings.ExportersFilesystemApi.read(exporter.pulp_href)


@pytest.mark.parallel
def test_fsexport(pulpcore_bindings, fs_exporter_factory, fs_export_factory, pub_and_repo):
exporter = fs_exporter_factory()
(publication, _) = pub_and_repo()
# Test export
body = {"publication": publication.pulp_href}
export = fs_export_factory(exporter, body=body)

# Test list and delete
exports = pulpcore_bindings.ExportersPulpExportsApi.list(exporter.pulp_href).results
assert len(exports) == 1
pulpcore_bindings.ExportersPulpExportsApi.delete(export.pulp_href)
exports = pulpcore_bindings.ExportersPulpExportsApi.list(exporter.pulp_href).results
assert len(exports) == 0


@pytest.mark.parallel
def test_fsexport_by_version(
fs_exporter_factory,
fs_export_factory,
pub_and_repo,
):
(publication, repository) = pub_and_repo()
latest = repository.latest_version_href
zeroth = latest.replace("/2/", "/0/")

# export by version
exporter = fs_exporter_factory()
body = {"repository_version": latest}
fs_export_factory(exporter, body=body)

# export by version with start_version
exporter = fs_exporter_factory()
body = {"repository_version": latest, "start_repository_version": zeroth}
fs_export_factory(exporter, body=body)

# export by publication with start_version
exporter = fs_exporter_factory()
body = {"publication": publication.pulp_href, "start_repository_version": zeroth}
fs_export_factory(exporter, body=body)

# negative: specify publication and version
with pytest.raises(BadRequestException) as e:
exporter = fs_exporter_factory()
body = {"publication": publication.pulp_href, "repository_version": zeroth}
fs_export_factory(exporter, body=body)
assert e.value.status == 400
assert json.loads(e.value.body) == {
"non_field_errors": [
"publication or repository_version must either be supplied but not both."
]
}


@pytest.mark.skipif(not settings.DOMAIN_ENABLED, reason="Domains not enabled.")
@pytest.mark.parallel
def test_fsexport_cross_domain(
fs_exporter_factory,
fs_export_factory,
gen_object_with_cleanup,
pulpcore_bindings,
pub_and_repo,
):

entities = [{}, {}]
for e in entities:
body = {
"name": str(uuid.uuid4()),
"storage_class": "pulpcore.app.models.storage.FileSystem",
"storage_settings": {"MEDIA_ROOT": "/var/lib/pulp/media/"},
}
e["domain"] = gen_object_with_cleanup(pulpcore_bindings.DomainsApi, body)
(e["publication"], e["repository"]) = pub_and_repo(pulp_domain=e["domain"].name)
e["exporter"] = fs_exporter_factory(pulp_domain=e["domain"].name)
body = {"publication": e["publication"].pulp_href}
e["export"] = fs_export_factory(e["exporter"], body=body)

latest = entities[0]["repository"].latest_version_href
zeroth = latest.replace("/2/", "/0/")

with pytest.raises(BadRequestException) as e:
body = {"publication": entities[0]["publication"].pulp_href}
fs_export_factory(entities[1]["exporter"], body=body)

with pytest.raises(BadRequestException) as e:
body = {"repository_version": latest}
fs_export_factory(entities[1]["exporter"], body=body)

with pytest.raises(BadRequestException) as e:
body = {"repository_version": latest, "start_repository_version": zeroth}
fs_export_factory(entities[1]["exporter"], body=body)

with pytest.raises(BadRequestException) as e:
body = {
"publication": entities[0]["publication"].pulp_href,
"start_repository_version": zeroth,
}
fs_export_factory(entities[1]["exporter"], body=body)
106 changes: 103 additions & 3 deletions pulp_file/tests/functional/api/test_pulp_export.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import json
import pytest
import uuid

from pulpcore.client.pulpcore.exceptions import ApiException
from pulpcore.client.pulpcore.exceptions import BadRequestException
from pulpcore.app import settings
from pulpcore.constants import TASK_STATES


pytestmark = [
pytest.mark.skipif(settings.DOMAIN_ENABLED, reason="Domains do not support export."),
pytest.mark.skipif(
"/tmp" not in settings.ALLOWED_EXPORT_PATHS,
reason="Cannot run export-tests unless /tmp is in ALLOWED_EXPORT_PATHS "
Expand All @@ -23,7 +24,10 @@ def pulp_exporter_factory(
gen_object_with_cleanup,
add_to_filesystem_cleanup,
):
def _pulp_exporter_factory(repositories=None):
def _pulp_exporter_factory(
repositories=None,
pulp_domain=None,
):
if repositories is None:
repositories = []
name = str(uuid.uuid4())
Expand All @@ -33,7 +37,11 @@ def _pulp_exporter_factory(repositories=None):
"path": path,
"repositories": [r.pulp_href for r in repositories],
}
exporter = gen_object_with_cleanup(pulpcore_bindings.ExportersPulpApi, body)
kwargs = {}
if pulp_domain:
kwargs["pulp_domain"] = pulp_domain.name

exporter = gen_object_with_cleanup(pulpcore_bindings.ExportersPulpApi, body, **kwargs)
add_to_filesystem_cleanup(path)
assert exporter.name == name
assert exporter.path == path
Expand Down Expand Up @@ -291,3 +299,95 @@ def test_export_incremental(
with pytest.raises(ApiException):
body = {"start_versions": [file_repo.latest_version_href], "full": False}
pulp_export_factory(exporter, body)


# Test that cross-domain attempts fail
# Exporter: repository, last-export : create, update
# Export: versions, start_versions
@pytest.mark.skipif(not settings.DOMAIN_ENABLED, reason="Domains not enabled.")
@pytest.mark.parallel
def test_cross_domain_exporter(
basic_manifest_path,
file_bindings,
file_remote_factory,
gen_object_with_cleanup,
pulpcore_bindings,
pulp_export_factory,
pulp_exporter_factory,
monitor_task,
):
# Create two domains
# In each, create and sync a repository, create and export an exporter
# Attempt to create an exporter using the *other domain's* repo
# Attempt to update the exporter using the *other domain's* repo and last_export
# Use the exporter and attempt to export the *other domain's* repo-versions

entities = [{}, {}]
for e in entities:
body = {
"name": str(uuid.uuid4()),
"storage_class": "pulpcore.app.models.storage.FileSystem",
"storage_settings": {"MEDIA_ROOT": "/var/lib/pulp/media/"},
}
e["domain"] = gen_object_with_cleanup(pulpcore_bindings.DomainsApi, body)
remote = file_remote_factory(
manifest_path=basic_manifest_path, policy="immediate", pulp_domain=e["domain"].name
)

repo_body = {"name": str(uuid.uuid4()), "remote": remote.pulp_href}
e["repository"] = gen_object_with_cleanup(
file_bindings.RepositoriesFileApi, repo_body, pulp_domain=e["domain"].name
)
task = file_bindings.RepositoriesFileApi.sync(e["repository"].pulp_href, {}).task
monitor_task(task)
e["repository"] = file_bindings.RepositoriesFileApi.read(e["repository"].pulp_href)
e["exporter"] = pulp_exporter_factory(
repositories=[e["repository"]], pulp_domain=e["domain"]
)
e["export"] = pulp_export_factory(e["exporter"])

target_domain = entities[1]["domain"]
# cross-create
with pytest.raises(BadRequestException) as e:
pulp_exporter_factory(repositories=[entities[0]["repository"]], pulp_domain=target_domain)
assert e.value.status == 400
assert json.loads(e.value.body) == {
"non_field_errors": [f"Objects must all be a part of the {target_domain.name} domain."]
}
# cross-update
body = {"repositories": [entities[0]["repository"].pulp_href]}
with pytest.raises(BadRequestException) as e:
pulpcore_bindings.ExportersPulpApi.partial_update(entities[1]["exporter"].pulp_href, body)
assert e.value.status == 400
assert json.loads(e.value.body) == {
"non_field_errors": [f"Objects must all be a part of the {target_domain.name} domain."]
}

body = {"last_export": entities[0]["export"].pulp_href}
with pytest.raises(BadRequestException) as e:
pulpcore_bindings.ExportersPulpApi.partial_update(entities[1]["exporter"].pulp_href, body)
assert e.value.status == 400
assert json.loads(e.value.body) == {
"non_field_errors": [f"Objects must all be a part of the {target_domain.name} domain."]
}

# cross-export
with pytest.raises(BadRequestException) as e:
latest_v = entities[0]["repository"].latest_version_href
zero_v = latest_v.replace("/1/", "/0/")
body = {
"start_versions": [latest_v],
"versions": [zero_v],
"full": False,
}
pulp_export_factory(entities[1]["exporter"], body)
assert e.value.status == 400
msgs = json.loads(e.value.body)
assert "versions" in msgs
assert "start_versions" in msgs
assert msgs["versions"] == [
"Requested RepositoryVersions must belong to the Repositories named by the Exporter!"
]
assert msgs["start_versions"] == [
"Requested RepositoryVersions must belong to the Repositories named by the Exporter!"
]
Loading