Skip to content

Commit

Permalink
Set units to blocked status when scaling beyond 1
Browse files Browse the repository at this point in the history
Instead of throwing an error, block the units when scaled beyond 1.

Throwing an error causes the charm to go into a bad state, where it
can't even be removed without manual intervention. This even causes an
issue with a single unit, as sometimes leader status is removed before
the charm has finished processing all events.
  • Loading branch information
DanielArndt committed Oct 18, 2023
1 parent cf1a9cc commit 72eedc1
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 1 deletion.
8 changes: 7 additions & 1 deletion src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,13 @@ class PCFOperatorCharm(CharmBase):
def __init__(self, *args):
super().__init__(*args)
if not self.unit.is_leader():
raise NotImplementedError("Scaling is not implemented for this charm")
# NOTE: In cases where leader status is lost before the charm is
# finished processing all teardown events, this prevents teardown
# event code from running. Luckily, for this charm, none of the
# teardown code is necessary to preform if we're removing the
# charm.
self.unit.status = BlockedStatus("Scaling is not implemented for this charm")
return
self._container_name = self._service_name = "pcf"
self._container = self.unit.get_container(self._container_name)

Expand Down
22 changes: 22 additions & 0 deletions tests/integration/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@


import logging
from collections import Counter
from pathlib import Path

import pytest
import yaml
from juju.application import Application
from pytest_operator.plugin import OpsTest

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -134,3 +136,23 @@ async def test_restore_tls_and_wait_for_active_status(ops_test: OpsTest, build_a
relation1=APPLICATION_NAME, relation2=TLS_PROVIDER_NAME
)
await ops_test.model.wait_for_idle(apps=[APPLICATION_NAME], status="active", timeout=1000) # type: ignore[union-attr] # noqa: E501


@pytest.mark.abort_on_fail
async def test_when_scale_app_beyond_1_then_only_one_unit_is_active(
ops_test: OpsTest, build_and_deploy
):
assert ops_test.model
assert isinstance(app := ops_test.model.applications[APPLICATION_NAME], Application)
await app.scale(3)
await ops_test.model.wait_for_idle(
apps=[APPLICATION_NAME], timeout=1000, wait_for_at_least_units=3
)
unit_statuses = Counter(unit.workload_status for unit in app.units)
assert unit_statuses.get("active") == 1
assert unit_statuses.get("blocked") == 2


async def test_remove_app(ops_test: OpsTest, build_and_deploy):
assert ops_test.model
await ops_test.model.remove_application(APPLICATION_NAME, block_until_done=True)

0 comments on commit 72eedc1

Please sign in to comment.