From 6f7e8cc0128b519cca56ed20f32ae6fb5fcee39d Mon Sep 17 00:00:00 2001 From: Jason Peacock Date: Fri, 13 Sep 2024 10:04:39 -0500 Subject: [PATCH] Remove implicit wildcards from configcache commands. The configcache commands now require the user to specify `*` to get wildcard selection behavior. ZEN-35054 --- .../ZenCollector/configcache/cli/expire.py | 45 +-- Products/ZenCollector/configcache/cli/list.py | 45 ++- .../ZenCollector/configcache/cli/remove.py | 47 ++- Products/ZenCollector/configcache/cli/show.py | 6 +- .../ZenCollector/configcache/cli/stats.py | 93 +++--- .../configcache/cli/tests/__init__.py | 0 .../configcache/cli/tests/test_expire.py | 222 ++++++++++++++ .../configcache/cli/tests/test_list.py | 280 ++++++++++++++++++ .../configcache/cli/tests/test_remove.py | 222 ++++++++++++++ .../configcache/cli/tests/test_show.py | 158 ++++++++++ 10 files changed, 1002 insertions(+), 116 deletions(-) create mode 100644 Products/ZenCollector/configcache/cli/tests/__init__.py create mode 100644 Products/ZenCollector/configcache/cli/tests/test_expire.py create mode 100644 Products/ZenCollector/configcache/cli/tests/test_list.py create mode 100644 Products/ZenCollector/configcache/cli/tests/test_remove.py create mode 100644 Products/ZenCollector/configcache/cli/tests/test_show.py diff --git a/Products/ZenCollector/configcache/cli/expire.py b/Products/ZenCollector/configcache/cli/expire.py index 97d7d798ee..5518eea346 100644 --- a/Products/ZenCollector/configcache/cli/expire.py +++ b/Products/ZenCollector/configcache/cli/expire.py @@ -81,18 +81,20 @@ def run(self): file=sys.stderr, ) return - else: - self._devices = self._devices[0].replace("*", "") if not self._confirm_inputs(): print("exit") return initialize_environment(configs=self.configs, useZope=False) client = getRedisClient(url=getRedisUrl()) store = createObject("deviceconfigcache-store", client) - query = DeviceQuery(service=self._service, monitor=self._monitor) + self._expire(store, self._get(store, haswildcard)) + + def _get(self, store, haswildcard): + query = self._make_query(haswildcard) results = store.query_statuses(query) - method = self._no_devices if not self._devices else self._with_devices - keys = method(results, wildcard=haswildcard) + return tuple(self._get_keys_from_results(results, haswildcard)) + + def _expire(self, store, keys): now = time.time() store.set_expired(*((key, now) for key in keys)) count = len(keys) @@ -101,25 +103,24 @@ def run(self): % (count, "" if count == 1 else "s") ) - def _no_devices(self, results, wildcard=False): - return tuple(status.key for status in results) - - def _with_devices(self, results, wildcard=False): - if wildcard: - predicate = self._check_wildcard - else: - predicate = self._check_list - - return tuple( - status.key for status in results if predicate(status.key.device) + def _make_query(self, haswildcard): + if haswildcard: + return DeviceQuery( + service=self._service, + monitor=self._monitor, + device=self._devices[0], + ) + return DeviceQuery(service=self._service, monitor=self._monitor) + + def _get_keys_from_results(self, results, haswildcard): + if not self._devices or haswildcard: + return (status.key for status in results) + return ( + status.key + for status in results + if status.key.device in self._devices ) - def _check_wildcard(self, device): - return self._devices in device - - def _check_list(self, device): - return device in self._devices - def _confirm_inputs(self): if self._devices: return True diff --git a/Products/ZenCollector/configcache/cli/list.py b/Products/ZenCollector/configcache/cli/list.py index 04278cb560..e64d6916b8 100644 --- a/Products/ZenCollector/configcache/cli/list.py +++ b/Products/ZenCollector/configcache/cli/list.py @@ -59,8 +59,8 @@ def add_arguments(parser, subparsers): listp.set_defaults(factory=ListDevice) def __init__(self, args): - self._monitor = "*{}*".format(args.collector).replace("***", "*") - self._service = "*{}*".format(args.service).replace("***", "*") + self._monitor = args.collector + self._service = args.service self._showuid = args.show_uid self._devices = getattr(args, "device", []) state_names = getattr(args, "states", ()) @@ -81,17 +81,22 @@ def run(self): ) return initialize_environment(configs=self.configs, useZope=False) + self._display(*self._collate(*self._get(haswildcard))) + + def _get(self, haswildcard): client = getRedisClient(url=getRedisUrl()) store = createObject("deviceconfigcache-store", client) query = self._make_query(haswildcard) - data = tuple(self._filter(store.query_statuses(query))) - uid_map = self._get_uidmap(store, data) + statuses = tuple(self._filter(store.query_statuses(query))) + uid_map = self._get_uidmap(store, statuses) + return (statuses, uid_map) + def _collate(self, statuses, uid_map): rows = [] maxd, maxs, maxt, maxa, maxm = 1, 1, 1, 1, 1 now = time.time() for status in sorted( - data, key=lambda x: (x.key.device, x.key.service) + statuses, key=lambda x: (x.key.device, x.key.service) ): devid = ( status.key.device @@ -117,30 +122,16 @@ def run(self): status.key.service, ) ) - hdr_tmplt = "{0:{6}} {1:{7}} {2:^{8}} {3:^{9}} {4:{10}} {5}" - row_tmplt = "{0:{6}} {1:{7}} {2:{8}} {3:>{9}} {4:{10}} {5}" - headings = ( - "DEVICE", - "STATUS", - "LAST CHANGE", - "AGE", - "COLLECTOR", - "SERVICE", - ) - widths = (maxd, maxs, maxt, maxa, maxm) + return rows, (maxd, maxs, maxt, maxa, maxm) + + def _display(self, rows, widths): if rows: - print(hdr_tmplt.format(*chain(headings, widths))) + print(_header_template.format(*chain(_headings, widths))) for row in rows: - print(row_tmplt.format(*chain(row, widths))) + print(_row_template.format(*chain(row, widths))) def _make_query(self, haswildcard): - if haswildcard: - return DeviceQuery( - service=self._service, - monitor=self._monitor, - device=self._devices[0], - ) - if len(self._devices) == 1: + if haswildcard or len(self._devices) == 1: return DeviceQuery( service=self._service, monitor=self._monitor, @@ -167,6 +158,10 @@ def _get_uidmap(self, store, data): return {} +_header_template = "{0:{6}} {1:{7}} {2:^{8}} {3:^{9}} {4:{10}} {5}" +_row_template = "{0:{6}} {1:{7}} {2:{8}} {3:>{9}} {4:{10}} {5}" +_headings = ("DEVICE", "STATUS", "LAST CHANGE", "AGE", "COLLECTOR", "SERVICE") + _name_state_lookup = { "current": ConfigStatus.Current, "retired": ConfigStatus.Retired, diff --git a/Products/ZenCollector/configcache/cli/remove.py b/Products/ZenCollector/configcache/cli/remove.py index 8ed61b0da2..b5ebe9bafd 100644 --- a/Products/ZenCollector/configcache/cli/remove.py +++ b/Products/ZenCollector/configcache/cli/remove.py @@ -24,7 +24,6 @@ class RemoveOidMap(object): - description = "Remove oidmap configuration from the cache" configs = (("store.zcml", __name__),) @@ -53,7 +52,6 @@ def run(self): class RemoveDevice(object): - description = "Delete device configurations from the cache" configs = (("store.zcml", __name__),) @@ -82,18 +80,20 @@ def run(self): file=sys.stderr, ) return - else: - self._devices = self._devices[0].replace("*", "") if not self._confirm_inputs(): print("exit") return initialize_environment(configs=self.configs, useZope=False) client = getRedisClient(url=getRedisUrl()) store = createObject("deviceconfigcache-store", client) - query = DeviceQuery(service=self._service, monitor=self._monitor) + self._remove(self._get(store, haswildcard)) + + def _get(self, store, haswildcard): + query = self._make_query(haswildcard) results = store.query_statuses(query) - method = self._no_devices if not self._devices else self._with_devices - keys = method(results, wildcard=haswildcard) + return tuple(self._get_keys_from_results(results, haswildcard)) + + def _remove(self, store, keys): store.remove(*keys) count = len(keys) print( @@ -101,25 +101,24 @@ def run(self): % (count, "" if count == 1 else "s") ) - def _no_devices(self, results, wildcard=False): - return tuple(status.key for status in results) - - def _with_devices(self, results, wildcard=False): - if wildcard: - predicate = self._check_wildcard - else: - predicate = self._check_list - - return tuple( - status.key for status in results if predicate(status.key.device) + def _make_query(self, haswildcard): + if haswildcard: + return DeviceQuery( + service=self._service, + monitor=self._monitor, + device=self._devices[0], + ) + return DeviceQuery(service=self._service, monitor=self._monitor) + + def _get_keys_from_results(self, results, haswildcard): + if not self._devices or haswildcard: + return (status.key for status in results) + return ( + status.key + for status in results + if status.key.device in self._devices ) - def _check_wildcard(self, device): - return self._devices in device - - def _check_list(self, device): - return device in self._devices - def _confirm_inputs(self): if self._devices: return True diff --git a/Products/ZenCollector/configcache/cli/show.py b/Products/ZenCollector/configcache/cli/show.py index 57e50c415c..508fb816b3 100644 --- a/Products/ZenCollector/configcache/cli/show.py +++ b/Products/ZenCollector/configcache/cli/show.py @@ -120,9 +120,9 @@ def run(self): store = createObject("deviceconfigcache-store", client) results, err = _query_cache( store, - service="*{}*".format(self._service), - monitor="*{}*".format(self._monitor), - device="*{}*".format(self._device), + service=self._service, + monitor=self._monitor, + device=self._device, ) if results: for cls in set(unjellyableRegistry.values()): diff --git a/Products/ZenCollector/configcache/cli/stats.py b/Products/ZenCollector/configcache/cli/stats.py index 74d665c97b..106057fd01 100644 --- a/Products/ZenCollector/configcache/cli/stats.py +++ b/Products/ZenCollector/configcache/cli/stats.py @@ -121,45 +121,20 @@ def add_arguments(parser, subparsers): subp.set_defaults(factory=StatsDevice) def __init__(self, args): - stats = [] - for statId in getattr(args, "statistic", StatsDevice._statistics): - if statId == "count": - stats.append(CountStat) - elif statId == "avg_age": - stats.append(AverageAgeStat) - elif statId == "median_age": - stats.append(MedianAgeStat) - elif statId == "min_age": - stats.append(MinAgeStat) - elif statId == "max_age": - stats.append(MaxAgeStat) - self._groups = [] - for groupId in getattr(args, "group", StatsDevice._groups): - if groupId == "collector": - self._groups.append(MonitorGroup(stats)) - elif groupId == "device": - try: - # DeviceGroup doesn't want CountStat - posn = stats.index(CountStat) - except ValueError: - # Not found, so don't worry about it - dg_stats = stats - pass - else: - # Found, replace it with UniqueCountStat - dg_stats = list(stats) - dg_stats[posn] = UniqueCountStat - self._groups.append(DeviceGroup(dg_stats)) - if groupId == "service": - self._groups.append(ServiceGroup(stats)) - elif groupId == "status": - self._groups.append(StatusGroup(stats)) + stats = [ + _name_stat_map.get(statId) + for statId in getattr(args, "statistic", StatsDevice._statistics) + ] + self._groups = [ + _make_statgroup(groupId, stats) + for groupId in getattr(args, "group", StatsDevice._groups) + ] if args.format == "tables": self._format = TablesOutput() elif args.format == "json": self._format = JSONOutput() - self._monitor = "*{}*".format(args.collector).replace("***", "*") - self._service = "*{}*".format(args.service).replace("***", "*") + self._monitor = args.collector + self._service = args.service self._devices = getattr(args, "device", []) def run(self): @@ -174,19 +149,19 @@ def run(self): client = getRedisClient(url=getRedisUrl()) store = createObject("deviceconfigcache-store", client) - if len(self._devices) == 1: + if haswildcard: query = DeviceQuery(self._service, self._monitor, self._devices[0]) else: query = DeviceQuery(self._service, self._monitor) - include = _get_device_predicate(self._devices) + included = _get_device_predicate(self._devices, haswildcard) for key, ts in store.query_updated(query): - if not include(key.device): + if not included(key.device): continue for group in self._groups: group.handle_key(key) group.handle_timestamp(key, ts) for status in store.query_statuses(query): - if not include(status.key.device): + if not included(status.key.device): continue for group in self._groups: group.handle_status(status) @@ -196,7 +171,41 @@ def run(self): ) -def _get_device_predicate(devices): - if len(devices) < 2: - return lambda _: True +def _make_statgroup(groupId, stats): + if groupId == "collector": + return MonitorGroup(stats) + + if groupId == "device": + try: + # DeviceGroup doesn't want CountStat + posn = stats.index(CountStat) + except ValueError: + # Not found, so don't worry about it + dg_stats = stats + pass + else: + # Found, replace it with UniqueCountStat + dg_stats = list(stats) + dg_stats[posn] = UniqueCountStat + return DeviceGroup(dg_stats) + + if groupId == "service": + return ServiceGroup(stats) + + if groupId == "status": + return StatusGroup(stats) + + +_name_stat_map = { + "count": CountStat, + "avg_age": AverageAgeStat, + "median_age": MedianAgeStat, + "min_age": MinAgeStat, + "max_age": MaxAgeStat, +} + + +def _get_device_predicate(devices, haswildcard): + if haswildcard: + return lambda x: True return lambda x: next((True for d in devices if x == d), False) diff --git a/Products/ZenCollector/configcache/cli/tests/__init__.py b/Products/ZenCollector/configcache/cli/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Products/ZenCollector/configcache/cli/tests/test_expire.py b/Products/ZenCollector/configcache/cli/tests/test_expire.py new file mode 100644 index 0000000000..6b634a36b3 --- /dev/null +++ b/Products/ZenCollector/configcache/cli/tests/test_expire.py @@ -0,0 +1,222 @@ +############################################################################## +# +# Copyright (C) Zenoss, Inc. 2024, all rights reserved. +# +# This content is made available according to terms specified in +# License.zenoss under the directory where your Zenoss product is installed. +# +############################################################################## + +from __future__ import absolute_import, print_function + +import collections + +from unittest import TestCase + +from Products.ZenCollector.services.config import DeviceProxy +from Products.Jobber.tests.utils import RedisLayer + +from ...cache import DeviceKey, DeviceRecord +from ...cache.storage import DeviceConfigStore +from ..expire import ExpireDevice + + +_fields = collections.namedtuple( + "_fields", "service monitor device uid updated" +) + +PATH = {"src": "Products.ZenCollector.configcache.cli.list"} + + +class _BaseTest(TestCase): + # Base class to share setup code + + layer = RedisLayer + + fields = ( + _fields("svc1", "mon1", "abc-01", "/abc-01", 1234500.0), + _fields("svc1", "mon1", "abc-02", "/abc-02", 1234550.0), + _fields("svc2", "mon1", "efg-01", "/efg-01", 1234550.0), + ) + + def setUp(t): + DeviceProxy.__eq__ = _compare_configs + t.store = DeviceConfigStore(t.layer.redis) + t.config1 = _make_config("abc-01", "_abc_01", "abef394c") + t.config2 = _make_config("abc-02", "_abc_02", "fbd987ba") + t.config3 = _make_config("efg-01", "_efg_01", "39da34cf") + t.record1 = DeviceRecord.make( + t.fields[0].service, + t.fields[0].monitor, + t.fields[0].device, + t.fields[0].uid, + t.fields[0].updated, + t.config1, + ) + t.record2 = DeviceRecord.make( + t.fields[1].service, + t.fields[1].monitor, + t.fields[1].device, + t.fields[1].uid, + t.fields[1].updated, + t.config2, + ) + t.record3 = DeviceRecord.make( + t.fields[2].service, + t.fields[2].monitor, + t.fields[2].device, + t.fields[2].uid, + t.fields[2].updated, + t.config3, + ) + t.store.add(t.record1) + t.store.add(t.record2) + t.store.add(t.record3) + + def tearDown(t): + del t.store + del t.config1 + del t.config2 + del t.config3 + del t.record1 + del t.record2 + del t.record3 + del DeviceProxy.__eq__ + + +_Args = collections.namedtuple("_Args", "service collector device") + + +class ExpireDeviceTest(_BaseTest): + """Test the ExpireDevice class.""" + + def test_no_args(t): + args = _Args("*", "*", []) + cmd = ExpireDevice(args) + + keys = cmd._get(t.store, False) + t.assertEqual(3, len(keys)) + keys = sorted(keys) + for n in range(3): + expectedkey = DeviceKey( + t.fields[n].service, + t.fields[n].monitor, + t.fields[n].device, + ) + t.assertEqual(keys[n], expectedkey) + + def test_matched_device(t): + args = _Args("*", "*", ["abc*"]) + cmd = ExpireDevice(args) + + keys = cmd._get(t.store, True) + t.assertEqual(2, len(keys)) + keys = sorted(keys) + for n in range(2): + expectedkey = DeviceKey( + t.fields[n].service, + t.fields[n].monitor, + t.fields[n].device, + ) + t.assertEqual(keys[n], expectedkey) + + def test_unmatched_device(t): + args = _Args("*", "*", ["abc"]) + cmd = ExpireDevice(args) + + keys = cmd._get(t.store, False) + t.assertEqual(0, len(keys)) + + def test_multiple_devices(t): + args = _Args("*", "*", ["abc-01", "abc-02"]) + cmd = ExpireDevice(args) + + keys = cmd._get(t.store, False) + t.assertEqual(2, len(keys)) + keys = sorted(keys) + for n in range(2): + expectedkey = DeviceKey( + t.fields[n].service, + t.fields[n].monitor, + t.fields[n].device, + ) + t.assertEqual(keys[n], expectedkey) + + def test_matched_service(t): + args = _Args("*1", "*", []) + cmd = ExpireDevice(args) + + keys = cmd._get(t.store, False) + t.assertEqual(2, len(keys)) + keys = sorted(keys) + for n in range(2): + expectedkey = DeviceKey( + t.fields[n].service, + t.fields[n].monitor, + t.fields[n].device, + ) + t.assertEqual(keys[n], expectedkey) + + def test_unmatched_service(t): + args = _Args("svc", "*", []) + cmd = ExpireDevice(args) + + keys = cmd._get(t.store, False) + t.assertEqual(0, len(keys)) + + def test_matched_monitor(t): + args = _Args("*", "*1", []) + cmd = ExpireDevice(args) + + keys = cmd._get(t.store, False) + t.assertEqual(3, len(keys)) + keys = sorted(keys) + for n in range(3): + expectedkey = DeviceKey( + t.fields[n].service, + t.fields[n].monitor, + t.fields[n].device, + ) + t.assertEqual(keys[n], expectedkey) + + def test_unmatched_monitor(t): + args = _Args("*", "mon", []) + cmd = ExpireDevice(args) + + keys = cmd._get(t.store, False) + t.assertEqual(0, len(keys)) + + def test_nonoverlapping_service(t): + args = _Args("svc2", "*", ["abc-01", "abc-02"]) + cmd = ExpireDevice(args) + + keys = cmd._get(t.store, False) + t.assertEqual(0, len(keys)) + + def test_nonoverlapping_monitor(t): + args = _Args("*", "mon2", []) + cmd = ExpireDevice(args) + + keys = cmd._get(t.store, False) + t.assertEqual(0, len(keys)) + + +def _make_config(_id, configId, guid): + config = DeviceProxy() + config.id = _id + config._config_id = configId + config._device_guid = guid + config.data = "fancy" + return config + + +def _compare_configs(self, cfg): + # _compare_configs used to monkeypatch DeviceProxy + # to make equivalent instances equal. + return all( + ( + self.id == cfg.id, + self._config_id == cfg._config_id, + self._device_guid == cfg._device_guid, + ) + ) diff --git a/Products/ZenCollector/configcache/cli/tests/test_list.py b/Products/ZenCollector/configcache/cli/tests/test_list.py new file mode 100644 index 0000000000..aa16e5c325 --- /dev/null +++ b/Products/ZenCollector/configcache/cli/tests/test_list.py @@ -0,0 +1,280 @@ +############################################################################## +# +# Copyright (C) Zenoss, Inc. 2024, all rights reserved. +# +# This content is made available according to terms specified in +# License.zenoss under the directory where your Zenoss product is installed. +# +############################################################################## + +from __future__ import absolute_import, print_function + +import collections + +from unittest import TestCase + +from mock import patch + +from Products.ZenCollector.services.config import DeviceProxy +from Products.Jobber.tests.utils import RedisLayer + +from ...cache import DeviceKey, DeviceRecord, ConfigStatus +from ...cache.storage import DeviceConfigStore +from ..list import ListDevice + + +_fields = collections.namedtuple( + "_fields", "service monitor device uid updated" +) + +PATH = {"src": "Products.ZenCollector.configcache.cli.list"} + +_Args = collections.namedtuple( + "_Args", "service collector show_uid device states" +) + + +class _BaseTest(TestCase): + # Base class to share setup code + + layer = RedisLayer + + fields = ( + _fields("svc1", "mon1", "abc-01", "/abc-01", 1234500.0), + _fields("svc1", "mon1", "abc-02", "/abc-02", 1234550.0), + _fields("svc2", "mon1", "efg-01", "/efg-01", 1234550.0), + ) + + def setUp(t): + DeviceProxy.__eq__ = _compare_configs + t.store = DeviceConfigStore(t.layer.redis) + t.config1 = _make_config("abc-01", "_abc_01", "abef394c") + t.config2 = _make_config("abc-02", "_abc_02", "fbd987ba") + t.config3 = _make_config("efg-01", "_efg_01", "39da34cf") + t.record1 = DeviceRecord.make( + t.fields[0].service, + t.fields[0].monitor, + t.fields[0].device, + t.fields[0].uid, + t.fields[0].updated, + t.config1, + ) + t.record2 = DeviceRecord.make( + t.fields[1].service, + t.fields[1].monitor, + t.fields[1].device, + t.fields[1].uid, + t.fields[1].updated, + t.config2, + ) + t.record3 = DeviceRecord.make( + t.fields[2].service, + t.fields[2].monitor, + t.fields[2].device, + t.fields[2].uid, + t.fields[2].updated, + t.config3, + ) + t.store.add(t.record1) + t.store.add(t.record2) + t.store.add(t.record3) + + def tearDown(t): + del t.store + del t.config1 + del t.config2 + del t.config3 + del t.record1 + del t.record2 + del t.record3 + del DeviceProxy.__eq__ + + +class ListDeviceTest(_BaseTest): + """Test the ListDevice class.""" + + @patch("{src}.createObject".format(**PATH), autospec=True) + @patch("{src}.getRedisClient".format(**PATH), autospec=True) + def test_no_args(t, _getRedisClient, _createObject): + _getRedisClient.return_value = t.layer.redis + _createObject.return_value = t.store + args = _Args("*", "*", False, [], []) + cmd = ListDevice(args) + + statuses, oidmap = cmd._get(False) + t.assertEqual(3, len(statuses)) + statuses = sorted(statuses, key=lambda s: s.key) + t.assertTrue( + all(isinstance(st, ConfigStatus.Current) for st in statuses) + ) + for n in range(3): + key = DeviceKey( + t.fields[n].service, + t.fields[n].monitor, + t.fields[n].device, + ) + t.assertEqual(statuses[n].key, key) + t.assertDictEqual({}, oidmap) + + @patch("{src}.createObject".format(**PATH), autospec=True) + @patch("{src}.getRedisClient".format(**PATH), autospec=True) + def test_matched_device(t, _getRedisClient, _createObject): + _getRedisClient.return_value = t.layer.redis + _createObject.return_value = t.store + args = _Args("*", "*", False, ["abc*"], []) + cmd = ListDevice(args) + + statuses, oidmap = cmd._get(True) + t.assertEqual(2, len(statuses)) + statuses = sorted(statuses, key=lambda s: s.key) + t.assertTrue( + all(isinstance(st, ConfigStatus.Current) for st in statuses) + ) + for n in range(2): + key = DeviceKey( + t.fields[n].service, + t.fields[n].monitor, + t.fields[n].device, + ) + t.assertEqual(statuses[n].key, key) + t.assertDictEqual({}, oidmap) + + @patch("{src}.createObject".format(**PATH), autospec=True) + @patch("{src}.getRedisClient".format(**PATH), autospec=True) + def test_multiple_devices(t, _getRedisClient, _createObject): + _getRedisClient.return_value = t.layer.redis + _createObject.return_value = t.store + args = _Args("*", "*", False, ["abc-01", "abc-02"], []) + cmd = ListDevice(args) + + statuses, oidmap = cmd._get(False) + t.assertEqual(2, len(statuses)) + statuses = sorted(statuses, key=lambda s: s.key) + for n in range(2): + expectedkey = DeviceKey( + t.fields[n].service, + t.fields[n].monitor, + t.fields[n].device, + ) + t.assertEqual(statuses[n].key, expectedkey) + + @patch("{src}.createObject".format(**PATH), autospec=True) + @patch("{src}.getRedisClient".format(**PATH), autospec=True) + def test_unmatched_device(t, _getRedisClient, _createObject): + _getRedisClient.return_value = t.layer.redis + _createObject.return_value = t.store + args = _Args("*", "*", False, ["abc"], []) + cmd = ListDevice(args) + + statuses, oidmap = cmd._get(False) + t.assertEqual(0, len(statuses)) + t.assertDictEqual({}, oidmap) + + @patch("{src}.createObject".format(**PATH), autospec=True) + @patch("{src}.getRedisClient".format(**PATH), autospec=True) + def test_matched_service(t, _getRedisClient, _createObject): + _getRedisClient.return_value = t.layer.redis + _createObject.return_value = t.store + args = _Args("*1", "*", False, [], []) + cmd = ListDevice(args) + + statuses, oidmap = cmd._get(False) + t.assertEqual(2, len(statuses)) + statuses = sorted(statuses, key=lambda s: s.key) + for n in range(2): + key = DeviceKey( + t.fields[n].service, + t.fields[n].monitor, + t.fields[n].device, + ) + t.assertEqual(statuses[n].key, key) + t.assertDictEqual({}, oidmap) + + @patch("{src}.createObject".format(**PATH), autospec=True) + @patch("{src}.getRedisClient".format(**PATH), autospec=True) + def test_unmatched_service(t, _getRedisClient, _createObject): + _getRedisClient.return_value = t.layer.redis + _createObject.return_value = t.store + args = _Args("svc", "*", False, [], []) + cmd = ListDevice(args) + + statuses, oidmap = cmd._get(False) + t.assertEqual(0, len(statuses)) + t.assertDictEqual({}, oidmap) + + @patch("{src}.createObject".format(**PATH), autospec=True) + @patch("{src}.getRedisClient".format(**PATH), autospec=True) + def test_matched_monitor(t, _getRedisClient, _createObject): + _getRedisClient.return_value = t.layer.redis + _createObject.return_value = t.store + args = _Args("*", "*1", False, [], []) + cmd = ListDevice(args) + + statuses, oidmap = cmd._get(False) + t.assertEqual(3, len(statuses)) + statuses = sorted(statuses, key=lambda s: s.key) + for n in range(3): + key = DeviceKey( + t.fields[n].service, + t.fields[n].monitor, + t.fields[n].device, + ) + t.assertEqual(statuses[n].key, key) + t.assertDictEqual({}, oidmap) + + @patch("{src}.createObject".format(**PATH), autospec=True) + @patch("{src}.getRedisClient".format(**PATH), autospec=True) + def test_unmatched_monitor(t, _getRedisClient, _createObject): + _getRedisClient.return_value = t.layer.redis + _createObject.return_value = t.store + args = _Args("*", "mon", False, [], []) + cmd = ListDevice(args) + + statuses, oidmap = cmd._get(False) + t.assertEqual(0, len(statuses)) + t.assertDictEqual({}, oidmap) + + @patch("{src}.createObject".format(**PATH), autospec=True) + @patch("{src}.getRedisClient".format(**PATH), autospec=True) + def test_nonoverlapping_service(t, _getRedisClient, _createObject): + _getRedisClient.return_value = t.layer.redis + _createObject.return_value = t.store + args = _Args("svc2", "*", False, ["abc-01", "abc-02"], []) + cmd = ListDevice(args) + + statuses, oidmap = cmd._get(False) + t.assertEqual(0, len(statuses)) + t.assertDictEqual({}, oidmap) + + @patch("{src}.createObject".format(**PATH), autospec=True) + @patch("{src}.getRedisClient".format(**PATH), autospec=True) + def test_nonoverlapping_monitor(t, _getRedisClient, _createObject): + _getRedisClient.return_value = t.layer.redis + _createObject.return_value = t.store + args = _Args("*", "mon2", False, [], []) + cmd = ListDevice(args) + + statuses, oidmap = cmd._get(False) + t.assertEqual(0, len(statuses)) + t.assertDictEqual({}, oidmap) + + +def _make_config(_id, configId, guid): + config = DeviceProxy() + config.id = _id + config._config_id = configId + config._device_guid = guid + config.data = "fancy" + return config + + +def _compare_configs(self, cfg): + # _compare_configs used to monkeypatch DeviceProxy + # to make equivalent instances equal. + return all( + ( + self.id == cfg.id, + self._config_id == cfg._config_id, + self._device_guid == cfg._device_guid, + ) + ) diff --git a/Products/ZenCollector/configcache/cli/tests/test_remove.py b/Products/ZenCollector/configcache/cli/tests/test_remove.py new file mode 100644 index 0000000000..ffac417672 --- /dev/null +++ b/Products/ZenCollector/configcache/cli/tests/test_remove.py @@ -0,0 +1,222 @@ +############################################################################## +# +# Copyright (C) Zenoss, Inc. 2024, all rights reserved. +# +# This content is made available according to terms specified in +# License.zenoss under the directory where your Zenoss product is installed. +# +############################################################################## + +from __future__ import absolute_import, print_function + +import collections + +from unittest import TestCase + +from Products.ZenCollector.services.config import DeviceProxy +from Products.Jobber.tests.utils import RedisLayer + +from ...cache import DeviceKey, DeviceRecord +from ...cache.storage import DeviceConfigStore +from ..remove import RemoveDevice + + +_fields = collections.namedtuple( + "_fields", "service monitor device uid updated" +) + +PATH = {"src": "Products.ZenCollector.configcache.cli.list"} + + +class _BaseTest(TestCase): + # Base class to share setup code + + layer = RedisLayer + + fields = ( + _fields("svc1", "mon1", "abc-01", "/abc-01", 1234500.0), + _fields("svc1", "mon1", "abc-02", "/abc-02", 1234550.0), + _fields("svc2", "mon1", "efg-01", "/efg-01", 1234550.0), + ) + + def setUp(t): + DeviceProxy.__eq__ = _compare_configs + t.store = DeviceConfigStore(t.layer.redis) + t.config1 = _make_config("abc-01", "_abc_01", "abef394c") + t.config2 = _make_config("abc-02", "_abc_02", "fbd987ba") + t.config3 = _make_config("efg-01", "_efg_01", "39da34cf") + t.record1 = DeviceRecord.make( + t.fields[0].service, + t.fields[0].monitor, + t.fields[0].device, + t.fields[0].uid, + t.fields[0].updated, + t.config1, + ) + t.record2 = DeviceRecord.make( + t.fields[1].service, + t.fields[1].monitor, + t.fields[1].device, + t.fields[1].uid, + t.fields[1].updated, + t.config2, + ) + t.record3 = DeviceRecord.make( + t.fields[2].service, + t.fields[2].monitor, + t.fields[2].device, + t.fields[2].uid, + t.fields[2].updated, + t.config3, + ) + t.store.add(t.record1) + t.store.add(t.record2) + t.store.add(t.record3) + + def tearDown(t): + del t.store + del t.config1 + del t.config2 + del t.config3 + del t.record1 + del t.record2 + del t.record3 + del DeviceProxy.__eq__ + + +_Args = collections.namedtuple("_Args", "service collector device") + + +class RemoveDeviceTest(_BaseTest): + """Test the RemoveDevice class.""" + + def test_no_args(t): + args = _Args("*", "*", []) + cmd = RemoveDevice(args) + + keys = cmd._get(t.store, False) + t.assertEqual(3, len(keys)) + keys = sorted(keys) + for n in range(3): + expectedkey = DeviceKey( + t.fields[n].service, + t.fields[n].monitor, + t.fields[n].device, + ) + t.assertEqual(keys[n], expectedkey) + + def test_matched_device(t): + args = _Args("*", "*", ["abc*"]) + cmd = RemoveDevice(args) + + keys = cmd._get(t.store, True) + t.assertEqual(2, len(keys)) + keys = sorted(keys) + for n in range(2): + expectedkey = DeviceKey( + t.fields[n].service, + t.fields[n].monitor, + t.fields[n].device, + ) + t.assertEqual(keys[n], expectedkey) + + def test_unmatched_device(t): + args = _Args("*", "*", ["abc"]) + cmd = RemoveDevice(args) + + keys = cmd._get(t.store, False) + t.assertEqual(0, len(keys)) + + def test_multiple_devices(t): + args = _Args("*", "*", ["abc-01", "abc-02"]) + cmd = RemoveDevice(args) + + keys = cmd._get(t.store, False) + t.assertEqual(2, len(keys)) + keys = sorted(keys) + for n in range(2): + expectedkey = DeviceKey( + t.fields[n].service, + t.fields[n].monitor, + t.fields[n].device, + ) + t.assertEqual(keys[n], expectedkey) + + def test_matched_service(t): + args = _Args("*1", "*", []) + cmd = RemoveDevice(args) + + keys = cmd._get(t.store, False) + t.assertEqual(2, len(keys)) + keys = sorted(keys) + for n in range(2): + expectedkey = DeviceKey( + t.fields[n].service, + t.fields[n].monitor, + t.fields[n].device, + ) + t.assertEqual(keys[n], expectedkey) + + def test_unmatched_service(t): + args = _Args("svc", "*", []) + cmd = RemoveDevice(args) + + keys = cmd._get(t.store, False) + t.assertEqual(0, len(keys)) + + def test_matched_monitor(t): + args = _Args("*", "*1", []) + cmd = RemoveDevice(args) + + keys = cmd._get(t.store, False) + t.assertEqual(3, len(keys)) + keys = sorted(keys) + for n in range(3): + expectedkey = DeviceKey( + t.fields[n].service, + t.fields[n].monitor, + t.fields[n].device, + ) + t.assertEqual(keys[n], expectedkey) + + def test_unmatched_monitor(t): + args = _Args("*", "mon", []) + cmd = RemoveDevice(args) + + keys = cmd._get(t.store, False) + t.assertEqual(0, len(keys)) + + def test_nonoverlapping_service(t): + args = _Args("svc2", "*", ["abc-01", "abc-02"]) + cmd = RemoveDevice(args) + + keys = cmd._get(t.store, False) + t.assertEqual(0, len(keys)) + + def test_nonoverlapping_monitor(t): + args = _Args("*", "mon2", []) + cmd = RemoveDevice(args) + + keys = cmd._get(t.store, False) + t.assertEqual(0, len(keys)) + + +def _make_config(_id, configId, guid): + config = DeviceProxy() + config.id = _id + config._config_id = configId + config._device_guid = guid + config.data = "fancy" + return config + + +def _compare_configs(self, cfg): + # _compare_configs used to monkeypatch DeviceProxy + # to make equivalent instances equal. + return all( + ( + self.id == cfg.id, + self._config_id == cfg._config_id, + self._device_guid == cfg._device_guid, + ) + ) diff --git a/Products/ZenCollector/configcache/cli/tests/test_show.py b/Products/ZenCollector/configcache/cli/tests/test_show.py new file mode 100644 index 0000000000..d6b118183a --- /dev/null +++ b/Products/ZenCollector/configcache/cli/tests/test_show.py @@ -0,0 +1,158 @@ +############################################################################## +# +# Copyright (C) Zenoss, Inc. 2024, all rights reserved. +# +# This content is made available according to terms specified in +# License.zenoss under the directory where your Zenoss product is installed. +# +############################################################################## + +from __future__ import absolute_import, print_function + +import collections + +from unittest import TestCase + +from Products.ZenCollector.services.config import DeviceProxy +from Products.Jobber.tests.utils import RedisLayer + +from ...cache import DeviceRecord +from ...cache.storage import DeviceConfigStore +from ..show import _query_cache + + +_fields = collections.namedtuple( + "_fields", "service monitor device uid updated" +) + +PATH = {"src": "Products.ZenCollector.configcache.cli.list"} + + +class _BaseTest(TestCase): + # Base class to share setup code + + layer = RedisLayer + + fields = ( + _fields("svc1", "mon1", "abc-01", "/abc-01", 1234500.0), + _fields("svc1", "mon1", "abc-02", "/abc-02", 1234550.0), + _fields("svc2", "mon1", "efg-01", "/efg-01", 1234550.0), + ) + + def setUp(t): + DeviceProxy.__eq__ = _compare_configs + t.store = DeviceConfigStore(t.layer.redis) + t.config1 = _make_config("abc-01", "_abc_01", "abef394c") + t.config2 = _make_config("abc-02", "_abc_02", "fbd987ba") + t.config3 = _make_config("efg-01", "_efg_01", "39da34cf") + t.record1 = DeviceRecord.make( + t.fields[0].service, + t.fields[0].monitor, + t.fields[0].device, + t.fields[0].uid, + t.fields[0].updated, + t.config1, + ) + t.record2 = DeviceRecord.make( + t.fields[1].service, + t.fields[1].monitor, + t.fields[1].device, + t.fields[1].uid, + t.fields[1].updated, + t.config2, + ) + t.record3 = DeviceRecord.make( + t.fields[2].service, + t.fields[2].monitor, + t.fields[2].device, + t.fields[2].uid, + t.fields[2].updated, + t.config3, + ) + t.store.add(t.record1) + t.store.add(t.record2) + t.store.add(t.record3) + + def tearDown(t): + del t.store + del t.config1 + del t.config2 + del t.config3 + del t.record1 + del t.record2 + del t.record3 + del DeviceProxy.__eq__ + + +class QueryCacheTest(_BaseTest): + """Test the _query_cache function.""" + + def test_unmatched_service(t): + svc, mon, dvc = ("1", "mon1", "abc-01") + + results, err = _query_cache(t.store, svc, mon, dvc) + t.assertIsNone(results) + t.assertIsNotNone(err) + + def test_unmatched_monitor(t): + svc, mon, dvc = ("svc1", "1", "abc-01") + + results, err = _query_cache(t.store, svc, mon, dvc) + t.assertIsNone(results) + t.assertIsNotNone(err) + + def test_unmatched_device(t): + svc, mon, dvc = ("svc1", "mon1", "abc") + + results, err = _query_cache(t.store, svc, mon, dvc) + t.assertIsNone(results) + t.assertIsNotNone(err) + + def test_multiple_devices(t): + svc, mon, dvc = ("svc1", "mon1", "abc*") + + results, err = _query_cache(t.store, svc, mon, dvc) + t.assertIsNone(results) + t.assertIsNotNone(err) + + def test_matching_service(t): + svc, mon, dvc = ("*1", "mon1", "abc-01") + + record, err = _query_cache(t.store, svc, mon, dvc) + t.assertIsNotNone(record) + t.assertIsNone(err) + + def test_matching_monitor(t): + svc, mon, dvc = ("svc1", "*1", "abc-01") + + record, err = _query_cache(t.store, svc, mon, dvc) + t.assertIsNotNone(record) + t.assertIsNone(err) + + def test_matching_device(t): + svc, mon, dvc = ("svc1", "mon1", "a*1") + + record, err = _query_cache(t.store, svc, mon, dvc) + t.assertIsNotNone(record) + t.assertIsNone(err) + + +def _make_config(_id, configId, guid): + config = DeviceProxy() + config.id = _id + config._config_id = configId + config._device_guid = guid + config.data = "fancy" + return config + + +def _compare_configs(self, cfg): + # _compare_configs used to monkeypatch DeviceProxy + # to make equivalent instances equal. + return all( + ( + self.id == cfg.id, + self._config_id == cfg._config_id, + self._device_guid == cfg._device_guid, + ) + )