diff --git a/cou/apps/base.py b/cou/apps/base.py index c96d0074..c3aeee97 100644 --- a/cou/apps/base.py +++ b/cou/apps/base.py @@ -122,6 +122,13 @@ def __str__(self) -> str: "machine": unit.machine.machine_id, "workload_version": unit.workload_version, "o7k_version": str(self.get_latest_o7k_version(unit)), + "subordinates": { + subordinate.name: { + "name": subordinate.name, + "charm": subordinate.charm, + } + for subordinate in unit.subordinates + }, } for unit in self.units.values() }, diff --git a/cou/apps/core.py b/cou/apps/core.py index d4082b74..1581a90c 100644 --- a/cou/apps/core.py +++ b/cou/apps/core.py @@ -106,7 +106,45 @@ def post_upgrade_steps( :return: List of post upgrade steps. :rtype: list[PostUpgradeStep] """ - return self._get_enable_scheduler_step(units) + super().post_upgrade_steps(target, units) + return ( + self._get_enable_scheduler_step(units) + + self._get_restart_subordinate_services_steps(units) + + super().post_upgrade_steps(target, units) + ) + + def _get_restart_subordinate_services_steps( + self, units: Optional[list[Unit]] + ) -> list[PostUpgradeStep]: + """Get step to restart all subordinate services if they aren't running. + + :param units: Units to restart subordinate services. + :type units: Optional[list[Unit]] + :return: Steps to restart to subordinate services + :rtype: list[PostUpgradeStep] + """ + if not units: + units = list(self.units.values()) + steps = [] + for unit in units: + for subordinate in unit.subordinates: + if "ceilometer-agent" == subordinate.charm: + service = "ceilometer-agent-compute" + steps.append( + PostUpgradeStep( + description=( + "Restart service ceilometer-agent-compute " + f"for subordinate unit: '{subordinate.name}'" + ), + coro=self.model.run_on_unit( + unit_name=subordinate.name, + command=( + f"systemctl is-active --quiet {service}" + f" || systemctl restart {service}" + ), + ), + ) + ) + return steps def _get_unit_upgrade_steps(self, unit: Unit, force: bool) -> UnitUpgradeStep: """Get the upgrade steps for a single unit. diff --git a/cou/utils/juju_utils.py b/cou/utils/juju_utils.py index 124198bf..b853666c 100644 --- a/cou/utils/juju_utils.py +++ b/cou/utils/juju_utils.py @@ -19,9 +19,9 @@ import asyncio import logging import os -from dataclasses import dataclass +from dataclasses import dataclass, field from datetime import datetime -from typing import Any, Callable, Optional +from typing import Any, Callable, List, Optional from juju.action import Action from juju.application import Application as JujuApplication @@ -142,6 +142,22 @@ class Machine: az: Optional[str] = None # simple deployments may not have azs +@dataclass(frozen=True) +class SubordinateUnit: + """Representation of a single unit of subordinate unit.""" + + name: str + charm: str + + def __repr__(self) -> str: + """App representation. + + :return: Name of the application + :rtype: str + """ + return self.name + + @dataclass(frozen=True) class Unit: """Representation of a single unit of application.""" @@ -149,6 +165,7 @@ class Unit: name: str machine: Machine workload_version: str + subordinates: List[SubordinateUnit] = field(default_factory=lambda: [], compare=False) def __repr__(self) -> str: """App representation. @@ -383,7 +400,17 @@ async def get_applications(self) -> dict[str, Application]: series=status.series, subordinate_to=status.subordinate_to, units={ - name: Unit(name, machines[unit.machine], unit.workload_version) + name: Unit( + name, + machines[unit.machine], + unit.workload_version, + [ + SubordinateUnit( + subordinate, model.applications[subordinate.split("/")[0]] + ) + for subordinate, subordinate_unit in unit.subordinates.items() + ], + ) for name, unit in status.units.items() }, workload_version=status.workload_version, diff --git a/tests/mocked_plans/sample_plans/018346c5-f95c-46df-a34e-9a78bdec0018.yaml b/tests/mocked_plans/sample_plans/018346c5-f95c-46df-a34e-9a78bdec0018.yaml index d9c9c4c4..298404bc 100644 --- a/tests/mocked_plans/sample_plans/018346c5-f95c-46df-a34e-9a78bdec0018.yaml +++ b/tests/mocked_plans/sample_plans/018346c5-f95c-46df-a34e-9a78bdec0018.yaml @@ -320,6 +320,10 @@ plan: | Enable nova-compute scheduler from unit: 'nova-compute-kvm/2' Enable nova-compute scheduler from unit: 'nova-compute-kvm/3' Enable nova-compute scheduler from unit: 'nova-compute-kvm/9' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/0' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/2' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/3' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/9' Wait for up to 2400s for model '018346c5-f95c-46df-a34e-9a78bdec0018' to reach the idle state Verify that the workload of 'nova-compute-kvm' has been upgraded on units: nova-compute-kvm/0, nova-compute-kvm/2, nova-compute-kvm/3, nova-compute-kvm/9 Upgrade plan for [cinder-volume/1, cinder-volume/10, cinder-volume/11, cinder-volume/5, nova-compute-kvm/1, nova-compute-kvm/10, nova-compute-kvm/11, nova-compute-kvm/5] in 'zone3' to 'victoria' @@ -387,6 +391,10 @@ plan: | Enable nova-compute scheduler from unit: 'nova-compute-kvm/10' Enable nova-compute scheduler from unit: 'nova-compute-kvm/11' Enable nova-compute scheduler from unit: 'nova-compute-kvm/5' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/1' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/10' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/11' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/5' Wait for up to 2400s for model '018346c5-f95c-46df-a34e-9a78bdec0018' to reach the idle state Verify that the workload of 'nova-compute-kvm' has been upgraded on units: nova-compute-kvm/1, nova-compute-kvm/10, nova-compute-kvm/11, nova-compute-kvm/5 Upgrade plan for [cinder-volume/4, cinder-volume/6, cinder-volume/7, cinder-volume/8, nova-compute-kvm/4, nova-compute-kvm/6, nova-compute-kvm/7, nova-compute-kvm/8] in 'zone1' to 'victoria' @@ -454,6 +462,10 @@ plan: | Enable nova-compute scheduler from unit: 'nova-compute-kvm/6' Enable nova-compute scheduler from unit: 'nova-compute-kvm/7' Enable nova-compute scheduler from unit: 'nova-compute-kvm/8' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/4' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/6' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/7' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/8' Wait for up to 2400s for model '018346c5-f95c-46df-a34e-9a78bdec0018' to reach the idle state Verify that the workload of 'nova-compute-kvm' has been upgraded on units: nova-compute-kvm/4, nova-compute-kvm/6, nova-compute-kvm/7, nova-compute-kvm/8 Remaining Data Plane principal(s) upgrade plan @@ -6739,61 +6751,108 @@ applications: machine: '21' workload_version: 21.2.4 o7k_version: ussuri + subordinates: + ceilometer-agent/0: + name: ceilometer-agent/0 + charm: ceilometer-agent nova-compute-kvm/1: name: nova-compute-kvm/1 machine: '22' workload_version: 21.2.4 - o7k_version: ussuri + subordinates: + ceilometer-agent/1: + name: ceilometer-agent/1 + charm: ceilometer-agent nova-compute-kvm/10: name: nova-compute-kvm/10 machine: '31' workload_version: 21.2.4 o7k_version: ussuri + subordinates: + ceilometer-agent/10: + name: ceilometer-agent/10 + charm: ceilometer-agent nova-compute-kvm/11: name: nova-compute-kvm/11 machine: '32' workload_version: 21.2.4 o7k_version: ussuri + subordinates: + ceilometer-agent/11: + name: ceilometer-agent/11 + charm: ceilometer-agent nova-compute-kvm/2: name: nova-compute-kvm/2 machine: '23' workload_version: 21.2.4 o7k_version: ussuri + subordinates: + ceilometer-agent/2: + name: ceilometer-agent/2 + charm: ceilometer-agent nova-compute-kvm/3: name: nova-compute-kvm/3 machine: '24' workload_version: 21.2.4 o7k_version: ussuri + subordinates: + ceilometer-agent/3: + name: ceilometer-agent/3 + charm: ceilometer-agent nova-compute-kvm/4: name: nova-compute-kvm/4 machine: '25' workload_version: 21.2.4 o7k_version: ussuri + subordinates: + ceilometer-agent/4: + name: ceilometer-agent/4 + charm: ceilometer-agent nova-compute-kvm/5: name: nova-compute-kvm/5 machine: '26' workload_version: 21.2.4 o7k_version: ussuri + subordinates: + ceilometer-agent/5: + name: ceilometer-agent/5 + charm: ceilometer-agent nova-compute-kvm/6: name: nova-compute-kvm/6 machine: '27' workload_version: 21.2.4 o7k_version: ussuri + subordinates: + ceilometer-agent/6: + name: ceilometer-agent/6 + charm: ceilometer-agent nova-compute-kvm/7: name: nova-compute-kvm/7 machine: '28' workload_version: 21.2.4 o7k_version: ussuri + subordinates: + ceilometer-agent/7: + name: ceilometer-agent/7 + charm: ceilometer-agent nova-compute-kvm/8: name: nova-compute-kvm/8 machine: '29' workload_version: 21.2.4 o7k_version: ussuri + subordinates: + ceilometer-agent/8: + name: ceilometer-agent/8 + charm: ceilometer-agent nova-compute-kvm/9: name: nova-compute-kvm/9 machine: '30' workload_version: 21.2.4 o7k_version: ussuri + subordinates: + ceilometer-agent/9: + name: ceilometer-agent/9 + charm: ceilometer-agent machines: '21': id: '21' diff --git a/tests/mocked_plans/sample_plans/9eb9af6a-b919-4cf9-8f2f-9df16a1556be.yaml b/tests/mocked_plans/sample_plans/9eb9af6a-b919-4cf9-8f2f-9df16a1556be.yaml index 45f30d77..2b4393ad 100644 --- a/tests/mocked_plans/sample_plans/9eb9af6a-b919-4cf9-8f2f-9df16a1556be.yaml +++ b/tests/mocked_plans/sample_plans/9eb9af6a-b919-4cf9-8f2f-9df16a1556be.yaml @@ -342,6 +342,9 @@ plan: | Enable nova-compute scheduler from unit: 'nova-compute/0' Enable nova-compute scheduler from unit: 'nova-compute/2' Enable nova-compute scheduler from unit: 'nova-compute/3' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/0' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/2' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/3' Wait for up to 2400s for model '9eb9af6a-b919-4cf9-8f2f-9df16a1556be' to reach the idle state Verify that the workload of 'nova-compute' has been upgraded on units: nova-compute/0, nova-compute/2, nova-compute/3 Upgrade plan for [nova-compute/1, nova-compute/6, nova-compute/8] in 'az3' to 'victoria' @@ -374,6 +377,9 @@ plan: | Enable nova-compute scheduler from unit: 'nova-compute/1' Enable nova-compute scheduler from unit: 'nova-compute/6' Enable nova-compute scheduler from unit: 'nova-compute/8' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/1' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/6' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/8' Wait for up to 2400s for model '9eb9af6a-b919-4cf9-8f2f-9df16a1556be' to reach the idle state Verify that the workload of 'nova-compute' has been upgraded on units: nova-compute/1, nova-compute/6, nova-compute/8 Upgrade plan for [nova-compute/4, nova-compute/5, nova-compute/7] in 'az2' to 'victoria' @@ -406,6 +412,9 @@ plan: | Enable nova-compute scheduler from unit: 'nova-compute/4' Enable nova-compute scheduler from unit: 'nova-compute/5' Enable nova-compute scheduler from unit: 'nova-compute/7' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/4' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/5' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/7' Wait for up to 2400s for model '9eb9af6a-b919-4cf9-8f2f-9df16a1556be' to reach the idle state Verify that the workload of 'nova-compute' has been upgraded on units: nova-compute/4, nova-compute/5, nova-compute/7 Remaining Data Plane principal(s) upgrade plan @@ -5305,46 +5314,82 @@ applications: machine: '0' workload_version: 21.2.4 o7k_version: ussuri + subordinates: + ceilometer-agent/0: + name: ceilometer-agent/0 + charm: ceilometer-agent nova-compute/1: name: nova-compute/1 machine: '1' workload_version: 21.2.4 o7k_version: ussuri + subordinates: + ceilometer-agent/1: + name: ceilometer-agent/1 + charm: ceilometer-agent nova-compute/2: name: nova-compute/2 machine: '2' workload_version: 21.2.4 o7k_version: ussuri + subordinates: + ceilometer-agent/2: + name: ceilometer-agent/2 + charm: ceilometer-agent nova-compute/3: name: nova-compute/3 machine: '3' workload_version: 21.2.4 o7k_version: ussuri + subordinates: + ceilometer-agent/3: + name: ceilometer-agent/3 + charm: ceilometer-agent nova-compute/4: name: nova-compute/4 machine: '4' workload_version: 21.2.4 o7k_version: ussuri + subordinates: + ceilometer-agent/4: + name: ceilometer-agent/4 + charm: ceilometer-agent nova-compute/5: name: nova-compute/5 machine: '5' workload_version: 21.2.4 o7k_version: ussuri + subordinates: + ceilometer-agent/5: + name: ceilometer-agent/5 + charm: ceilometer-agent nova-compute/6: name: nova-compute/6 machine: '6' workload_version: 21.2.4 o7k_version: ussuri + subordinates: + ceilometer-agent/6: + name: ceilometer-agent/6 + charm: ceilometer-agent nova-compute/7: name: nova-compute/7 machine: '7' workload_version: 21.2.4 o7k_version: ussuri + subordinates: + ceilometer-agent/7: + name: ceilometer-agent/7 + charm: ceilometer-agent nova-compute/8: name: nova-compute/8 machine: '8' workload_version: 21.2.4 o7k_version: ussuri + subordinates: + ceilometer-agent/8: + name: ceilometer-agent/8 + charm: ceilometer-agent machines: '5': id: '5' diff --git a/tests/mocked_plans/sample_plans/base.yaml b/tests/mocked_plans/sample_plans/base.yaml index 33133329..b98ad72f 100644 --- a/tests/mocked_plans/sample_plans/base.yaml +++ b/tests/mocked_plans/sample_plans/base.yaml @@ -33,6 +33,7 @@ plan: | ├── Upgrade the unit: 'nova-compute/0' ├── Resume the unit: 'nova-compute/0' Enable nova-compute scheduler from unit: 'nova-compute/0' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/0' Wait for up to 2400s for model 'base' to reach the idle state Verify that the workload of 'nova-compute' has been upgraded on units: nova-compute/0 Remaining Data Plane principal(s) upgrade plan @@ -133,6 +134,10 @@ applications: machine: '1' workload_version: 21.0.0 o7k_version: ussuri + subordinates: + ceilometer-agent/0: + name: ceilometer-agent/0 + charm: ceilometer-agent machines: '1': id: '1' diff --git a/tests/mocked_plans/utils.py b/tests/mocked_plans/utils.py index 11f16e10..b61fcf40 100644 --- a/tests/mocked_plans/utils.py +++ b/tests/mocked_plans/utils.py @@ -18,7 +18,7 @@ import yaml -from cou.utils.juju_utils import Application, Machine, Model, Unit +from cou.utils.juju_utils import Application, Machine, Model, SubordinateUnit, Unit from tests.unit.utils import dedent_plan @@ -64,7 +64,18 @@ def parse_sample_plan_file(source: Path) -> tuple[Model, str]: series=app_data["series"], subordinate_to=app_data["subordinate_to"], units={ - name: Unit(name, machines[unit["machine"]], unit["workload_version"]) + name: Unit( + name, + machines[unit["machine"]], + unit["workload_version"], + [ + SubordinateUnit( + subordinate["name"], + subordinate["charm"], + ) + for _, subordinate in unit.get("subordinates", {}).items() + ], + ) for name, unit in app_data["units"].items() }, workload_version=app_data["workload_version"], diff --git a/tests/unit/apps/test_core.py b/tests/unit/apps/test_core.py index ae1d3bdf..f5a9d0d8 100644 --- a/tests/unit/apps/test_core.py +++ b/tests/unit/apps/test_core.py @@ -32,7 +32,7 @@ ) from cou.utils import app_utils from cou.utils import nova_compute as nova_compute_utils -from cou.utils.juju_utils import Unit +from cou.utils.juju_utils import SubordinateUnit, Unit from cou.utils.openstack import OpenStackRelease from tests.unit.utils import assert_steps, dedent_plan, generate_cou_machine @@ -631,7 +631,14 @@ def test_nova_compute_pre_upgrade_steps( @patch("cou.apps.base.OpenStackApplication._get_wait_step") @patch("cou.apps.base.OpenStackApplication._get_reached_expected_target_step") @patch("cou.apps.core.NovaCompute._get_enable_scheduler_step") -def test_nova_compute_post_upgrade_steps(mock_enable, mock_expected_target, mock_wait_step, model): +@patch("cou.apps.core.NovaCompute._get_restart_subordinate_services_steps") +def test_nova_compute_post_upgrade_steps( + mock_restart_subordinate, + mock_enable, + mock_expected_target, + mock_wait_step, + model, +): app = _generate_nova_compute_app(model) target = OpenStackRelease("victoria") units = list(app.units.values()) @@ -640,6 +647,7 @@ def test_nova_compute_post_upgrade_steps(mock_enable, mock_expected_target, mock mock_enable.assert_called_once_with(units) mock_expected_target.assert_called_once_with(target, units) mock_wait_step.assert_called_once_with() + mock_restart_subordinate.assert_called_once_with(units) @pytest.mark.parametrize("force", [True, False]) @@ -708,6 +716,35 @@ def test_nova_compute_get_enable_scheduler_step(model, units): assert app._get_enable_scheduler_step(units_selected) == expected_step +@pytest.mark.parametrize( + "units", + [ + ["nova-compute/0"], + ["nova-compute/0", "nova-compute/1"], + ["nova-compute/0", "nova-compute/1", "nova-compute/2"], + ], +) +def test_nova_compute_get_restart_subordinate_services_steps(model, units): + app = _generate_nova_compute_app(model) + units_selected = [app.units[unit] for unit in units] + assert app._get_restart_subordinate_services_steps(units_selected) == [ + PostUpgradeStep( + description=( + "Restart service ceilometer-agent-compute " + f"for subordinate unit: '{unit.subordinates[0].name}'" + ), + coro=model.run_on_unit( + unit_name=unit.subordinates[0].name, + command=( + "systemctl is-active --quiet ceilometer-agent-compute" + " || systemctl restart ceilometer-agent-compute" + ), + ), + ) + for unit in units_selected + ] + + def test_nova_compute_get_enable_scheduler_step_no_units(model): """Enable the scheduler on all units if no units are passed.""" app = _generate_nova_compute_app(model) @@ -770,7 +807,12 @@ def _generate_nova_compute_app(model): channel = "ussuri/stable" units = { - f"nova-compute/{unit_num}": Unit(f"nova-compute/{unit_num}", MagicMock(), "21.0.1") + f"nova-compute/{unit_num}": Unit( + f"nova-compute/{unit_num}", + MagicMock(), + "21.0.1", + [SubordinateUnit("ceilometer-agent/{unit_num}", "ceilometer-agent")], + ) for unit_num in range(3) } app = NovaCompute( @@ -816,6 +858,9 @@ def test_nova_compute_upgrade_plan(model): Enable nova-compute scheduler from unit: 'nova-compute/0' Enable nova-compute scheduler from unit: 'nova-compute/1' Enable nova-compute scheduler from unit: 'nova-compute/2' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/0' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/1' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/2' Wait for up to 2400s for model 'test_model' to reach the idle state Verify that the workload of 'nova-compute' has been upgraded on units: nova-compute/0, nova-compute/1, nova-compute/2 """ # noqa: E501 line too long @@ -826,6 +871,9 @@ def test_nova_compute_upgrade_plan(model): name=f"nova-compute/{unit}", workload_version="21.0.0", machine=machines[f"{unit}"], + subordinates=[ + SubordinateUnit(name=f"ceilometer-agent/{unit}", charm="ceilometer-agent"), + ], ) for unit in range(3) } @@ -845,7 +893,6 @@ def test_nova_compute_upgrade_plan(model): ) plan = nova_compute.generate_upgrade_plan(target, False) - assert str(plan) == exp_plan @@ -869,6 +916,7 @@ def test_nova_compute_upgrade_plan_single_unit(model): ├── Upgrade the unit: 'nova-compute/0' ├── Resume the unit: 'nova-compute/0' Enable nova-compute scheduler from unit: 'nova-compute/0' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/0' Wait for up to 2400s for model 'test_model' to reach the idle state Verify that the workload of 'nova-compute' has been upgraded on units: nova-compute/0 """ @@ -879,6 +927,9 @@ def test_nova_compute_upgrade_plan_single_unit(model): name=f"nova-compute/{unit}", workload_version="21.0.0", machine=machines[f"{unit}"], + subordinates=[ + SubordinateUnit(name=f"ceilometer-agent/{unit}", charm="ceilometer-agent"), + ], ) for unit in range(3) } diff --git a/tests/unit/steps/test_analyze.py b/tests/unit/steps/test_analyze.py index 50dde2d9..3ab96a3e 100644 --- a/tests/unit/steps/test_analyze.py +++ b/tests/unit/steps/test_analyze.py @@ -51,16 +51,19 @@ def test_analysis_dump(model): machine: '0' workload_version: 17.0.1 o7k_version: ussuri + subordinates: {} keystone/1: name: keystone/1 machine: '1' workload_version: 17.0.1 o7k_version: ussuri + subordinates: {} keystone/2: name: keystone/2 machine: '2' workload_version: 17.0.1 o7k_version: ussuri + subordinates: {} machines: '0': id: '0' @@ -93,16 +96,19 @@ def test_analysis_dump(model): machine: '0' workload_version: 16.4.2 o7k_version: ussuri + subordinates: {} cinder/1: name: cinder/1 machine: '1' workload_version: 16.4.2 o7k_version: ussuri + subordinates: {} cinder/2: name: cinder/2 machine: '2' workload_version: 16.4.2 o7k_version: ussuri + subordinates: {} machines: '0': id: '0' @@ -135,6 +141,7 @@ def test_analysis_dump(model): machine: '0' workload_version: '3.8' o7k_version: yoga + subordinates: {} machines: '0': id: '0' diff --git a/tests/unit/steps/test_hypervisor.py b/tests/unit/steps/test_hypervisor.py index 76df230c..232edf37 100644 --- a/tests/unit/steps/test_hypervisor.py +++ b/tests/unit/steps/test_hypervisor.py @@ -25,7 +25,7 @@ UpgradeStep, ) from cou.steps.hypervisor import AZs, HypervisorGroup, HypervisorUpgradePlanner -from cou.utils.juju_utils import Application, Machine, Unit +from cou.utils.juju_utils import Application, Machine, SubordinateUnit, Unit from cou.utils.openstack import OpenStackRelease from tests.unit.utils import dedent_plan, generate_cou_machine @@ -420,6 +420,7 @@ def test_hypervisor_upgrade_plan(model): Wait for up to 300s for app 'cinder' to reach the idle state Verify that the workload of 'cinder' has been upgraded on units: cinder/0 Enable nova-compute scheduler from unit: 'nova-compute/0' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/0' Wait for up to 2400s for model 'test_model' to reach the idle state Verify that the workload of 'nova-compute' has been upgraded on units: nova-compute/0 Upgrade plan for [nova-compute/1] in 'az-1' to 'victoria' @@ -437,6 +438,7 @@ def test_hypervisor_upgrade_plan(model): ├── Upgrade the unit: 'nova-compute/1' ├── Resume the unit: 'nova-compute/1' Enable nova-compute scheduler from unit: 'nova-compute/1' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/1' Wait for up to 2400s for model 'test_model' to reach the idle state Verify that the workload of 'nova-compute' has been upgraded on units: nova-compute/1 Upgrade plan for [nova-compute/2] in 'az-2' to 'victoria' @@ -454,6 +456,7 @@ def test_hypervisor_upgrade_plan(model): ├── Upgrade the unit: 'nova-compute/2' ├── Resume the unit: 'nova-compute/2' Enable nova-compute scheduler from unit: 'nova-compute/2' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/2' Wait for up to 2400s for model 'test_model' to reach the idle state Verify that the workload of 'nova-compute' has been upgraded on units: nova-compute/2 """ @@ -498,6 +501,9 @@ def test_hypervisor_upgrade_plan(model): name=f"nova-compute/{unit}", workload_version="21.0.0", machine=machines[f"{unit}"], + subordinates=[ + SubordinateUnit(name=f"ceilometer-agent/{unit}", charm="ceilometer-agent") + ], ) for unit in range(3) }, @@ -547,6 +553,7 @@ def test_hypervisor_upgrade_plan_single_machine(model): Wait for up to 300s for app 'cinder' to reach the idle state Verify that the workload of 'cinder' has been upgraded on units: cinder/0 Enable nova-compute scheduler from unit: 'nova-compute/0' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/0' Wait for up to 2400s for model 'test_model' to reach the idle state Verify that the workload of 'nova-compute' has been upgraded on units: nova-compute/0 """ @@ -591,6 +598,9 @@ def test_hypervisor_upgrade_plan_single_machine(model): name=f"nova-compute/{unit}", workload_version="21.0.0", machine=machines[f"{unit}"], + subordinates=[ + SubordinateUnit(name=f"ceilometer-agent/{unit}", charm="ceilometer-agent"), + ], ) for unit in range(3) }, @@ -639,6 +649,7 @@ def test_hypervisor_upgrade_plan_some_units_upgraded(model): Wait for up to 300s for app 'cinder' to reach the idle state Verify that the workload of 'cinder' has been upgraded on units: cinder/2 Enable nova-compute scheduler from unit: 'nova-compute/2' + Restart service ceilometer-agent-compute for subordinate unit: 'ceilometer-agent/2' Wait for up to 2400s for model 'test_model' to reach the idle state Verify that the workload of 'nova-compute' has been upgraded on units: nova-compute/2 """ @@ -701,16 +712,25 @@ def test_hypervisor_upgrade_plan_some_units_upgraded(model): name="nova-compute/0", workload_version="22.0.0", machine=machines["0"], + subordinates=[ + SubordinateUnit(name="ceilometer-agent/0", charm="ceilometer-agent") + ], ), "nova-compute/1": Unit( name="nova-compute/1", workload_version="22.0.0", machine=machines["1"], + subordinates=[ + SubordinateUnit(name="ceilometer-agent/1", charm="ceilometer-agent") + ], ), "nova-compute/2": Unit( name="nova-compute/2", workload_version="21.0.0", machine=machines["2"], + subordinates=[ + SubordinateUnit(name="ceilometer-agent/2", charm="ceilometer-agent") + ], ), }, workload_version="22.0.0", diff --git a/tests/unit/utils/test_juju_utils.py b/tests/unit/utils/test_juju_utils.py index 54aafad6..6a254076 100644 --- a/tests/unit/utils/test_juju_utils.py +++ b/tests/unit/utils/test_juju_utils.py @@ -584,10 +584,10 @@ async def test_get_machines(mocked_model): } mocked_model.machines = {f"{i}": _generate_juju_machine(f"{i}") for i in range(3)} mocked_model.units = { - "my_app1/0": _generate_juju_unit("my_app1", "0"), - "my_app1/1": _generate_juju_unit("my_app1", "1"), - "my_app1/2": _generate_juju_unit("my_app1", "2"), - "my_app2/0": _generate_juju_unit("my_app2", "0"), + "my_app1/0": _generate_juju_unit("my_app1", "0", "0"), + "my_app1/1": _generate_juju_unit("my_app1", "1", "1"), + "my_app1/2": _generate_juju_unit("my_app1", "2", "2"), + "my_app2/0": _generate_juju_unit("my_app2", "0", "0"), } mocked_model.applications = { "my_app1": _generate_juju_app("app1"), @@ -600,9 +600,10 @@ async def test_get_machines(mocked_model): assert machines == expected_machines -def _generate_juju_unit(app: str, machine_id: str) -> MagicMock: +def _generate_juju_unit(app: str, unit_id: str, machine_id: str) -> MagicMock: unit = MagicMock(set=Unit)() unit.application = app + unit.name = f"{app}/{unit_id}" unit.machine.id = machine_id return unit @@ -625,10 +626,17 @@ def _generate_juju_machine(machine_id: str) -> MagicMock: return machine -def _generate_unit_status(app: str, unit_id: int, machine_id: str) -> tuple[str, MagicMock]: +def _generate_unit_status( + app: str, + unit_id: int, + machine_id: str, + subordinates: dict[str, MagicMock] = {}, +) -> tuple[str, MagicMock]: """Generate unit name and status.""" status = MagicMock(spec_set=UnitStatus)() status.machine = machine_id + status.subordinates = subordinates + status.charm = app return f"{app}/{unit_id}", status @@ -678,21 +686,25 @@ async def test_get_applications(mock_get_machines, mock_get_status, mocked_model } exp_units_from_status = { "app1": dict([_generate_unit_status("app1", i, f"{i}") for i in range(3)]), - "app2": dict([_generate_unit_status("app2", 0, "0")]), + "app2": dict( + [_generate_unit_status("app2", 0, "0", dict([_generate_unit_status("app4", 0, "")]))] + ), "app3": dict([_generate_unit_status("app3", 0, "1")]), "app4": {}, # subordinate application has no units defined in juju status } exp_units = { - "app1": [_generate_juju_unit("app1", f"{i}") for i in range(3)], - "app2": [_generate_juju_unit("app2", "0")], - "app3": [_generate_juju_unit("app3", "0")], - "app4": [_generate_juju_unit("app4", "0")], + "app1": [_generate_juju_unit("app1", f"{i}", f"{i}") for i in range(3)], + "app2": [_generate_juju_unit("app2", "0", "0")], + "app3": [_generate_juju_unit("app3", "0", "0")], + "app4": [_generate_juju_unit("app4", "0", "0")], } mocked_model.applications = {app: MagicMock(spec_set=Application)() for app in exp_apps} + for app in exp_apps: mocked_model.applications[app].get_config = AsyncMock() mocked_model.applications[app].units = exp_units[app] + mocked_model.applications[app].charm_name = app full_status_apps = {app: _generate_app_status(exp_units_from_status[app]) for app in exp_apps} mock_get_status.return_value.applications = full_status_apps @@ -712,7 +724,18 @@ async def test_get_applications(mock_get_machines, mock_get_status, mocked_model series=status.series, subordinate_to=status.subordinate_to, units={ - name: juju_utils.Unit(name, exp_machines[unit.machine], unit.workload_version) + name: juju_utils.Unit( + name, + exp_machines[unit.machine], + unit.workload_version, + [ + juju_utils.SubordinateUnit( + subordinate_name, + subordinate.charm, + ) + for subordinate_name, subordinate in unit.subordinates.items() + ], + ) for name, unit in exp_units_from_status[app].items() }, workload_version=status.workload_version, @@ -746,3 +769,8 @@ async def test_get_applications(mock_get_machines, mock_get_status, mocked_model def test_unit_repr(): unit = juju_utils.Unit(name="foo/0", machine=MagicMock(), workload_version="1") assert repr(unit) == "foo/0" + + +def test_suborinate_unit_repr(): + unit = juju_utils.SubordinateUnit(name="foo/0", charm="foo") + assert repr(unit) == "foo/0"