From 07866bf39b909ded031a87ac2e72f32c4d127b59 Mon Sep 17 00:00:00 2001 From: Myles Penner Date: Tue, 14 Jul 2026 14:27:10 -0700 Subject: [PATCH 1/2] fix: accept blocked infra observability agent with external COS With an external COS, the opentelemetry-collector COS relations are only created by IntegrateRemoteCosOffersStep after all agents are deployed, since the terraform juju provider cannot consume offers across controllers. The infra model agent deploy step introduced in eb3c5684 waits for the collector to reach active status, which it can never do at that point, so `sunbeam enable observability external` on MAAS deployments timed out after 30 minutes and the COS offers were never consumed or related in any model. Accept blocked status for the infra agent deploy step, matching the other agent deploy steps in the external flow. The collector settles to active once the offers are integrated by the subsequent step. Closes-Bug: #2159965 Signed-off-by: Myles Penner Assisted-by: Claude Fable 5 (cherry picked from commit 09baf987352fe385de407e9e4958bf80bfe57169) --- .../sunbeam/features/observability/feature.py | 1 + .../sunbeam/features/test_observability.py | 47 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/sunbeam-python/sunbeam/features/observability/feature.py b/sunbeam-python/sunbeam/features/observability/feature.py index 777eb85bd..0efce54b1 100644 --- a/sunbeam-python/sunbeam/features/observability/feature.py +++ b/sunbeam-python/sunbeam/features/observability/feature.py @@ -1715,6 +1715,7 @@ def run_enable_plans( self, tfhelper_observability_agent_infra, jhelper, + accepted_app_status=["active", "blocked"], ), ] run_plan(infra_agent_plan, console, show_hints) diff --git a/sunbeam-python/tests/unit/sunbeam/features/test_observability.py b/sunbeam-python/tests/unit/sunbeam/features/test_observability.py index f65b3a4b8..87305c9a0 100644 --- a/sunbeam-python/tests/unit/sunbeam/features/test_observability.py +++ b/sunbeam-python/tests/unit/sunbeam/features/test_observability.py @@ -930,6 +930,53 @@ def test_run_non_maas_excludes_infra_model( assert result.result_type == ResultType.COMPLETED +class TestExternalObservabilityEnablePlans: + """Test enablement plans for ExternalObservabilityFeature.""" + + def _run_enable_plans(self, feature, deployment, run_plan_obs, is_maas): + feature._manifest = Mock() + with patch( + "sunbeam.features.observability.feature.is_maas_deployment", + return_value=is_maas, + ): + feature.run_enable_plans(deployment, Mock(), False) + + return [step for call in run_plan_obs.call_args_list for step in call.args[0]] + + def test_maas_infra_agent_step_accepts_blocked( + self, deployment, run_plan_obs, juju_helper_obs + ): + """External COS: infra agent is blocked until offers are integrated. + + With an external COS, integrations are only created by + IntegrateRemoteCosOffersStep after all agents are deployed, so the + infra agent deploy step must accept 'blocked' status (LP#2159965). + """ + feature = observability_feature.ExternalObservabilityFeature() + steps = self._run_enable_plans(feature, deployment, run_plan_obs, is_maas=True) + + infra_steps = [ + step + for step in steps + if isinstance(step, observability_feature.DeployObservabilityAgentInfraStep) + ] + assert len(infra_steps) == 1 + assert "blocked" in infra_steps[0].accepted_app_status + assert "active" in infra_steps[0].accepted_app_status + + def test_maas_integrate_offers_runs_after_infra_agent( + self, deployment, run_plan_obs, juju_helper_obs + ): + """Offers are integrated only after the infra agent is deployed.""" + feature = observability_feature.ExternalObservabilityFeature() + steps = self._run_enable_plans(feature, deployment, run_plan_obs, is_maas=True) + + step_types = [type(step) for step in steps] + assert step_types.index( + observability_feature.IntegrateRemoteCosOffersStep + ) > step_types.index(observability_feature.DeployObservabilityAgentInfraStep) + + class TestObservabilityFeatureTimeouts: """Test timeout calculation for ObservabilityFeature.""" From 4afe048437382faa3e3ecbd59b47227c14a23909 Mon Sep 17 00:00:00 2001 From: Myles Penner Date: Tue, 14 Jul 2026 14:27:46 -0700 Subject: [PATCH 2/2] fix: use send-loki-logs endpoint when removing external COS relations The migration from grafana-agent to opentelemetry-collector (f5fc0a15) kept the grafana-agent loki endpoint name logging-consumer, which does not exist on the opentelemetry-collector charm. Commit 7fdf5bd8 corrected it to send-loki-logs in IntegrateRemoteCosOffersStep but missed RemoveRemoteCosOffersStep, so disabling external observability silently skipped removing the loki relation and then waited for the collector to reach blocked status, which it may never do while the relation remains. Signed-off-by: Myles Penner Assisted-by: Claude Fable 5 (cherry picked from commit eb0eead85ad152d34858385c6242542539051af6) --- sunbeam-python/sunbeam/features/observability/feature.py | 2 +- .../tests/unit/sunbeam/features/test_observability.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sunbeam-python/sunbeam/features/observability/feature.py b/sunbeam-python/sunbeam/features/observability/feature.py index 0efce54b1..13c431cae 100644 --- a/sunbeam-python/sunbeam/features/observability/feature.py +++ b/sunbeam-python/sunbeam/features/observability/feature.py @@ -937,7 +937,7 @@ def __init__( self.endpoints = [ "opentelemetry-collector:grafana-dashboards-provider", "opentelemetry-collector:send-remote-write", - "opentelemetry-collector:logging-consumer", + "opentelemetry-collector:send-loki-logs", ] def _get_relations(self, model: str, endpoints: list[str]) -> list[tuple]: diff --git a/sunbeam-python/tests/unit/sunbeam/features/test_observability.py b/sunbeam-python/tests/unit/sunbeam/features/test_observability.py index 87305c9a0..646bb6f82 100644 --- a/sunbeam-python/tests/unit/sunbeam/features/test_observability.py +++ b/sunbeam-python/tests/unit/sunbeam/features/test_observability.py @@ -794,7 +794,7 @@ def test_run( Mock( apps={ "opentelemetry-collector": Mock( - relations={"logging-consumer": "loki:loki_push_api"} + relations={"send-loki-logs": "loki:loki_push_api"} ) } ), @@ -849,7 +849,7 @@ def test_run_waiting_timedout( Mock( apps={ "opentelemetry-collector": Mock( - relations={"logging-consumer": "loki:loki_push_api"} + relations={"send-loki-logs": "loki:loki_push_api"} ) } ),