diff --git a/cou/apps/base.py b/cou/apps/base.py index 86189f51..95466560 100644 --- a/cou/apps/base.py +++ b/cou/apps/base.py @@ -68,10 +68,6 @@ class OpenStackApplication(Application): wait_timeout: int = field(default=STANDARD_IDLE_TIMEOUT, init=False) wait_for_model: bool = field(default=False, init=False) # waiting only for application itself - def __post_init__(self) -> None: - """Initialize the Application dataclass.""" - self._verify_channel() - def __hash__(self) -> int: """Hash magic method for Application. @@ -135,27 +131,6 @@ def __str__(self) -> str: return yaml.dump(summary, sort_keys=False) - def _verify_channel(self) -> None: - """Verify app channel from current data. - - :raises ApplicationError: Exception raised when channel is not a valid OpenStack channel. - """ - if ( - self.is_from_charm_store - or self.channel == LATEST_STABLE - or self.is_valid_track(self.channel) - ): - logger.debug("%s app has proper channel %s", self.name, self.channel) - return - - raise ApplicationError( - f"Channel: {self.channel} for charm '{self.charm}' on series " - f"'{self.series}' is currently not supported in this tool. Please take a look at the " - "documentation: " - "https://docs.openstack.org/charm-guide/latest/project/charm-delivery.html to see if " - "you are using the right track." - ) - @property def apt_source_codename(self) -> Optional[OpenStackRelease]: """Identify the OpenStack release set on "openstack-origin" or "source" config. @@ -367,11 +342,12 @@ def upgrade_plan_sanity_checks( :type target: OpenStackRelease :param units: Units to generate upgrade plan, defaults to None :type units: Optional[list[Unit]], optional - :raises ApplicationError: When enable-auto-restarts is not enabled. + :raises ApplicationError: When application is wrongly configured. :raises HaltUpgradePlanGeneration: When the application halt the upgrade plan generation. :raises MismatchedOpenStackVersions: When the units of the app are running different OpenStack versions. """ + self._check_channel() self._check_application_target(target) self._check_mismatched_versions(units) self._check_auto_restarts() @@ -760,6 +736,27 @@ def _get_wait_step(self) -> PostUpgradeStep: coro=self.model.wait_for_active_idle(self.wait_timeout, apps=apps), ) + def _check_channel(self) -> None: + """Check app channel from current data. + + :raises ApplicationError: Exception raised when channel is not a valid OpenStack channel. + """ + if ( + self.is_from_charm_store + or self.channel == LATEST_STABLE + or self.is_valid_track(self.channel) + ): + logger.debug("%s app has proper channel %s", self.name, self.channel) + return + + raise ApplicationError( + f"Channel: {self.channel} for charm '{self.charm}' on series " + f"'{self.series}' is currently not supported in this tool. Please take a look at the " + "documentation: " + "https://docs.openstack.org/charm-guide/latest/project/charm-delivery.html to see if " + "you are using the right track." + ) + def _check_auto_restarts(self) -> None: """Check if enable-auto-restarts is enabled. diff --git a/tests/unit/apps/test_auxiliary.py b/tests/unit/apps/test_auxiliary.py index 9a6afb42..9b138472 100644 --- a/tests/unit/apps/test_auxiliary.py +++ b/tests/unit/apps/test_auxiliary.py @@ -341,61 +341,60 @@ def test_auxiliary_upgrade_plan_unknown_track(model): "to see if you are using the right track." ) machines = {"0": MagicMock(spec_set=Machine)} + app = RabbitMQServer( + name="rabbitmq-server", + can_upgrade_to="3.9/stable", + charm="rabbitmq-server", + channel=channel, + config={"source": {"value": "distro"}}, + machines=machines, + model=model, + origin="ch", + series="focal", + subordinate_to=[], + units={ + "rabbitmq-server/0": Unit( + name="rabbitmq-server/0", + workload_version="3.8", + machine=machines["0"], + ) + }, + workload_version="3.8", + ) + with pytest.raises(ApplicationError, match=exp_msg): - RabbitMQServer( - name="rabbitmq-server", - can_upgrade_to="3.9/stable", - charm="rabbitmq-server", - channel=channel, - config={"source": {"value": "distro"}}, - machines=machines, - model=model, - origin="ch", - series="focal", - subordinate_to=[], - units={ - "rabbitmq-server/0": Unit( - name="rabbitmq-server/0", - workload_version="3.8", - machine=machines["0"], - ) - }, - workload_version="3.8", - ) + app._check_channel() def test_auxiliary_app_unknown_version_raise_ApplicationError(model): - """Test auxiliary upgrade plan with unknown version.""" + """Test auxiliary application with unknown workload version.""" version = "80.5" charm = "rabbitmq-server" exp_msg = f"'{charm}' with workload version {version} has no compatible OpenStack release." machines = {"0": MagicMock(spec_set=Machine)} + unit = Unit(name=f"{charm}/0", workload_version=version, machine=machines["0"]) + app = RabbitMQServer( + name=charm, + can_upgrade_to="3.8/stable", + charm=charm, + channel="3.8/stable", + config={"source": {"value": "distro"}}, + machines=machines, + model=model, + origin="ch", + series="focal", + subordinate_to=[], + units={unit.name: unit}, + workload_version=version, + ) + with pytest.raises(ApplicationError, match=exp_msg): - RabbitMQServer( - name=charm, - can_upgrade_to="3.8/stable", - charm=charm, - channel="3.8/stable", - config={"source": {"value": "distro"}}, - machines=machines, - model=model, - origin="ch", - series="focal", - subordinate_to=[], - units={ - f"{charm}/0": Unit( - name=f"{charm}/0", - workload_version=version, - machine=machines["0"], - ) - }, - workload_version=version, - ) + app.get_latest_os_version(unit) def test_auxiliary_raise_error_unknown_series(model): - """Test auxiliary upgrade plan with unknown series.""" + """Test auxiliary application with unknown series.""" series = "foo" channel = "3.8/stable" exp_msg = ( @@ -405,27 +404,28 @@ def test_auxiliary_raise_error_unknown_series(model): "to see if you are using the right track." ) machines = {"0": MagicMock(spec_set=Machine)} + app = RabbitMQServer( + name="rabbitmq-server", + can_upgrade_to="3.9/stable", + charm="rabbitmq-server", + channel=channel, + config={"source": {"value": "distro"}}, + machines=machines, + model=model, + origin="ch", + series=series, + subordinate_to=[], + units={ + "rabbitmq-server/0": Unit( + name="rabbitmq-server/0", + workload_version="3.8", + machine=machines["0"], + ) + }, + workload_version="3.8", + ) with pytest.raises(ApplicationError, match=exp_msg): - RabbitMQServer( - name="rabbitmq-server", - can_upgrade_to="3.9/stable", - charm="rabbitmq-server", - channel=channel, - config={"source": {"value": "distro"}}, - machines=machines, - model=model, - origin="ch", - series=series, - subordinate_to=[], - units={ - "rabbitmq-server/0": Unit( - name="rabbitmq-server/0", - workload_version="3.8", - machine=machines["0"], - ) - }, - workload_version="3.8", - ) + app._check_channel() @patch("cou.apps.core.OpenStackApplication.current_os_release") @@ -435,29 +435,36 @@ def test_auxiliary_raise_error_os_not_on_lookup(current_os_release, model): Using OpenStack release version that is not on openstack_to_track_mapping.csv table. """ current_os_release.return_value = OpenStackRelease("diablo") - + exp_error_msg = ( + "Channel: 3.8/stable for charm 'rabbitmq-server' on series 'focal' is currently not " + "supported in this tool. Please take a look at the documentation: " + "https://docs.openstack.org/charm-guide/latest/project/charm-delivery.html to see if you " + "are using the right track." + ) machines = {"0": MagicMock(spec_set=Machine)} - with pytest.raises(ApplicationError): - RabbitMQServer( - name="rabbitmq-server", - can_upgrade_to="", - charm="rabbitmq-server", - channel="3.8/stable", - config={"source": {"value": "distro"}}, - machines=machines, - model=model, - origin="ch", - series="focal", - subordinate_to=[], - units={ - "rabbitmq-server/0": Unit( - name="rabbitmq-server/0", - workload_version="3.8", - machine=machines["0"], - ) - }, - workload_version="3.8", - ) + app = RabbitMQServer( + name="rabbitmq-server", + can_upgrade_to="", + charm="rabbitmq-server", + channel="3.8/stable", + config={"source": {"value": "distro"}}, + machines=machines, + model=model, + origin="ch", + series="focal", + subordinate_to=[], + units={ + "rabbitmq-server/0": Unit( + name="rabbitmq-server/0", + workload_version="3.8", + machine=machines["0"], + ) + }, + workload_version="3.8", + ) + + with pytest.raises(ApplicationError, match=exp_error_msg): + app._check_channel() def test_auxiliary_raise_halt_upgrade(model): @@ -840,28 +847,29 @@ def test_ovn_no_compatible_os_release(channel, model): "https://docs.openstack.org/charm-guide/latest/project/charm-delivery.html " "to see if you are using the right track." ) + app = OvnPrincipal( + name=charm, + can_upgrade_to="quincy/stable", + charm=charm, + channel=channel, + config={"source": {"value": "distro"}}, + machines=machines, + model=model, + origin="ch", + series="focal", + subordinate_to=[], + units={ + f"{charm}/0": Unit( + name=f"{charm}/0", + workload_version="22.03", + machine=machines["0"], + ) + }, + workload_version="22.03", + ) with pytest.raises(ApplicationError, match=exp_msg): - OvnPrincipal( - name=charm, - can_upgrade_to="quincy/stable", - charm=charm, - channel=channel, - config={"source": {"value": "distro"}}, - machines=machines, - model=model, - origin="ch", - series="focal", - subordinate_to=[], - units={ - f"{charm}/0": Unit( - name=f"{charm}/0", - workload_version="22.03", - machine=machines["0"], - ) - }, - workload_version="22.03", - ) + app._check_channel() def test_ovn_principal_upgrade_plan(model): @@ -1280,10 +1288,9 @@ def test_ceph_osd_upgrade_plan(model): ), ], ) -@patch("cou.apps.auxiliary.AuxiliaryApplication._verify_channel", return_value=None) @patch("cou.apps.auxiliary.TRACK_TO_OPENSTACK_MAPPING") def test_need_current_channel_refresh_auxiliary( - mock_track_os_mapping, _, model, can_upgrade_to, compatible_os_releases, exp_result + mock_track_os_mapping, model, can_upgrade_to, compatible_os_releases, exp_result ): mock_track_os_mapping.__getitem__.return_value = compatible_os_releases target = OpenStackRelease("victoria") diff --git a/tests/unit/apps/test_base.py b/tests/unit/apps/test_base.py index ea19492a..f35be50e 100644 --- a/tests/unit/apps/test_base.py +++ b/tests/unit/apps/test_base.py @@ -28,7 +28,6 @@ from tests.unit.utils import assert_steps -@patch("cou.apps.base.OpenStackApplication._verify_channel", return_value=None) def test_openstack_application_magic_functions(model): """Test OpenStackApplication magic functions, like __hash__, __eq__.""" app = OpenStackApplication( @@ -52,7 +51,6 @@ def test_openstack_application_magic_functions(model): assert app != "test-app" -@patch("cou.apps.base.OpenStackApplication._verify_channel", return_value=None) @patch("cou.utils.openstack.OpenStackCodenameLookup.find_compatible_versions") def test_application_get_latest_os_version_failed(mock_find_compatible_versions, model): charm = "app" @@ -298,9 +296,40 @@ def test_get_reached_expected_target_step(mock_workload_upgrade, units, model): mock_workload_upgrade.assert_has_calls(expected_calls) +@pytest.mark.parametrize("origin", ["cs", "ch"]) +@patch("cou.apps.base.OpenStackApplication.is_valid_track", return_value=True) +def test_check_channel(_, origin): + """Test function to verify validity of the charm channel.""" + app_name = "app" + app = OpenStackApplication( + app_name, "", app_name, "stable", {}, {}, MagicMock(), origin, "focal", [], {}, "1" + ) + + app._check_channel() + + +@patch("cou.apps.base.OpenStackApplication.is_valid_track", return_value=False) +def test_check_channel_error(_): + """Test function to verify validity of the charm channel when it's not valid.""" + name = "app" + channel = "stable" + series = "focal" + exp_error_msg = ( + f"Channel: {channel} for charm '{name}' on series '{series}' is currently not supported " + "in this tool. Please take a look at the documentation: " + "https://docs.openstack.org/charm-guide/latest/project/charm-delivery.html to see if you " + "are using the right track." + ) + app = OpenStackApplication( + name, "", name, channel, {}, {}, MagicMock(), "ch", series, [], {}, "1" + ) + + with pytest.raises(ApplicationError, match=exp_error_msg): + app._check_channel() + + @pytest.mark.parametrize("config", ({}, {"enable-auto-restarts": {"value": True}})) -@patch("cou.apps.base.OpenStackApplication._verify_channel", return_value=None) -def test_check_auto_restarts(_, config): +def test_check_auto_restarts(config): """Test function to verify that enable-auto-restarts is disabled.""" app_name = "app" app = OpenStackApplication( @@ -310,8 +339,7 @@ def test_check_auto_restarts(_, config): app._check_auto_restarts() -@patch("cou.apps.base.OpenStackApplication._verify_channel", return_value=None) -def test_check_auto_restarts_error(_): +def test_check_auto_restarts_error(): """Test function to verify that enable-auto-restarts is disabled raising error.""" app_name = "app" exp_error_msg = ( @@ -328,10 +356,9 @@ def test_check_auto_restarts_error(_): app._check_auto_restarts() -@patch("cou.apps.base.OpenStackApplication._verify_channel", return_value=None) @patch("cou.apps.base.OpenStackApplication.apt_source_codename", new_callable=PropertyMock) @patch("cou.apps.base.OpenStackApplication.current_os_release", new_callable=PropertyMock) -def test_check_application_target(current_os_release, apt_source_codename, _): +def test_check_application_target(current_os_release, apt_source_codename): """Test function to verify target.""" target = OpenStackRelease("victoria") release = OpenStackRelease("ussuri") @@ -344,10 +371,9 @@ def test_check_application_target(current_os_release, apt_source_codename, _): app._check_application_target(target) -@patch("cou.apps.base.OpenStackApplication._verify_channel", return_value=None) @patch("cou.apps.base.OpenStackApplication.apt_source_codename", new_callable=PropertyMock) @patch("cou.apps.base.OpenStackApplication.current_os_release", new_callable=PropertyMock) -def test_check_application_target_can_upgrade(current_os_release, apt_source_codename, _): +def test_check_application_target_can_upgrade(current_os_release, apt_source_codename): """Test function to verify target.""" target = OpenStackRelease("victoria") release = OpenStackRelease("ussuri") @@ -360,10 +386,9 @@ def test_check_application_target_can_upgrade(current_os_release, apt_source_cod app._check_application_target(target) -@patch("cou.apps.base.OpenStackApplication._verify_channel", return_value=None) @patch("cou.apps.base.OpenStackApplication.apt_source_codename", new_callable=PropertyMock) @patch("cou.apps.base.OpenStackApplication.current_os_release", new_callable=PropertyMock) -def test_check_application_target_error(current_os_release, apt_source_codename, _): +def test_check_application_target_error(current_os_release, apt_source_codename): """Test function to verify target raising error.""" target = OpenStackRelease("victoria") app_name = "app" diff --git a/tests/unit/apps/test_subordinate.py b/tests/unit/apps/test_subordinate.py index e6e2f1d1..28678c39 100644 --- a/tests/unit/apps/test_subordinate.py +++ b/tests/unit/apps/test_subordinate.py @@ -128,22 +128,29 @@ def test_channel_valid(model, channel): def test_channel_setter_invalid(model, channel): """Test unsuccessful validation of channel upgrade plan for SubordinateApplication.""" machines = {"0": MagicMock(spec_set=Machine)} + exp_error_msg = ( + f"Channel: {channel} for charm 'keystone-ldap' on series 'focal' is currently not " + "supported in this tool. Please take a look at the documentation: " + "https://docs.openstack.org/charm-guide/latest/project/charm-delivery.html to see if you " + "are using the right track." + ) + app = SubordinateApplication( + name="keystone-ldap", + can_upgrade_to=channel, + charm="keystone-ldap", + channel=channel, + config={}, + machines=machines, + model=model, + origin="ch", + series="focal", + subordinate_to=["nova-compute"], + units={}, + workload_version="18.1.0", + ) - with pytest.raises(ApplicationError): - SubordinateApplication( - name="keystone-ldap", - can_upgrade_to=channel, - charm="keystone-ldap", - channel=channel, - config={}, - machines=machines, - model=model, - origin="ch", - series="focal", - subordinate_to=["nova-compute"], - units={}, - workload_version="18.1.0", - ) + with pytest.raises(ApplicationError, match=exp_error_msg): + app._check_channel() @pytest.mark.parametrize(