Merge "Enable storage model collector by default"
This commit is contained in:
10
.zuul.yaml
10
.zuul.yaml
@@ -32,6 +32,11 @@
|
|||||||
period: 120
|
period: 120
|
||||||
watcher_cluster_data_model_collectors.storage:
|
watcher_cluster_data_model_collectors.storage:
|
||||||
period: 120
|
period: 120
|
||||||
|
$CINDER_CONF:
|
||||||
|
# enable notifications in compute node, by default they are only
|
||||||
|
# configured in the controller
|
||||||
|
oslo_messaging_notifications:
|
||||||
|
driver: messagingv2
|
||||||
devstack_services:
|
devstack_services:
|
||||||
watcher-api: false
|
watcher-api: false
|
||||||
watcher-decision-engine: true
|
watcher-decision-engine: true
|
||||||
@@ -63,6 +68,11 @@
|
|||||||
period: 120
|
period: 120
|
||||||
watcher_cluster_data_model_collectors.storage:
|
watcher_cluster_data_model_collectors.storage:
|
||||||
period: 120
|
period: 120
|
||||||
|
$CINDER_CONF:
|
||||||
|
# enable notifications in compute node, by default they are only
|
||||||
|
# configured in the controller
|
||||||
|
oslo_messaging_notifications:
|
||||||
|
driver: messagingv2
|
||||||
test-config:
|
test-config:
|
||||||
$TEMPEST_CONFIG:
|
$TEMPEST_CONFIG:
|
||||||
compute:
|
compute:
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
upgrade:
|
||||||
|
- |
|
||||||
|
The storage model collector is now enabled by default if a cinder service exists
|
||||||
|
in the deployment. This is done to prevent the storage model becoming obsolete
|
||||||
|
when running volume migrations if new volumes are created and the model was not
|
||||||
|
enabled in the configuration.
|
||||||
@@ -88,3 +88,16 @@ class KeystoneHelper(object):
|
|||||||
message=(_("Domain name seems ambiguous: %s") %
|
message=(_("Domain name seems ambiguous: %s") %
|
||||||
name_or_id))
|
name_or_id))
|
||||||
return domains[0]
|
return domains[0]
|
||||||
|
|
||||||
|
def is_service_enabled_by_type(self, svc_type):
|
||||||
|
services = self.keystone.services.list(type=svc_type)
|
||||||
|
svcs_enabled = [svc for svc in services if svc.enabled]
|
||||||
|
if len(svcs_enabled) == 0:
|
||||||
|
LOG.warning(f"Service enabled not found for type: {svc_type}")
|
||||||
|
return False
|
||||||
|
elif len(svcs_enabled) > 1:
|
||||||
|
LOG.warning(f"Multiple services found for type: {svc_type}")
|
||||||
|
return False
|
||||||
|
# if there is only one enabled service, consider it a valid
|
||||||
|
# case
|
||||||
|
return True
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ collector = cfg.OptGroup(name='collector',
|
|||||||
|
|
||||||
COLLECTOR_OPTS = [
|
COLLECTOR_OPTS = [
|
||||||
cfg.ListOpt('collector_plugins',
|
cfg.ListOpt('collector_plugins',
|
||||||
default=['compute'],
|
default=['compute', 'storage'],
|
||||||
help="""
|
help="""
|
||||||
The cluster data model plugin names.
|
The cluster data model plugin names.
|
||||||
|
|
||||||
|
|||||||
@@ -18,23 +18,46 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log
|
||||||
|
|
||||||
|
from watcher.common import clients
|
||||||
|
from watcher.common import keystone_helper
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
from watcher.decision_engine.loading import default
|
from watcher.decision_engine.loading import default
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class CollectorManager(object):
|
class CollectorManager(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, osc=None):
|
||||||
self.collector_loader = default.ClusterDataModelCollectorLoader()
|
self.collector_loader = default.ClusterDataModelCollectorLoader()
|
||||||
self._collectors = None
|
self._collectors = None
|
||||||
self._notification_endpoints = None
|
self._notification_endpoints = None
|
||||||
|
self.osc = osc if osc else clients.OpenStackClients()
|
||||||
|
|
||||||
|
def is_cinder_enabled(self):
|
||||||
|
keystone = keystone_helper.KeystoneHelper(self.osc)
|
||||||
|
if keystone.is_service_enabled_by_type(svc_type='block-storage'):
|
||||||
|
return True
|
||||||
|
elif keystone.is_service_enabled_by_type(svc_type='volumev3'):
|
||||||
|
# volumev3 is a commonly used alias for the cinder keystone service
|
||||||
|
# type
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def get_collectors(self):
|
def get_collectors(self):
|
||||||
if self._collectors is None:
|
if self._collectors is None:
|
||||||
collectors = utils.Struct()
|
collectors = utils.Struct()
|
||||||
collector_plugins = cfg.CONF.collector.collector_plugins
|
collector_plugins = cfg.CONF.collector.collector_plugins
|
||||||
for collector_name in collector_plugins:
|
for collector_name in collector_plugins:
|
||||||
|
if collector_name == 'storage':
|
||||||
|
if not self.is_cinder_enabled():
|
||||||
|
LOG.warning(
|
||||||
|
"Block storage service is not enabled,"
|
||||||
|
" skipping storage collector"
|
||||||
|
)
|
||||||
|
continue
|
||||||
collector = self.collector_loader.load(collector_name)
|
collector = self.collector_loader.load(collector_name)
|
||||||
collectors[collector_name] = collector
|
collectors[collector_name] = collector
|
||||||
self._collectors = collectors
|
self._collectors = collectors
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ class BaseStrategy(loadable.Loadable, metaclass=abc.ABCMeta):
|
|||||||
@property
|
@property
|
||||||
def collector_manager(self):
|
def collector_manager(self):
|
||||||
if self._collector_manager is None:
|
if self._collector_manager is None:
|
||||||
self._collector_manager = manager.CollectorManager()
|
self._collector_manager = manager.CollectorManager(osc=self.osc)
|
||||||
return self._collector_manager
|
return self._collector_manager
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from watcher.common import service as watcher_service
|
|||||||
from watcher.decision_engine.audit import continuous
|
from watcher.decision_engine.audit import continuous
|
||||||
from watcher.decision_engine import sync
|
from watcher.decision_engine import sync
|
||||||
from watcher.tests import base
|
from watcher.tests import base
|
||||||
|
from watcher.tests.fixtures import watcher as watcher_fixtures
|
||||||
|
|
||||||
|
|
||||||
class TestDecisionEngine(base.BaseTestCase):
|
class TestDecisionEngine(base.BaseTestCase):
|
||||||
@@ -49,6 +50,7 @@ class TestDecisionEngine(base.BaseTestCase):
|
|||||||
continuous.ContinuousAuditHandler, "start")
|
continuous.ContinuousAuditHandler, "start")
|
||||||
self.m_continuoushandler = p_continuoushandler.start()
|
self.m_continuoushandler = p_continuoushandler.start()
|
||||||
self.addCleanup(p_continuoushandler.stop)
|
self.addCleanup(p_continuoushandler.stop)
|
||||||
|
self.fake_keystone = self.useFixture(watcher_fixtures.KeystoneClient())
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super(TestDecisionEngine, self).tearDown()
|
super(TestDecisionEngine, self).tearDown()
|
||||||
@@ -57,5 +59,6 @@ class TestDecisionEngine(base.BaseTestCase):
|
|||||||
@mock.patch.object(sync.Syncer, "sync", mock.Mock())
|
@mock.patch.object(sync.Syncer, "sync", mock.Mock())
|
||||||
@mock.patch.object(service, "launch")
|
@mock.patch.object(service, "launch")
|
||||||
def test_run_de_app(self, m_launch):
|
def test_run_de_app(self, m_launch):
|
||||||
|
|
||||||
decisionengine.main()
|
decisionengine.main()
|
||||||
self.assertEqual(1, m_launch.call_count)
|
self.assertEqual(1, m_launch.call_count)
|
||||||
|
|||||||
60
watcher/tests/common/test_keystone_helper.py
Normal file
60
watcher/tests/common/test_keystone_helper.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# Copyright 2025 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import keystoneclient.v3.services as ks_service
|
||||||
|
|
||||||
|
from watcher.common import keystone_helper
|
||||||
|
from watcher.tests import base as test
|
||||||
|
from watcher.tests.fixtures import watcher as watcher_fixtures
|
||||||
|
|
||||||
|
|
||||||
|
class TestKeystoneHelper(test.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestKeystoneHelper, self).setUp()
|
||||||
|
self.fake_keystone = self.useFixture(watcher_fixtures.KeystoneClient())
|
||||||
|
self.keystone_svs = self.fake_keystone.m_keystone.return_value.services
|
||||||
|
|
||||||
|
self.keystone_helper = keystone_helper.KeystoneHelper()
|
||||||
|
|
||||||
|
def test_is_service_enabled(self):
|
||||||
|
self.keystone_svs.list.return_value = [mock.MagicMock(
|
||||||
|
spec=ks_service.Service, enabled=True)]
|
||||||
|
self.assertTrue(
|
||||||
|
self.keystone_helper.is_service_enabled_by_type('block-storage'))
|
||||||
|
self.keystone_svs.list.assert_called_once_with(type='block-storage')
|
||||||
|
|
||||||
|
def test_is_service_enabled_not_found(self):
|
||||||
|
self.keystone_svs.list.return_value = []
|
||||||
|
self.assertFalse(
|
||||||
|
self.keystone_helper.is_service_enabled_by_type('block-storage'))
|
||||||
|
self.keystone_svs.list.assert_called_once_with(type='block-storage')
|
||||||
|
|
||||||
|
def test_is_service_enabled_with_multiple_services_one_enabled(self):
|
||||||
|
self.keystone_svs.list.return_value = [
|
||||||
|
mock.MagicMock(spec=ks_service.Service, enabled=True),
|
||||||
|
mock.MagicMock(spec=ks_service.Service, enabled=False)]
|
||||||
|
self.assertTrue(
|
||||||
|
self.keystone_helper.is_service_enabled_by_type('block-storage'))
|
||||||
|
self.keystone_svs.list.assert_called_once_with(type='block-storage')
|
||||||
|
|
||||||
|
def test_is_service_enabled_multiple_services_two_enabled(self):
|
||||||
|
self.keystone_svs.list.return_value = [
|
||||||
|
mock.MagicMock(spec=ks_service.Service, enabled=True),
|
||||||
|
mock.MagicMock(spec=ks_service.Service, enabled=True)]
|
||||||
|
self.assertFalse(
|
||||||
|
self.keystone_helper.is_service_enabled_by_type('block-storage'))
|
||||||
|
self.keystone_svs.list.assert_called_once_with(type='block-storage')
|
||||||
@@ -32,6 +32,7 @@ from watcher import objects
|
|||||||
from watcher.tests import base
|
from watcher.tests import base
|
||||||
from watcher.tests.db import base as db_base
|
from watcher.tests.db import base as db_base
|
||||||
from watcher.tests.decision_engine.model import faker_cluster_state
|
from watcher.tests.decision_engine.model import faker_cluster_state
|
||||||
|
from watcher.tests.fixtures import watcher as watcher_fixtures
|
||||||
from watcher.tests.objects import utils as obj_utils
|
from watcher.tests.objects import utils as obj_utils
|
||||||
|
|
||||||
|
|
||||||
@@ -80,6 +81,10 @@ class TestCancelOngoingAudits(db_base.DbTestCase):
|
|||||||
@mock.patch.object(objects.audit.Audit, 'list')
|
@mock.patch.object(objects.audit.Audit, 'list')
|
||||||
class TestDecisionEngineSchedulingService(base.TestCase):
|
class TestDecisionEngineSchedulingService(base.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestDecisionEngineSchedulingService, self).setUp()
|
||||||
|
self.fake_keystone = self.useFixture(watcher_fixtures.KeystoneClient())
|
||||||
|
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
default_loading.ClusterDataModelCollectorLoader, 'load')
|
default_loading.ClusterDataModelCollectorLoader, 'load')
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
|
|||||||
24
watcher/tests/fixtures/watcher.py
vendored
24
watcher/tests/fixtures/watcher.py
vendored
@@ -17,8 +17,13 @@
|
|||||||
|
|
||||||
import logging as std_logging
|
import logging as std_logging
|
||||||
import os
|
import os
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
import fixtures
|
import fixtures
|
||||||
|
import keystoneclient.v3.client as ks_client
|
||||||
|
import keystoneclient.v3.services as ks_service
|
||||||
|
|
||||||
|
from watcher.common.clients import OpenStackClients
|
||||||
|
|
||||||
|
|
||||||
class NullHandler(std_logging.Handler):
|
class NullHandler(std_logging.Handler):
|
||||||
@@ -113,3 +118,22 @@ class StandardLogging(fixtures.Fixture):
|
|||||||
|
|
||||||
self.useFixture(
|
self.useFixture(
|
||||||
fixtures.MonkeyPatch('oslo_log.log.setup', fake_logging_setup))
|
fixtures.MonkeyPatch('oslo_log.log.setup', fake_logging_setup))
|
||||||
|
|
||||||
|
|
||||||
|
class KeystoneClient(fixtures.Fixture):
|
||||||
|
"""Fixture to mock the keystone client.
|
||||||
|
|
||||||
|
Prevents the OpenStackClients from actually connecting to the
|
||||||
|
keystone server.
|
||||||
|
Mocks the services.get and services.list methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(KeystoneClient, self).setUp()
|
||||||
|
mock_osc = mock.patch.object(
|
||||||
|
OpenStackClients, "keystone",
|
||||||
|
return_value=mock.MagicMock(spec=ks_client.Client))
|
||||||
|
self.m_keystone = mock_osc.start()
|
||||||
|
self.m_keystone.return_value.services = mock.MagicMock(
|
||||||
|
spec=ks_service.ServiceManager)
|
||||||
|
self.addCleanup(mock_osc.stop)
|
||||||
|
|||||||
Reference in New Issue
Block a user