mirror of
https://github.com/neondatabase/neon.git
synced 2026-05-25 00:50:36 +00:00
Timeline archival test (#8824)
This PR: * Implements the rule that archived timelines require all of their children to be archived as well, as specified in the RFC. There is no fancy locking mechanism though, so the precondition can still be broken. As a TODO for later, we still allow unarchiving timelines with archived parents. * Adds an `is_archived` flag to `TimelineInfo` * Adds timeline_archival_config to `PageserverHttpClient` * Adds a new `test_timeline_archive` test, loosely based on `test_timeline_delete` Part of #8088
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from functools import total_ordering
|
||||
from typing import Any, Dict, Type, TypeVar, Union
|
||||
|
||||
@@ -213,3 +214,9 @@ class TenantShardId:
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self._tuple())
|
||||
|
||||
|
||||
# TODO: Replace with `StrEnum` when we upgrade to python 3.11
|
||||
class TimelineArchivalState(str, Enum):
|
||||
ARCHIVED = "Archived"
|
||||
UNARCHIVED = "Unarchived"
|
||||
|
||||
@@ -10,7 +10,7 @@ import requests
|
||||
from requests.adapters import HTTPAdapter
|
||||
from urllib3.util.retry import Retry
|
||||
|
||||
from fixtures.common_types import Lsn, TenantId, TenantShardId, TimelineId
|
||||
from fixtures.common_types import Lsn, TenantId, TenantShardId, TimelineArchivalState, TimelineId
|
||||
from fixtures.log_helper import log
|
||||
from fixtures.metrics import Metrics, MetricsGetter, parse_metrics
|
||||
from fixtures.pg_version import PgVersion
|
||||
@@ -621,6 +621,22 @@ class PageserverHttpClient(requests.Session, MetricsGetter):
|
||||
)
|
||||
self.verbose_error(res)
|
||||
|
||||
def timeline_archival_config(
|
||||
self,
|
||||
tenant_id: Union[TenantId, TenantShardId],
|
||||
timeline_id: TimelineId,
|
||||
state: TimelineArchivalState,
|
||||
):
|
||||
config = {"state": state.value}
|
||||
log.info(
|
||||
f"requesting timeline archival config {config} for tenant {tenant_id} and timeline {timeline_id}"
|
||||
)
|
||||
res = self.post(
|
||||
f"http://localhost:{self.port}/v1/tenant/{tenant_id}/timeline/{timeline_id}/archival_config",
|
||||
json=config,
|
||||
)
|
||||
self.verbose_error(res)
|
||||
|
||||
def timeline_get_lsn_by_timestamp(
|
||||
self,
|
||||
tenant_id: Union[TenantId, TenantShardId],
|
||||
|
||||
96
test_runner/regress/test_timeline_archive.py
Normal file
96
test_runner/regress/test_timeline_archive.py
Normal file
@@ -0,0 +1,96 @@
|
||||
import pytest
|
||||
from fixtures.common_types import TenantId, TimelineArchivalState, TimelineId
|
||||
from fixtures.neon_fixtures import (
|
||||
NeonEnv,
|
||||
)
|
||||
from fixtures.pageserver.http import PageserverApiException
|
||||
|
||||
|
||||
def test_timeline_archive(neon_simple_env: NeonEnv):
|
||||
env = neon_simple_env
|
||||
|
||||
env.pageserver.allowed_errors.extend(
|
||||
[
|
||||
".*Timeline .* was not found.*",
|
||||
".*timeline not found.*",
|
||||
".*Cannot archive timeline which has unarchived child timelines.*",
|
||||
".*Precondition failed: Requested tenant is missing.*",
|
||||
]
|
||||
)
|
||||
|
||||
ps_http = env.pageserver.http_client()
|
||||
|
||||
# first try to archive non existing timeline
|
||||
# for existing tenant:
|
||||
invalid_timeline_id = TimelineId.generate()
|
||||
with pytest.raises(PageserverApiException, match="timeline not found") as exc:
|
||||
ps_http.timeline_archival_config(
|
||||
tenant_id=env.initial_tenant,
|
||||
timeline_id=invalid_timeline_id,
|
||||
state=TimelineArchivalState.ARCHIVED,
|
||||
)
|
||||
|
||||
assert exc.value.status_code == 404
|
||||
|
||||
# for non existing tenant:
|
||||
invalid_tenant_id = TenantId.generate()
|
||||
with pytest.raises(
|
||||
PageserverApiException,
|
||||
match=f"NotFound: tenant {invalid_tenant_id}",
|
||||
) as exc:
|
||||
ps_http.timeline_archival_config(
|
||||
tenant_id=invalid_tenant_id,
|
||||
timeline_id=invalid_timeline_id,
|
||||
state=TimelineArchivalState.ARCHIVED,
|
||||
)
|
||||
|
||||
assert exc.value.status_code == 404
|
||||
|
||||
# construct pair of branches to validate that pageserver prohibits
|
||||
# archival of ancestor timelines when they have non-archived child branches
|
||||
parent_timeline_id = env.neon_cli.create_branch("test_ancestor_branch_archive_parent", "empty")
|
||||
|
||||
leaf_timeline_id = env.neon_cli.create_branch(
|
||||
"test_ancestor_branch_archive_branch1", "test_ancestor_branch_archive_parent"
|
||||
)
|
||||
|
||||
timeline_path = env.pageserver.timeline_dir(env.initial_tenant, parent_timeline_id)
|
||||
|
||||
with pytest.raises(
|
||||
PageserverApiException,
|
||||
match="Cannot archive timeline which has non-archived child timelines",
|
||||
) as exc:
|
||||
assert timeline_path.exists()
|
||||
|
||||
ps_http.timeline_archival_config(
|
||||
tenant_id=env.initial_tenant,
|
||||
timeline_id=parent_timeline_id,
|
||||
state=TimelineArchivalState.ARCHIVED,
|
||||
)
|
||||
|
||||
assert exc.value.status_code == 412
|
||||
|
||||
# Test timeline_detail
|
||||
leaf_detail = ps_http.timeline_detail(
|
||||
tenant_id=env.initial_tenant,
|
||||
timeline_id=leaf_timeline_id,
|
||||
)
|
||||
assert leaf_detail["is_archived"] is False
|
||||
|
||||
# Test that archiving the leaf timeline and then the parent works
|
||||
ps_http.timeline_archival_config(
|
||||
tenant_id=env.initial_tenant,
|
||||
timeline_id=leaf_timeline_id,
|
||||
state=TimelineArchivalState.ARCHIVED,
|
||||
)
|
||||
leaf_detail = ps_http.timeline_detail(
|
||||
tenant_id=env.initial_tenant,
|
||||
timeline_id=leaf_timeline_id,
|
||||
)
|
||||
assert leaf_detail["is_archived"] is True
|
||||
|
||||
ps_http.timeline_archival_config(
|
||||
tenant_id=env.initial_tenant,
|
||||
timeline_id=parent_timeline_id,
|
||||
state=TimelineArchivalState.ARCHIVED,
|
||||
)
|
||||
Reference in New Issue
Block a user