diff --git a/openstack_project_manager/manage.py b/openstack_project_manager/manage.py index 496a085..448a150 100755 --- a/openstack_project_manager/manage.py +++ b/openstack_project_manager/manage.py @@ -215,6 +215,8 @@ def check_quota( project.id, **{key: quota_should_be} ) + check_bandwidth_limit(configuration, project, quotaclass) + logger.info(f"{project.name} - check compute quota") quotacompute = configuration.os_cloud.get_compute_quotas(project.id) for key in quotaclass["compute"]: @@ -270,6 +272,111 @@ def check_quota( ) +def update_bandwidth_policy_rule( + configuration: Configuration, + project: openstack.identity.v3.project.Project, + policy: openstack.network.v2.qos_policy.QoSPolicy, + direction: str, + max_kbps: int, + max_burst_kbps: int, +): + existingRules = configuration.os_cloud.list_qos_bandwidth_limit_rules( + policy.id, {"direction": direction} + ) + existingRule = existingRules[0] if len(existingRules) > 0 else None + + if max_kbps == -1 and max_burst_kbps == -1: + if existingRule: + logger.info(f"{project.name} - removing {direction} bandwidth limit rule") + configuration.os_cloud.delete_qos_bandwidth_limit_rule( + policy.id, existingRule.id + ) + return + + if not existingRule: + logger.info(f"{project.name} - creating new {direction} bandwidth limit rule") + configuration.os_cloud.create_qos_bandwidth_limit_rule( + policy.id, + max_kbps=max_kbps, + max_burst_kbps=max_burst_kbps, + direction=direction, + ) + elif ( + existingRule.max_kbps != max_kbps + or existingRule.max_burst_kbps != max_burst_kbps + ): + logger.info(f"{project.name} - updating {direction} bandwidth limit rule") + configuration.os_cloud.update_qos_bandwidth_limit_rule( + policy.id, + existingRule.id, + max_kbps=max_kbps, + max_burst_kbps=max_burst_kbps, + ) + + +def check_bandwidth_limit( + configuration: Configuration, + project: openstack.identity.v3.project.Project, + quotaclass: dict, +) -> None: + + domain = configuration.os_cloud.get_domain(name_or_id=project.domain_id) + domain_name = domain.name.lower() + + if domain_name == "default" and project.name in ["admin", "service"]: + logger.info(f"{project.name} - skip network bandwith limit policy check") + return + + logger.info(f"{project.name} - check network bandwith limit policy") + + limit_egress = -1 + limit_egress_burst = -1 + limit_ingress = -1 + limit_ingress_burst = -1 + + if "bandwidth" in quotaclass: + if "egress" in quotaclass["bandwidth"]: + limit_egress = int(quotaclass["bandwidth"]["egress"]) + if "egress_burst" in quotaclass["bandwidth"]: + limit_egress_burst = int(quotaclass["bandwidth"]["egress_burst"]) + if "ingress" in quotaclass["bandwidth"]: + limit_ingress = int(quotaclass["bandwidth"]["ingress"]) + if "ingress_burst" in quotaclass["bandwidth"]: + limit_ingress_burst = int(quotaclass["bandwidth"]["ingress_burst"]) + + existingPolicies = configuration.os_cloud.list_qos_policies( + {"name": "bw-limiter", "project_id": project.id} + ) + + if ( + limit_egress == -1 + and limit_egress_burst == -1 + and limit_ingress == -1 + and limit_ingress_burst == -1 + ): + # There are no limits defined (anymore) so we remove or skip the policy entirely + if len(existingPolicies) > 0: + logger.info(f"{project.name} - removing bandwidth limit policy") + for policy in existingPolicies: + configuration.os_cloud.delete_qos_policy(policy.id) + return + + if len(existingPolicies) == 0: + logger.info(f"{project.name} - creating new bandwidth limit policy") + policy = configuration.os_cloud.create_qos_policy( + name="bw-limiter", default=True, project_id=project.id + ) + else: + policy = existingPolicies[0] + + update_bandwidth_policy_rule( + configuration, project, policy, "egress", limit_egress, limit_egress_burst + ) + update_bandwidth_policy_rule( + configuration, project, policy, "ingress", limit_ingress, limit_ingress_burst + ) + + def manage_external_network_rbacs( configuration: Configuration, project: openstack.identity.v3.project.Project, diff --git a/test/unit/test_manage.py b/test/unit/test_manage.py index fdb6024..e6e036b 100644 --- a/test/unit/test_manage.py +++ b/test/unit/test_manage.py @@ -12,8 +12,10 @@ get_quotaclass, check_bool, check_quota, + update_bandwidth_policy_rule, manage_external_network_rbacs, check_volume_types, + check_bandwidth_limit, manage_private_volumetypes, create_network_resources, add_service_network, @@ -289,6 +291,157 @@ def test_check_quota_2(self): self.mock_os_cloud.set_volume_quotas.assert_any_call(ANY, gigabytes=3) +class TestCheckBandwidth(TestBase): + def setUp(self): + super().setUp() + + self.mock_project = MagicMock() + self.mock_project.name = "test" + self.mock_project.id = 9012 + self.mock_domain = MagicMock() + self.mock_domain.name = "test" + self.config.os_cloud.get_domain.return_value = self.mock_domain + + def test_update_bandwidth_policy_rule_0(self): + mock_policy = MagicMock() + mock_policy.id = 5678 + self.config.os_cloud.list_qos_bandwidth_limit_rules.return_value = [] + + update_bandwidth_policy_rule( + self.config, self.mock_project, mock_policy, "egress", -1, -1 + ) + + self.config.os_cloud.delete_qos_bandwidth_limit_rule.assert_not_called() + self.config.os_cloud.create_qos_bandwidth_limit_rule.assert_not_called() + self.config.os_cloud.update_qos_bandwidth_limit_rule.assert_not_called() + + def test_update_bandwidth_policy_rule_1(self): + mock_policy = MagicMock() + mock_policy.id = 5678 + mock_rule = MagicMock() + mock_rule.id = 1234 + self.config.os_cloud.list_qos_bandwidth_limit_rules.return_value = [mock_rule] + + update_bandwidth_policy_rule( + self.config, self.mock_project, mock_policy, "egress", -1, -1 + ) + + self.config.os_cloud.delete_qos_bandwidth_limit_rule.assert_called_once_with( + 5678, 1234 + ) + self.config.os_cloud.create_qos_bandwidth_limit_rule.assert_not_called() + self.config.os_cloud.update_qos_bandwidth_limit_rule.assert_not_called() + + def test_update_bandwidth_policy_rule_2(self): + mock_policy = MagicMock() + mock_policy.id = 5678 + self.config.os_cloud.list_qos_bandwidth_limit_rules.return_value = [] + + update_bandwidth_policy_rule( + self.config, self.mock_project, mock_policy, "egress", 100, 200 + ) + + self.config.os_cloud.delete_qos_bandwidth_limit_rule.assert_not_called() + self.config.os_cloud.create_qos_bandwidth_limit_rule.assert_called_once_with( + 5678, max_kbps=100, max_burst_kbps=200, direction="egress" + ) + self.config.os_cloud.update_qos_bandwidth_limit_rule.assert_not_called() + + def test_update_bandwidth_policy_rule_3(self): + mock_policy = MagicMock() + mock_policy.id = 5678 + mock_rule = MagicMock() + mock_rule.id = 1234 + self.config.os_cloud.list_qos_bandwidth_limit_rules.return_value = [mock_rule] + + update_bandwidth_policy_rule( + self.config, self.mock_project, mock_policy, "egress", 300, 400 + ) + + self.config.os_cloud.delete_qos_bandwidth_limit_rule.assert_not_called() + self.config.os_cloud.create_qos_bandwidth_limit_rule.assert_not_called() + self.config.os_cloud.update_qos_bandwidth_limit_rule.assert_called_once_with( + 5678, 1234, max_kbps=300, max_burst_kbps=400 + ) + + @patch("openstack_project_manager.manage.update_bandwidth_policy_rule") + def test_check_bandwidth_limit_0(self, mock_update_bandwidth_policy_rule): + mock_admin_project = MagicMock() + mock_admin_project.name = "admin" + mock_default_domain = MagicMock() + mock_default_domain.name = "Default" + self.config.os_cloud.get_domain.return_value = mock_default_domain + + check_bandwidth_limit(self.config, mock_admin_project, {}) + + self.config.os_cloud.list_qos_policies.assert_not_called() + self.config.os_cloud.delete_qos_policy.assert_not_called() + self.config.os_cloud.create_qos_policy.assert_not_called() + mock_update_bandwidth_policy_rule.assert_not_called() + + @patch("openstack_project_manager.manage.update_bandwidth_policy_rule") + def test_check_bandwidth_limit_1(self, mock_update_bandwidth_policy_rule): + mock_policy = MagicMock() + mock_policy.id = 5678 + self.config.os_cloud.list_qos_policies.return_value = [mock_policy] + mock_quota_class = {} + + check_bandwidth_limit(self.config, self.mock_project, mock_quota_class) + + self.config.os_cloud.delete_qos_policy.assert_called_once_with(5678) + self.config.os_cloud.create_qos_policy.assert_not_called() + mock_update_bandwidth_policy_rule.assert_not_called() + + @patch("openstack_project_manager.manage.update_bandwidth_policy_rule") + def test_check_bandwidth_limit_2(self, mock_update_bandwidth_policy_rule): + self.config.os_cloud.list_qos_policies.return_value = [] + mock_quota_class = {} + + check_bandwidth_limit(self.config, self.mock_project, mock_quota_class) + + self.config.os_cloud.delete_qos_policy.assert_not_called() + self.config.os_cloud.create_qos_policy.assert_not_called() + mock_update_bandwidth_policy_rule.assert_not_called() + + @patch("openstack_project_manager.manage.update_bandwidth_policy_rule") + def test_check_bandwidth_limit_3(self, mock_update_bandwidth_policy_rule): + self.config.os_cloud.list_qos_policies.return_value = [] + mock_quota_class = {"bandwidth": {"egress": 1000}} + + check_bandwidth_limit(self.config, self.mock_project, mock_quota_class) + + self.config.os_cloud.delete_qos_policy.assert_not_called() + self.config.os_cloud.create_qos_policy.assert_called_once_with( + name="bw-limiter", default=True, project_id=9012 + ) + mock_update_bandwidth_policy_rule.assert_any_call( + self.config, self.mock_project, ANY, "egress", 1000, -1 + ) + mock_update_bandwidth_policy_rule.assert_any_call( + self.config, self.mock_project, ANY, "ingress", -1, -1 + ) + + @patch("openstack_project_manager.manage.update_bandwidth_policy_rule") + def test_check_bandwidth_limit_4(self, mock_update_bandwidth_policy_rule): + mock_policy = MagicMock() + mock_policy.id = 5678 + self.config.os_cloud.list_qos_policies.return_value = [mock_policy] + mock_quota_class = { + "bandwidth": {"egress_burst": 1000, "ingress": 2000, "ingress_burst": 3000} + } + + check_bandwidth_limit(self.config, self.mock_project, mock_quota_class) + + self.config.os_cloud.delete_qos_policy.assert_not_called() + self.config.os_cloud.create_qos_policy.assert_not_called() + mock_update_bandwidth_policy_rule.assert_any_call( + self.config, self.mock_project, mock_policy, "egress", -1, 1000 + ) + mock_update_bandwidth_policy_rule.assert_any_call( + self.config, self.mock_project, mock_policy, "ingress", 2000, 3000 + ) + + class TestManageExternalNetworkRbacs(TestBase): def setUp(self):