Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BAR-29: Minor AWS snapshot fixes #825

Merged
merged 5 commits into from
Jul 20, 2023
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
5 changes: 5 additions & 0 deletions barman/cloud_providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,16 @@ def get_snapshot_interface_from_backup_info(backup_info, config=None):
elif backup_info.snapshots_info.provider == "aws":
from barman.cloud_providers.aws_s3 import AwsCloudSnapshotInterface

# When creating a snapshot interface for existing backups we use the region
# from the backup_info, unless a region is set in the config in which case the
# config region takes precedence.
region = None
profile = None
if config is not None and hasattr(config, "aws_region"):
region = config.aws_region
profile = config.aws_profile
if region is None:
region = backup_info.snapshots_info.region
return AwsCloudSnapshotInterface(profile, region)
else:
raise CloudProviderUnsupported(
Expand Down
5 changes: 5 additions & 0 deletions doc/manual/28-snapshots.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ The following additional prerequisites apply to snapshot backups on AWS:
The following permissions are required:

- `ec2:CreateSnapshot`
- `ec2:CreateTags`
- `ec2:DeleteSnapshot`
- `ec2:DescribeSnapshots`
- `ec2:DescribeInstances`
Expand Down Expand Up @@ -147,6 +148,10 @@ azure_resource_group = AZURE_RESOURCE_GROUP

#### Configuration for AWS snapshots

When specifying `snapshot_instance` or `snapshot_disks`, Barman will accept either the instance/volume ID which was assigned to the resource by AWS *or* a name.
If a name is used then Barman will query AWS to find resources with a matching `Name` tag.
If zero or multiple matching resources are found then Barman will exit with an error.

The following optional parameters can be set when using AWS:

``` ini
Expand Down
51 changes: 47 additions & 4 deletions tests/test_cloud_snapshot_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class TestGetSnapshotInterface(object):
("aws", AwsCloudSnapshotInterface),
("azure", AzureCloudSnapshotInterface),
("gcp", GcpCloudSnapshotInterface),
("unsupportedcloud", None),
],
)
@mock.patch("barman.cloud_providers._get_azure_credential")
Expand Down Expand Up @@ -136,6 +137,7 @@ def test_from_config_azure_no_subscription_id(self):
("aws", AwsCloudSnapshotInterface),
("azure", AzureCloudSnapshotInterface),
("gcp", GcpCloudSnapshotInterface),
("unsupportedcloud", None),
],
)
@mock.patch("barman.cloud_providers.aws_s3.boto3")
Expand Down Expand Up @@ -205,7 +207,7 @@ def test_from_backup_info_azure_no_subscription_id(self):
mock_backup_info = mock.Mock(
snapshots_info=mock.Mock(provider="azure", subscription_id=None)
)
# WHEN get snapshot_interface_from_server_config is called
# WHEN get snapshot_interface_from_backup_info is called
with pytest.raises(ConfigurationException) as exc:
get_snapshot_interface_from_backup_info(mock_backup_info, mock_config)
# THEN the expected exception is raised
Expand All @@ -214,6 +216,46 @@ def test_from_backup_info_azure_no_subscription_id(self):
in str(exc.value)
)

@pytest.mark.parametrize(
("config_region", "backup_info_region", "expected_region"),
(
# If neither config nor backup_info have a region we expect None
(None, None, None),
# If the config has a region but backup_info does not then we expect the
# region in the config
("config-region", None, "config-region"),
# If the backup_info has a region but the config does not then we expect
# the region in the backup_info
(None, "backup-info-region", "backup-info-region"),
# If both config and backup_info have a region we expect the region in the
# config
("config-region", "backup-info-region", "config-region"),
),
)
@mock.patch("barman.cloud_providers.aws_s3.boto3")
def test_from_backup_info_aws_region(
self, mock_boto3, config_region, backup_info_region, expected_region
):
"""
Verify that the region is taken from the backup_info but can be overridden by
the config.
"""
# GIVEN a server config with the aws snapshot provider and the specified region
mock_config = mock.Mock(snapshot_provider="aws", aws_region=config_region)
# AND a backup info with the specified region
mock_backup_info = mock.Mock(
snapshots_info=mock.Mock(provider="aws", region=backup_info_region)
)
# AND the session has no default region name
mock_boto3.Session.return_value.region_name = None

# WHEN get snapshot_interface_from_backup_info is called
snapshot_interface = get_snapshot_interface_from_backup_info(
mock_backup_info, mock_config
)
# THEN the snapshot interface has the expected region
assert snapshot_interface.region == expected_region

@pytest.mark.parametrize(
("cloud_provider", "interface_cls"),
[
Expand All @@ -223,6 +265,7 @@ def test_from_backup_info_azure_no_subscription_id(self):
AzureCloudSnapshotInterface,
),
("google-cloud-storage", GcpCloudSnapshotInterface),
("unsupportedcloud", None),
],
)
@mock.patch("barman.cloud_providers._get_azure_credential")
Expand Down Expand Up @@ -2051,7 +2094,7 @@ def test_get_attached_volumes_disk_not_found(self):
expected_volumes = [d["name"] for d in disks[:-1]]
assert set(attached_volumes.keys()) == set(expected_volumes)

def test_get_attached_volumes_disk_not_attaached(self):
def test_get_attached_volumes_disk_not_attached(self):
"""
Verify that a SnapshotBackupException is raised if a disk is not attached
to the instance.
Expand Down Expand Up @@ -2235,15 +2278,15 @@ class TestAzureVolumeMetadata(object):
# If no attachment metadata or disk metadata is passed then we expect init
# to succeed but the lun and location to be None.
(None, None, None, None),
# If the lun is set in the attachment metadata then we eexpect it to be
# If the lun is set in the attachment metadata then we expect it to be
# set in the VolumeMetadata instance.
(
mock.Mock(lun="10"),
None,
"10",
None,
),
# If the location is set in the attachment metadata then we eexpect it to
# If the location is set in the attachment metadata then we expect it to
# be set in the VolumeMetadata instance.
(None, mock.Mock(location="uksouth"), None, "uksouth"),
# If lun and location are set in the attachment metadata then we expect
Expand Down
2 changes: 1 addition & 1 deletion tests/test_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1712,7 +1712,7 @@ def mock_resolve_mounted_volume(mock_volume, disk_name, _cmd):
mock_resolve_mounted_volume, mock_volume, disk
)

# WHEN find_missing_and_unmounted_disks is called for all expected disksts
# WHEN find_missing_and_unmounted_disks is called for all expected disks
instance_name = "instance1"
snapshot_disks = (
expected_missing_disks + expected_unmounted_disks + expected_mounted_disks
Expand Down