Skip to content

Commit

Permalink
[wmi] fixing filters to work better with the parsed yaml. Fixing regr…
Browse files Browse the repository at this point in the history
…essions.

[wmi] simplifying filter logic. Handle wildcard characters, and OR/AND situations.

[wmi] adding and_props for property lists we should AND around. Improving tests. Additional filter tweaks.

[wmi] removing remaining inclusive references.

[wmi] fixing tests with new query formatting - parentheses changed.
  • Loading branch information
truthbk committed Jan 7, 2016
1 parent f558f2c commit 115ea87
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 113 deletions.
3 changes: 1 addition & 2 deletions checks.d/iis.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ def check(self, instance):
self.CLASS, properties,
filters=filters,
host=host, namespace=self.NAMESPACE,
username=user, password=password,
inclusive=True
username=user, password=password
)

# Sample, extract & submit metrics
Expand Down
36 changes: 21 additions & 15 deletions checks.d/win32_event_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def check(self, instance):
user = instance.get('user')
source_name = instance.get('source_name')
log_file = instance.get('log_file')
event_id = instance.get('event_id')
event_id = instance.get('event_id', [])
message_filters = instance.get('message_filters', [])

instance_key = self._get_instance_key(host, self.NAMESPACE, self.CLASS)
Expand All @@ -51,26 +51,32 @@ def check(self, instance):
self.last_ts[instance_key] = datetime.utcnow()
return

query = {}
filters = []
last_ts = self.last_ts[instance_key]
filters += [{'TimeGenerated': ('>=', self._dt_to_wmi(last_ts))}]
query['TimeGenerated'] = ('>=', self._dt_to_wmi(last_ts))
if ltype:
filters += [{'Type': ('=', ltype)}]
query['Type'] = ('=', ltype)
if user:
filters += [{'User': ('=', user)}]
if event_id:
filters += [{'EventCode': ('=', event_id)}]
query['User'] = ('=', user)
if source_name:
filters += [{'SourceName': ('=', source_name)}]
query['SourceName'] = ('=', source_name)
if log_file:
filters += [{'LogFile': ('=', log_file)}]
query['LogFile'] = ('=', log_file)
if event_id:
query['EventCode'] = []
for code in event_id:
query['EventCode'] += ('=', event_id)
if message_filters:
query['NOT Message'] = []
query['Message'] = []
for filt in message_filters:
if filt[0] == '-':
query['NOT Message'] += [('LIKE', filt[1:])]
else:
query['Message'] += [('LIKE', filt)]

for filt in message_filters:
is_not = False
if filt[0] == '-':
filters += [{'NOT Message': ('LIKE', filt[1:])}]
else:
filters += [{'Message': ('LIKE', filt)}]
filters.append(query)


wmi_sampler = self._get_wmi_sampler(
Expand All @@ -79,7 +85,7 @@ def check(self, instance):
filters=filters,
host=host, namespace=self.NAMESPACE,
username=username, password=password,
inclusive=False
and_props=['Message']
)

wmi_sampler.sample()
Expand Down
3 changes: 1 addition & 2 deletions checks.d/windows_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ def check(self, instance):
self.CLASS, properties,
filters=filters,
host=host, namespace=self.NAMESPACE,
username=user, password=password,
inclusive=True
username=user, password=password
)

# Sample, extract & submit metrics
Expand Down
117 changes: 58 additions & 59 deletions checks/libs/wmi/sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,12 @@ def get(self, key):
class WMISampler(object):
"""
WMI Sampler.
- inclusive applies to the filters. An inclusive filter will OR filter
elements, a non-inclusive will AND the WHERE clause.
"""
_wmi_locators = {}
_wmi_connections = {}

def __init__(self, logger, class_name, property_names, filters="", host="localhost",
namespace="root\\cimv2", username="", password="", inclusive=False):
namespace="root\\cimv2", username="", password="", and_props=[]):
self.logger = logger

# Connection information
Expand Down Expand Up @@ -84,7 +82,7 @@ def __init__(self, logger, class_name, property_names, filters="", host="localho
self.class_name = class_name
self.property_names = property_names
self.filters = filters
self.inclusive_filter = inclusive
self._and_props = and_props
self._formatted_filters = None
self.property_counter_types = None

Expand All @@ -110,7 +108,7 @@ def formatted_filters(self):
"""
if not self._formatted_filters:
filters = deepcopy(self.filters)
self._formatted_filters = self._format_filter(filters, self.inclusive_filter)
self._formatted_filters = self._format_filter(filters, self._and_props)
return self._formatted_filters

def sample(self):
Expand Down Expand Up @@ -254,7 +252,7 @@ def _get_connection(self):
return connection

@staticmethod
def _format_filter(filters, inclusive=False):
def _format_filter(filters, and_props=[]):
"""
Transform filters to a comprehensive WQL `WHERE` clause.
Expand All @@ -265,73 +263,74 @@ def _format_filter(filters, inclusive=False):
NOTE: If we just provide a value we defailt to '=' comparison operator.
Otherwise, specify the operator in a tuple as above: (comp_op, value)
The filters also support lists/tuples as property 'values' to filter.
When such a list is provided, we OR filter for all such property values:
- [{'Property': [value1, value2, value3]},...] or
- [{'Property': [(cmp_op, value1), (cmp_op, value2)]},...] or
Normally the filter is expected to be exclusive, such that all filter conditions
should be met - therefore we AND all filters. If you'd like to OR, then set
inclusive to True.
If we detect a wildcard character such as '*' or '%' we will override
the operator to use LIKE
"""
def build_where_clause(fltr, inclusive):
"""
Recursively build `WHERE` clause.
"""
def build_where_clause(fltr):
f = fltr.pop()
prop, value = f.popitem()
if isinstance(value, tuple):
oper = value[0]
value = value[1]
else:
oper = '='
wql = ""
while f:
prop, value = f.popitem()

if isinstance(value, tuple):
oper = value[0]
value = value[1]
elif isinstance(value, basestring) and ('*' in value or '%' in value):
oper = 'LIKE'
else:
oper = '='

if len(fltr) == 0:
if isinstance(value, list):
if not len(value):
continue

internal_filter = map(lambda x:
{prop: x} if isinstance(x, tuple)
else {prop: (oper, x)}, value)
return "( {clause} )".format(clause=build_where_clause(internal_filter, inclusive=True))
(prop, x) if isinstance(x, tuple)
else (prop, ('LIKE', x)) if ('*' in x or '%' in x)
else (prop, (oper, x)), value)

bool_op = ' OR '
for p in and_props:
if p.lower() in prop.lower():
bool_op = ' AND '
break

clause = bool_op.join(['{0} {1} \'{2}\''.format(k, v[0], v[1]) if isinstance(v,tuple)
else '{0} = \'{1}\''.format(k,v)
for k,v in internal_filter])

if bool_op.strip() == 'OR':
wql += "( {clause} )".format(
clause=clause)
else:
wql += "{clause}".format(
clause=clause)

else:
return "{property} {cmp} '{constant}'".format(
wql += "{property} {cmp} '{constant}'".format(
property=prop,
cmp=oper,
constant=value
)

if isinstance(value, list):
internal_filter = map(lambda x: {prop: (oper, x)}, value)
if inclusive:
return "( {clause} ) OR {more}".format(
clause=build_where_clause(internal_filter, inclusive=True),
more=build_where_clause(fltr, inclusive)
)

return "( {clause} ) AND {more}".format(
clause=build_where_clause(internal_filter, inclusive=True),
more=build_where_clause(fltr, inclusive)
)
constant=value)
if f:
wql += " AND "

if inclusive:
return "{property} {cmp} '{constant}' OR {more}".format(
property=prop,
cmp=oper,
constant=value,
more=build_where_clause(fltr, inclusive)
)
# empty list skipped
if wql.endswith(" AND "):
wql = wql[:-5]

return "{property} {cmp} '{constant}' AND {more}".format(
property=prop,
cmp=oper,
constant=value,
more=build_where_clause(fltr, inclusive)
if len(fltr) == 0:
return "( {clause} )".format(clause=wql)

return "( {clause} ) OR {more}".format(
clause=wql,
more=build_where_clause(fltr)
)


if not filters:
return ""

return " WHERE {clause}".format(clause=build_where_clause(filters, inclusive))
return " WHERE {clause}".format(clause=build_where_clause(filters))

def _query(self):
"""
Expand Down Expand Up @@ -439,7 +438,7 @@ def _parse_results(self, raw_results, includes_qualifiers):

try:
item[wmi_property.Name] = float(wmi_property.Value)
except (TypeError, ValueError) as e:
except (TypeError, ValueError):
item[wmi_property.Name] = wmi_property.Value

results.append(item)
Expand Down
2 changes: 1 addition & 1 deletion tests/checks/fixtures/wmi/win32_ntlogevent
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ InsertionStrings [insertionstring]
Logfile Application
Message SomeMessage
SourceName MSQLSERVER
TimeGenerated 20151224113047.000000-480
TimeGenerated 21001224113047.000000-480
User FooUser
Type Error
2 changes: 1 addition & 1 deletion tests/checks/mock/test_w32logevent.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

def to_time(wmi_ts):
"Just return any time struct"
return (2015, 12, 24, 11, 30, 47, 0, 0)
return (2100, 12, 24, 11, 30, 47, 0, 0)

def from_time(year=0, month=0, day=0, hours=0, minutes=0,
seconds=0, microseconds=0, timezone=0):
Expand Down
Loading

0 comments on commit 115ea87

Please sign in to comment.