diff --git a/ZenPacks/zenoss/PostgreSQL/Table.py b/ZenPacks/zenoss/PostgreSQL/Table.py index 4733463..345ff30 100644 --- a/ZenPacks/zenoss/PostgreSQL/Table.py +++ b/ZenPacks/zenoss/PostgreSQL/Table.py @@ -60,5 +60,7 @@ class Table(DeviceComponent, ManagedEntity, CollectedOrModeledMixin): event_key = "ComponentId" def device(self): - return self.database().device() - + db = self.database() + if db: + return db.device() + return None diff --git a/ZenPacks/zenoss/PostgreSQL/__init__.py b/ZenPacks/zenoss/PostgreSQL/__init__.py index 5963237..6efd97e 100644 --- a/ZenPacks/zenoss/PostgreSQL/__init__.py +++ b/ZenPacks/zenoss/PostgreSQL/__init__.py @@ -12,6 +12,7 @@ ########################################################################### import logging + log = logging.getLogger('zen.PostgreSQL') import os @@ -22,6 +23,7 @@ from Products.ZenRelations.RelSchema import ToManyCont, ToOne from Products.ZenUtils.Utils import monkeypatch, zenPath + class ZenPack(ZenPackBase): packZProperties = [ ('zPostgreSQLPort', 5432, 'int'), @@ -29,8 +31,16 @@ class ZenPack(ZenPackBase): ('zPostgreSQLPassword', '', 'password'), ('zPostgreSQLUseSSL', False, 'boolean'), ('zPostgreSQLDefaultDB', 'postgres', 'string'), + ('zPostgreSQLTableRegex', [], 'lines'), ] + packZProperties_data = { + 'zPostgreSQLTableRegex': { + 'description': "List of regular expressions (matched against table names) to control which tables are NOT modeled from All databases.", + 'label': "Regex Table Filter", + 'type': "lines" }, + } + def install(self, app): super(ZenPack, self).install(app) self.patchPostgreSQLDriver() @@ -40,10 +50,10 @@ def remove(self, app, leaveObjects=False): if not leaveObjects: # Remove our custom relations addition. Device._relations = tuple( - [ x for x in Device._relations if x[0] != 'pgDatabases' ]) + [x for x in Device._relations if x[0] != 'pgDatabases']) self.updateExistingRelations(app.zport.dmd) - + # Revert all pg8000 library patches self.patchPostgreSQLDriver(revert=True) @@ -57,21 +67,22 @@ def updateExistingRelations(self, dmd): def patchPostgreSQLDriver(self, revert=False): log.info('Patching pg8000 core library') patch_dir = self.path('lib') - + # Getting a list of all patches which will be applied patches_list = [file for file in os.listdir(patch_dir) if file.endswith('.patch')] - - cmd = "patch -p0 -d %s -i %s" if not revert else "patch -p0 -R -d %s -i %s" + + cmd = "patch -p0 -d %s -i %s" if not revert else "patch -p0 -R -d %s -i %s" for patch in patches_list: os.system(cmd % ( os.path.join(patch_dir, 'pg8000'), os.path.join(patch_dir, patch) )) + # Allow PostgreSQL databases to be related to any device. Device._relations += ( ('pgDatabases', ToManyCont(ToOne, - 'ZenPacks.zenoss.PostgreSQL.Database.Database', 'server')), + 'ZenPacks.zenoss.PostgreSQL.Database.Database', 'server')), ) # We need to filter components by id instead of name. @@ -79,6 +90,7 @@ def patchPostgreSQLDriver(self, revert=False): "\"(device = '%s' and component = '%s')\"" " % (me.device().getDmdKey(), me.id)") + @monkeypatch('Products.ZenModel.Device.Device') def setPostgreSQL(self, active=True): if not active: @@ -96,11 +108,12 @@ def setPostgreSQL(self, active=True): if device.zCommandCommandTimeout < 180: device.setZenProperty('zCommandCommandTimeout', 180) + @monkeypatch('Products.ZenModel.Device.Device') def getPostgreSQL(self): device = self.primaryAq() if 'PostgreSQLServer' in device.zDeviceTemplates \ - and device.zCommandCommandTimeout >= 180: + and device.zCommandCommandTimeout >= 180: return True return False diff --git a/ZenPacks/zenoss/PostgreSQL/modeler/plugins/zenoss/PostgreSQL.py b/ZenPacks/zenoss/PostgreSQL/modeler/plugins/zenoss/PostgreSQL.py index 1a872df..66f7c7c 100644 --- a/ZenPacks/zenoss/PostgreSQL/modeler/plugins/zenoss/PostgreSQL.py +++ b/ZenPacks/zenoss/PostgreSQL/modeler/plugins/zenoss/PostgreSQL.py @@ -12,13 +12,16 @@ ########################################################################### import logging +import re + log = logging.getLogger('zen.PostgreSQL') from Products.DataCollector.plugins.CollectorPlugin import PythonPlugin from Products.DataCollector.plugins.DataMaps import ObjectMap, RelationshipMap from Products.ZenUtils.Utils import prepId -from ZenPacks.zenoss.PostgreSQL.util import PgHelper +from ZenPacks.zenoss.PostgreSQL.util import PgHelper, exclude_patterns_list, is_suppressed + class PostgreSQL(PythonPlugin): deviceProperties = PythonPlugin.deviceProperties + ( @@ -27,6 +30,7 @@ class PostgreSQL(PythonPlugin): 'zPostgreSQLPassword', 'zPostgreSQLUseSSL', 'zPostgreSQLDefaultDB', + 'zPostgreSQLTableRegex', ) def collect(self, device, unused): @@ -39,6 +43,7 @@ def collect(self, device, unused): device.zPostgreSQLDefaultDB) results = {} + exclude_patterns = exclude_patterns_list(getattr(device, 'zPostgreSQLTableRegex', [])) log.info("Getting database list") try: @@ -55,7 +60,14 @@ def collect(self, device, unused): log.info("Getting tables list for {0}".format(dbName)) try: - results['databases'][dbName]['tables'] = pg.getTablesInDatabase(dbName) + tables = pg.getTablesInDatabase(dbName) + if exclude_patterns: + for key in tables.keys(): + if is_suppressed(key, exclude_patterns): + del tables[key] + + results['databases'][dbName]['tables'] = tables + except Exception, ex: log.warn("Error getting tables list for {0}: {1}".format( dbName, ex)) @@ -68,7 +80,7 @@ def process(self, devices, results, unused): if results is None: return None - maps = [ self.objectMap(dict(setPostgreSQL=True)) ] + maps = [self.objectMap(dict(setPostgreSQL=True))] databases = [] for dbName, dbDetail in results['databases'].items(): diff --git a/ZenPacks/zenoss/PostgreSQL/util.py b/ZenPacks/zenoss/PostgreSQL/util.py index d701d2e..8a5823c 100644 --- a/ZenPacks/zenoss/PostgreSQL/util.py +++ b/ZenPacks/zenoss/PostgreSQL/util.py @@ -15,7 +15,9 @@ import math import sys import time - +import re +import logging +LOG = logging.getLogger('zen.PostgreSQL.utils') def addLocalLibPath(): """ @@ -545,3 +547,28 @@ def getTableStatsForDatabase(self, db): cursor.close() return tableStats + +def exclude_patterns_list(excludes): + exclude_patterns = [] + + for exclude in excludes: + exclude = exclude.strip() + if exclude == "" or exclude.startswith("#"): + continue + + try: + exclude_patterns.append(re.compile(exclude)) + except Exception: + LOG.warn("Invalid zPostgreSQLTableRegex value: '%s', this modeling filter will not be applied.", exclude) + continue + + return exclude_patterns + + +def is_suppressed(item, exclude_patterns): + + for exclude_pattern in exclude_patterns: + if exclude_pattern.search(item): + return True + + return False diff --git a/docs/body.md b/docs/body.md index 2e54c86..84ffdba 100644 --- a/docs/body.md +++ b/docs/body.md @@ -82,6 +82,7 @@ individual devices. - *zPostgreSQLUsername* - Must be a superuser. Default: postgres - *zPostgreSQLPassword* - Password for user. No default. - *zPostgreSQLDefaultDB* - Default database. Default: postgres + - *zPostgreSQLTableRegex* - Filter tables of all databases with Regex. Default: "" In addition to setting these properties you must add the ''zenoss.PostgreSQL'' modeler plugin to a device class or individual device. This modeler plugin will @@ -164,6 +165,11 @@ zSnmpMonitorIgnore property to True and remodel. Changes --------------- +1.0.13 + +* Make PostgreSQL table modeling optional (ZPS-8554) +* Tested with Zenoss Cloud, Zenoss 6.7.0 and Service Impact 5.6.1 + 1.0.12 * Resolved isuue with error in pg8000 library on Ubuntu OS (ZPS-7424) diff --git a/docs/releases.md b/docs/releases.md index e9a3fce..03599f0 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -1,6 +1,10 @@ Releases -------- +Version 1.0.13-Download +Released on 2023/9/12 +Compatible with Zenoss 6.x, Zenoss Cloud and Service Impact 5.6.1 + Version 1.0.12-Download Released on 2021/1/14 Compatible with Zenoss 6.4.1 - 6.5.0, Zenoss Cloud and Service Impact 5.5.3 diff --git a/setup.py b/setup.py index fc4241c..56dab97 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ # or saved. Do not modify them directly here. # NB: PACKAGES is deprecated NAME = "ZenPacks.zenoss.PostgreSQL" -VERSION = "1.1.0" +VERSION = "1.0.13" AUTHOR = "Zenoss" LICENSE = "GPLv2" NAMESPACE_PACKAGES = ['ZenPacks', 'ZenPacks.zenoss']