Skip to content

Commit

Permalink
Verify nova_cloud_controller_scheduler_default_filters (#594)
Browse files Browse the repository at this point in the history
Add verify step for scheduler_default_filters for nova-cloud-controller during antelope to bobcat upgrade

ref. https://docs.openstack.org/charm-guide/latest/release-notes/2023.2-bobcat.html#nova-availabilityzonefilter-removal-in-2023-2
  • Loading branch information
chanchiwai-ray authored Oct 23, 2024
1 parent cdd59a8 commit 1f5fda8
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 11 deletions.
48 changes: 47 additions & 1 deletion cou/steps/plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from cou.commands import CONTROL_PLANE, DATA_PLANE, HYPERVISORS, CLIargs
from cou.exceptions import (
ApplicationError,
ApplicationNotFound,
COUException,
DataPlaneMachineFilterError,
HaltUpgradePlanGeneration,
Expand All @@ -55,7 +56,12 @@
from cou.steps.nova_cloud_controller import archive, purge
from cou.steps.vault import verify_vault_is_unsealed
from cou.utils import print_and_debug
from cou.utils.juju_utils import DEFAULT_TIMEOUT, Machine, Unit
from cou.utils.juju_utils import (
DEFAULT_TIMEOUT,
Machine,
Unit,
get_applications_by_charm_name,
)
from cou.utils.nova_compute import get_empty_hypervisors
from cou.utils.openstack import LTS_TO_OS_RELEASE, OpenStackRelease

Expand Down Expand Up @@ -110,6 +116,7 @@ async def verify_cloud(analysis_result: Analysis, args: CLIargs) -> None:
_verify_highest_release_achieved(analysis_result)
_verify_data_plane_ready_to_upgrade(args, analysis_result)
_verify_hypervisors_cli_input(args, analysis_result)
_verify_nova_cloud_controller_scheduler_default_filters(args, analysis_result)
await _verify_vault_is_unsealed(analysis_result)
await _verify_osd_noout_unset(analysis_result)
await _verify_model_idle(analysis_result)
Expand Down Expand Up @@ -237,6 +244,45 @@ def _verify_highest_release_achieved(analysis_result: Analysis) -> None:
)


def _verify_nova_cloud_controller_scheduler_default_filters(
args: CLIargs, analysis_result: Analysis
) -> None:
"""Verify scheduler_default_filters option is set correctly for each OpenStack release.
:param args: CLI arguments
:type args: CLIargs
:param analysis_result: Analysis result.
:type analysis_result: Analysis
"""
if args.upgrade_group not in {CONTROL_PLANE, None}:
return

try:
nova_cloud_controllers = get_applications_by_charm_name(
analysis_result.apps_control_plane, "nova-cloud-controller"
)
except ApplicationNotFound:
PlanStatus.add_message(
"Cannot find nova-cloud-controller apps. Is this a valid OpenStack cloud?",
MessageType.WARNING,
)
return

curr_release = analysis_result.current_cloud_o7k_release
for nova_cloud_controller in nova_cloud_controllers:
config = nova_cloud_controller.config.get("scheduler-default-filters", "")
if curr_release == "antelope" and "AvailabilityZoneFilter" in config:
PlanStatus.add_message(
f"Upgrade from '{curr_release}' to 'bobcat' is not possible "
"if `AvailabilityZoneFilter` is in `scheduler-default-filters` option for app: "
f"{nova_cloud_controller.name}. \n"
"This is because `AvailabilityZoneFilter` is removed from the filters: "
"https://docs.openstack.org/charm-guide/latest/release-notes/2023.2-bobcat.html"
"#nova-availabilityzonefilter-removal-in-2023-2",
MessageType.ERROR,
)


def _verify_data_plane_ready_to_upgrade(args: CLIargs, analysis_result: Analysis) -> None:
"""Verify if data plane is ready to upgrade.
Expand Down
8 changes: 8 additions & 0 deletions tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import pytest

from cou.commands import CLIargs
from cou.steps.plan import PlanStatus
from cou.utils.juju_utils import Model
from tests.unit.utils import get_charm_name, get_status

Expand Down Expand Up @@ -58,3 +59,10 @@ def cli_args() -> MagicMock:
"""
# spec_set needs an instantiated class to be strict with the fields.
return MagicMock(spec_set=CLIargs(command="plan"))()


@pytest.fixture(autouse=True)
def plan_status() -> None:
"""Get an empty PlanStatus for every test case."""
PlanStatus.error_messages = []
PlanStatus.warning_messages = []
105 changes: 95 additions & 10 deletions tests/unit/steps/test_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from cou.commands import CONTROL_PLANE, DATA_PLANE, HYPERVISORS, CLIargs
from cou.exceptions import (
ApplicationError,
ApplicationNotFound,
COUException,
DataPlaneMachineFilterError,
HaltUpgradePlanGeneration,
Expand All @@ -49,7 +50,7 @@
from cou.utils import app_utils
from cou.utils.juju_utils import Machine, Unit
from cou.utils.openstack import OpenStackRelease
from tests.unit.utils import dedent_plan, generate_cou_machine
from tests.unit.utils import dedent_plan, generate_cou_machine, get_applications


def generate_expected_upgrade_plan_principal(app, target, model):
Expand Down Expand Up @@ -487,7 +488,8 @@ async def test_generate_plan_with_warning_messages(mock_filter_hypervisors, mode

upgrade_plan = await cou_plan.generate_plan(analysis_result, cli_args)
assert str(upgrade_plan) == exp_plan
assert len(cou_plan.PlanStatus.warning_messages) == 2 # keystone warning and set_noout
assert len(cou_plan.PlanStatus.error_messages) == 1 # set_noout error
assert len(cou_plan.PlanStatus.warning_messages) == 1 # keystone mismatch warning


def test_PlanStatus_warnings_property():
Expand All @@ -510,7 +512,9 @@ def test_PlanStatus_warnings_property():
@patch("cou.steps.plan._verify_supported_series")
@patch("cou.steps.plan._verify_highest_release_achieved")
@patch("cou.steps.plan._verify_data_plane_ready_to_upgrade")
@patch("cou.steps.plan._verify_nova_cloud_controller_scheduler_default_filters")
async def test_pre_plan_sanity_checks(
mock_verify_nova_cloud_controller_scheduler_default_filters,
mock_verify_data_plane_ready_to_upgrade,
mock_verify_highest_release_achieved,
mock_verify_supported_series,
Expand All @@ -528,6 +532,9 @@ async def test_pre_plan_sanity_checks(
mock_verify_supported_series.assert_called_once_with(mock_analysis_result)
mock_verify_data_plane_ready_to_upgrade.assert_called_once_with(cli_args, mock_analysis_result)
mock_verify_hypervisors_cli_input.assert_called_once_with(cli_args, mock_analysis_result)
mock_verify_nova_cloud_controller_scheduler_default_filters.assert_called_once_with(
cli_args, mock_analysis_result
)
mock_verify_vault_is_unsealed.assert_awaited_once_with(mock_analysis_result)
mock_verify_osd_noout_unset.assert_awaited_once_with(mock_analysis_result)
mock_verify_model_idle.assert_awaited_once_with(mock_analysis_result)
Expand All @@ -539,14 +546,12 @@ async def test_pre_plan_sanity_checks(
(
OpenStackRelease("caracal"),
"noble",
"Cloud series 'noble' is not a Ubuntu LTS series supported by COU. "
"The supporting series are: focal, jammy",
"Cloud series 'noble' is not a Ubuntu LTS series supported by COU. ",
),
(
OpenStackRelease("train"),
"bionic",
"Cloud series 'bionic' is not a Ubuntu LTS series supported by COU. "
"The supporting series are: focal, jammy",
"Cloud series 'bionic' is not a Ubuntu LTS series supported by COU. ",
),
],
)
Expand All @@ -555,7 +560,7 @@ def test_verify_supported_series(o7k_release, current_series, exp_error_msg):
mock_analysis_result.current_cloud_o7k_release = o7k_release
mock_analysis_result.current_cloud_series = current_series
cou_plan._verify_supported_series(mock_analysis_result)
cou_plan.PlanStatus.error_messages[0] == exp_error_msg
assert exp_error_msg in cou_plan.PlanStatus.error_messages[0]


@pytest.mark.parametrize(
Expand All @@ -579,7 +584,87 @@ def test_verify_highest_release_achieved(o7k_release, series):
"- https://docs.openstack.org/charm-guide/latest/admin/upgrades/series-openstack.html"
)
cou_plan._verify_highest_release_achieved(mock_analysis_result)
cou_plan.PlanStatus.error_messages[0] == exp_error_msg
assert cou_plan.PlanStatus.error_messages[0] == exp_error_msg


@pytest.mark.parametrize(
"upgrade_group",
[CONTROL_PLANE, None],
)
@patch("cou.steps.plan.get_applications_by_charm_name")
def test_verify_nova_cloud_controller_scheduler_default_filters_failed(
mock_get_applications_by_charm_name, upgrade_group, cli_args
):
app_count = 2
cli_args.upgrade_group = upgrade_group
nova_cloud_controllers = get_applications("nova-cloud-controller", app_count=app_count)
for nova_cloud_controller in nova_cloud_controllers:
nova_cloud_controller.config = {"scheduler-default-filters": "AvailabilityZoneFilter"}
mock_get_applications_by_charm_name.return_value = nova_cloud_controllers
mock_analysis_result = MagicMock(spec=Analysis)()
mock_analysis_result.current_cloud_o7k_release = OpenStackRelease("antelope")
exp_error_msg = (
"Upgrade from 'antelope' to 'bobcat' is not possible "
"if `AvailabilityZoneFilter` is in `scheduler-default-filters` option"
)
cou_plan._verify_nova_cloud_controller_scheduler_default_filters(
cli_args, mock_analysis_result
)
for i in range(app_count):
assert exp_error_msg in cou_plan.PlanStatus.error_messages[i]


@pytest.mark.parametrize(
"upgrade_group",
[CONTROL_PLANE, None],
)
@patch("cou.steps.plan.get_applications_by_charm_name")
def test_verify_nova_cloud_controller_scheduler_default_filters_missing_app(
mock_get_applications_by_charm_name, upgrade_group, cli_args
):
cli_args.upgrade_group = upgrade_group
mock_get_applications_by_charm_name.side_effect = ApplicationNotFound
mock_analysis_result = MagicMock(spec=Analysis)()
mock_analysis_result.current_cloud_o7k_release = OpenStackRelease("antelope")
exp_error_msg = "Cannot find nova-cloud-controller apps"
cou_plan._verify_nova_cloud_controller_scheduler_default_filters(
cli_args, mock_analysis_result
)
assert exp_error_msg in cou_plan.PlanStatus.warning_messages[0]


@patch("cou.steps.plan.get_applications_by_charm_name")
def test_verify_nova_cloud_controller_scheduler_default_filters_skip_upgrade_group(
mock_get_applications_by_charm_name, cli_args
):
app_count = 2
cli_args.upgrade_group = DATA_PLANE
mock_get_applications_by_charm_name.return_value = get_applications(
"nova-cloud-controller", app_count=app_count
)
mock_analysis_result = MagicMock(spec=Analysis)()
mock_analysis_result.current_cloud_o7k_release = OpenStackRelease("antelope")
cou_plan._verify_nova_cloud_controller_scheduler_default_filters(
cli_args, mock_analysis_result
)
assert not cou_plan.PlanStatus.error_messages


@patch("cou.steps.plan.get_applications_by_charm_name")
def test_verify_nova_cloud_controller_scheduler_default_filters_skip_not_antelope(
mock_get_applications_by_charm_name, cli_args
):
app_count = 2
cli_args.upgrade_group = CONTROL_PLANE
mock_get_applications_by_charm_name.return_value = get_applications(
"nova-cloud-controller", app_count=app_count
)
mock_analysis_result = MagicMock(spec=Analysis)()
mock_analysis_result.current_cloud_o7k_release = OpenStackRelease("yoga") # not antelope
cou_plan._verify_nova_cloud_controller_scheduler_default_filters(
cli_args, mock_analysis_result
)
assert not cou_plan.PlanStatus.error_messages


@pytest.mark.parametrize(
Expand All @@ -588,7 +673,7 @@ def test_verify_highest_release_achieved(o7k_release, series):
(
OpenStackRelease("ussuri"),
OpenStackRelease("ussuri"),
"Please, upgrade control-plane before data-plane",
"Please upgrade control-plane before data-plane",
),
(
OpenStackRelease("ussuri"),
Expand All @@ -606,7 +691,7 @@ def test_verify_data_plane_ready_to_upgrade_error(
mock_analysis_result.min_o7k_version_control_plane = min_o7k_version_control_plane
mock_analysis_result.min_o7k_version_data_plane = min_o7k_version_data_plane
cou_plan._verify_data_plane_ready_to_upgrade(cli_args, mock_analysis_result)
cou_plan.PlanStatus.error_messages[0] == exp_error_msg
assert exp_error_msg in cou_plan.PlanStatus.error_messages[0]


@pytest.mark.parametrize("upgrade_group", [DATA_PLANE, HYPERVISORS])
Expand Down
1 change: 1 addition & 0 deletions tests/unit/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def get_applications(
app = MagicMock(spec_set=Application)()
app.name = f"{charm_name}-{i}"
app.charm = charm_name
app.config = {}
units = {}
for j in range(unit_count):
unit = MagicMock(spec_set=Unit)()
Expand Down

0 comments on commit 1f5fda8

Please sign in to comment.