Skip to content

Commit

Permalink
feat(relocation): Split autopause (#81762)
Browse files Browse the repository at this point in the history
After this rolls out, I'll update the options-automator to match.
  • Loading branch information
azaslavsky authored Dec 6, 2024
1 parent 6b848ee commit c54abd8
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ Makefile @getsentry/owners-sentr
/src/sentry/analytics/events/relocation_*.py @getsentry/open-source
/src/sentry/api/endpoints/organization_fork.py @getsentry/open-source
/src/sentry/api/endpoints/relocation/ @getsentry/open-source
/src/sentry/api/serialiers/models/relocation/ @getsentry/open-source
/src/sentry/api/serializers/models/relocation/ @getsentry/open-source
/src/sentry/models/relocation/ @getsentry/open-source
/src/sentry/relocation/ @getsentry/open-source
/src/sentry/tasks/relocation.py @getsentry/open-source
Expand Down
5 changes: 3 additions & 2 deletions src/sentry/api/endpoints/organization_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,14 @@ def post(self, request: Request, organization_id_or_slug) -> Response:
# We do not create a `RelocationFile` yet. Instead, we trigger a series of RPC calls (via
# `uploading_start`, scheduled below) to create an export of the organization we are seeking
# duplicate from the foreign region.
provenance = Relocation.Provenance.SAAS_TO_SAAS
with atomic_transaction(using=(router.db_for_write(Relocation))):
new_relocation: Relocation = Relocation.objects.create(
creator_id=request.user.id,
owner_id=owner.id,
step=Relocation.Step.UPLOADING.value,
scheduled_pause_at_step=get_autopause_value(),
provenance=Relocation.Provenance.SAAS_TO_SAAS,
scheduled_pause_at_step=get_autopause_value(provenance),
provenance=provenance,
want_org_slugs=[org_mapping.slug],
)

Expand Down
17 changes: 13 additions & 4 deletions src/sentry/api/endpoints/relocations/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,18 @@ def validate_relocation_uniqueness(owner: RpcUser | AnonymousUser | User) -> Res
return None


def get_autopause_value() -> int | None:
def get_autopause_value(provenance: Relocation.Provenance) -> int | None:
try:
return Relocation.Step[options.get("relocation.autopause")].value
return Relocation.Step[options.get(f"relocation.autopause.{str(provenance)}")].value
except KeyError:
return None
# DEPRECATED: for now, we fall through to the old `relocation.autopause` if the more
# specific `relocation.autopause.*` does not exist OR is set to the default, an empty
# string. Once we remove the old setting, this block can go away, and we can use the mre
# specific autopause only.
try:
return Relocation.Step[options.get("relocation.autopause")].value
except KeyError:
return None


@region_silo_endpoint
Expand Down Expand Up @@ -263,12 +270,14 @@ def post(self, request: Request) -> Response:
with atomic_transaction(
using=(router.db_for_write(Relocation), router.db_for_write(RelocationFile))
):
provenance = Relocation.Provenance.SELF_HOSTED
relocation: Relocation = Relocation.objects.create(
creator_id=request.user.id,
owner_id=owner.id,
want_org_slugs=org_slugs,
step=Relocation.Step.UPLOADING.value,
scheduled_pause_at_step=get_autopause_value(),
scheduled_pause_at_step=get_autopause_value(provenance),
provenance=provenance,
)
RelocationFile.objects.create(
relocation=relocation,
Expand Down
5 changes: 4 additions & 1 deletion src/sentry/api/endpoints/relocations/retry.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ def post(self, request: Request, relocation_uuid: str) -> Response:
owner_id=relocation.owner_id,
want_org_slugs=relocation.want_org_slugs,
step=Relocation.Step.UPLOADING.value,
scheduled_pause_at_step=get_autopause_value(),
scheduled_pause_at_step=get_autopause_value(
Relocation.Provenance(relocation.provenance)
),
provenance=relocation.provenance,
)

relocation_retry_link_promo_code.send_robust(
Expand Down
8 changes: 8 additions & 0 deletions src/sentry/models/relocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ class Provenance(IntEnum):
def get_choices(cls) -> list[tuple[int, str]]:
return [(key.value, key.name) for key in cls]

def __str__(self):
if self.name == "SELF_HOSTED":
return "self-hosted"
elif self.name == "SAAS_TO_SAAS":
return "saas-to-saas"
else:
raise ValueError("Cannot extract a filename from `RelocationFile.Kind.UNKNOWN`.")

# The user that requested this relocation - if the request was made by an admin on behalf of a
# user, this will be different from `owner`. Otherwise, they are identical.
creator_id = BoundedBigIntegerField()
Expand Down
17 changes: 17 additions & 0 deletions src/sentry/options/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -2366,12 +2366,29 @@

# Relocation: the step at which new relocations should be autopaused, requiring admin approval
# before continuing.
# DEPRECATED: will be removed after the new `relocation.autopause.*` options are fully rolled out.
register(
"relocation.autopause",
default="",
flags=FLAG_AUTOMATOR_MODIFIABLE,
)

# Relocation: the step at which new `SELF_HOSTED` relocations should be autopaused, requiring an
# admin to unpause before continuing.
register(
"relocation.autopause.self-hosted",
default="",
flags=FLAG_AUTOMATOR_MODIFIABLE,
)

# Relocation: the step at which new `SELF_HOSTED` relocations should be autopaused, requiring an
# admin to unpause before continuing.
register(
"relocation.autopause.saas-to-saas",
default="",
flags=FLAG_AUTOMATOR_MODIFIABLE,
)

# Relocation: globally limits the number of small (<=10MB) relocations allowed per silo per day.
register(
"relocation.daily-limit.small",
Expand Down
41 changes: 39 additions & 2 deletions tests/sentry/api/endpoints/relocations/test_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ def test_good_promo_code(
{
"relocation.enabled": True,
"relocation.daily-limit.small": 1,
"relocation.autopause": "IMPORTING",
"relocation.autopause.self-hosted": "IMPORTING",
}
)
@patch("sentry.tasks.relocation.uploading_start.apply_async")
Expand Down Expand Up @@ -492,7 +492,44 @@ def test_good_with_valid_autopause_option(
{
"relocation.enabled": True,
"relocation.daily-limit.small": 1,
"relocation.autopause": "DOESNOTEXIST",
"relocation.autopause.saas-to-saas": "IMPORTING",
}
)
@patch("sentry.tasks.relocation.uploading_start.apply_async")
def test_good_with_untriggered_autopause_option(
self,
uploading_start_mock: Mock,
relocation_link_promo_code_signal_mock: Mock,
analytics_record_mock: Mock,
):
self.login_as(user=self.owner, superuser=False)

with tempfile.TemporaryDirectory() as tmp_dir:
(_, tmp_pub_key_path) = self.tmp_keys(tmp_dir)
with open(FRESH_INSTALL_PATH, "rb") as f:
data = orjson.loads(f.read())
with open(tmp_pub_key_path, "rb") as p:
response = self.get_success_response(
owner=self.owner.username,
file=SimpleUploadedFile(
"export.tar",
create_encrypted_export_tarball(data, LocalFileEncryptor(p)).getvalue(),
content_type="application/tar",
),
orgs="testing",
format="multipart",
status_code=201,
)

assert response.data["status"] == Relocation.Status.IN_PROGRESS.name
assert response.data["step"] == Relocation.Step.UPLOADING.name
assert response.data["scheduledPauseAtStep"] is None

@override_options(
{
"relocation.enabled": True,
"relocation.daily-limit.small": 1,
"relocation.autopause.self-hosted": "DOESNOTEXIST",
}
)
@patch("sentry.tasks.relocation.uploading_start.apply_async")
Expand Down
40 changes: 39 additions & 1 deletion tests/sentry/api/endpoints/test_organization_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def test_good_simple_using_organization_id(
{
"relocation.enabled": True,
"relocation.daily-limit.small": 1,
"relocation.autopause": "IMPORTING",
"relocation.autopause.saas-to-saas": "IMPORTING",
}
)
@assume_test_silo_mode(SiloMode.REGION, region_name=REQUESTING_TEST_REGION)
Expand Down Expand Up @@ -169,6 +169,44 @@ def test_good_with_valid_autopause_option(
replying_region_name=EXPORTING_TEST_REGION,
)

@override_options(
{
"relocation.enabled": True,
"relocation.daily-limit.small": 1,
"relocation.autopause.self-hosted": "IMPORTING",
}
)
@assume_test_silo_mode(SiloMode.REGION, region_name=REQUESTING_TEST_REGION)
def test_good_with_untriggered_autopause_option(
self,
uploading_start_mock: Mock,
analytics_record_mock: Mock,
):
self.login_as(user=self.superuser, superuser=True)

response = self.get_success_response(self.existing_org.slug)

assert response.data["status"] == Relocation.Status.IN_PROGRESS.name
assert response.data["step"] == Relocation.Step.UPLOADING.name
assert response.data["provenance"] == Relocation.Provenance.SAAS_TO_SAAS.name
assert response.data["scheduledPauseAtStep"] is None

assert uploading_start_mock.call_count == 1
uploading_start_mock.assert_called_with(
args=[UUID(response.data["uuid"]), EXPORTING_TEST_REGION, self.requested_org_slug]
)

assert analytics_record_mock.call_count == 1
analytics_record_mock.assert_called_with(
"relocation.forked",
creator_id=int(response.data["creator"]["id"]),
owner_id=int(response.data["owner"]["id"]),
uuid=response.data["uuid"],
from_org_slug=self.requested_org_slug,
requesting_region_name=REQUESTING_TEST_REGION,
replying_region_name=EXPORTING_TEST_REGION,
)

@override_options(
{"relocation.enabled": False, "relocation.daily-limit.small": 1, "staff.ga-rollout": True}
)
Expand Down

0 comments on commit c54abd8

Please sign in to comment.