Skip to content

Commit

Permalink
Deprecate Python win32_event_log check implementation (#16108)
Browse files Browse the repository at this point in the history
  • Loading branch information
clarkb7 authored Nov 10, 2023
1 parent 9487da0 commit cc63b0f
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 37 deletions.
32 changes: 27 additions & 5 deletions win32_event_log/assets/configuration/spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ files:
description: |
Whether or not to use a mode of operation that is now unmaintained and will be removed in a future version.
When set to `true`, WMI is used to collect events.
/\ WARNING /\
This mode, by nature of the underlying technology, is significantly more resource intensive.
Expand All @@ -58,6 +59,14 @@ files:
type: boolean
display_default: true
example: false
- name: legacy_mode_v2
description: |
Whether or not to use a mode of operation that is now unmaintained and will be removed in a future version.
When set to `true`, the Python Event Log API is used to collect events.
value:
type: boolean
example: false
- template: init_config/default
- template: instances
overrides:
Expand Down Expand Up @@ -271,11 +280,6 @@ files:
of the current user will be used.
value:
type: string
- name: timeout
description: The number of seconds to wait for new event signals.
value:
type: integer
example: 5
- name: payload_size
description: |
The number of events to request at a time.
Expand All @@ -292,10 +296,28 @@ files:
This is useful for preventing duplicate events being sent as a consequence of Agent restarts.
value:
type: integer
- name: legacy_mode_v2
description: |
Whether or not to use a mode of operation that is now unmaintained and will be removed in a future version.
When set to `true`, the Python Event Log API is used to collect events.
value:
type: boolean
example: false
- name: timeout
description: |
The number of seconds to wait for new event signals.
Note: This is only used when `legacy_mode_v2` is set to `true`.
Starting in Agent version 7.50.0 this option is no longer applicable and can be removed.
value:
type: integer
example: 5
- name: legacy_mode
description: |
Whether or not to use a mode of operation that is now unmaintained and will be removed in a future version.
When set to `true`, WMI is used to collect events.
/\ WARNING /\
This mode, by nature of the underlying technology, is significantly more resource intensive.
Expand Down
1 change: 1 addition & 0 deletions win32_event_log/changelog.d/16108.deprecated
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Deprecate the Python implementation of the check. Setting `legacy_mode: false` now runs a check defined in [datadog-agent](https://github.com/DataDog/datadog-agent). See [datadog-agent#20701](https://github.com/DataDog/datadog-agent/pull/20701) for more information on the new check implementation. Set `legacy_mode_v2: true` to revert to the Python implementation of the check. The Python implementation may be removed in a future version of the agent.
34 changes: 28 additions & 6 deletions win32_event_log/datadog_checks/win32_event_log/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from six import PY2

from datadog_checks.base import AgentCheck, ConfigurationError, is_affirmative
from datadog_checks.base.errors import SkipInstanceError
from datadog_checks.base.utils.common import exclude_undefined_keys
from datadog_checks.base.utils.time import get_timestamp

Expand Down Expand Up @@ -69,14 +70,36 @@ class Win32EventLogCheck(AgentCheck, ConfigMixin):
def __new__(cls, name, init_config, instances):
instance = instances[0]

# default to legacy mode for configuration backwards compatibility
init_config_legacy_mode = is_affirmative(init_config.get('legacy_mode', True))
init_config_legacy_mode = init_config.get('legacy_mode', None)
init_config_legacy_mode_v2 = init_config.get('legacy_mode_v2', None)
# If legacy_mode is unset for an instance, default to the init_config option
instance_legacy_mode = is_affirmative(instance.get('legacy_mode', init_config_legacy_mode))
if PY2 or instance_legacy_mode:
use_legacy_mode = instance.get('legacy_mode', init_config_legacy_mode)
use_legacy_mode_v2 = instance.get('legacy_mode_v2', init_config_legacy_mode_v2)

# If legacy_mode and legacy_mode_v2 are unset, default to legacy mode for configuration backwards compatibility
if use_legacy_mode is None and not is_affirmative(use_legacy_mode_v2):
use_legacy_mode = True

use_legacy_mode = is_affirmative(use_legacy_mode)
use_legacy_mode_v2 = is_affirmative(use_legacy_mode_v2)

if use_legacy_mode and use_legacy_mode_v2:
raise ConfigurationError(
"legacy_mode and legacy_mode_v2 are both true. Each instance must set a single mode to true."
)

if use_legacy_mode:
# Supports PY2 and PY3
return Win32EventLogWMI(name, init_config, instances)
else:
elif use_legacy_mode_v2:
# Supports PY3
if PY2:
raise ConfigurationError("legacy_mode_v2 is not supported on Python2")
return super(Win32EventLogCheck, cls).__new__(cls)
else:
raise SkipInstanceError(
"Set the legacy_mode and legacy_mode_v2 options to configure the implementation to use."
)

def __init__(self, name, init_config, instances):
super(Win32EventLogCheck, self).__init__(name, init_config, instances)
Expand Down Expand Up @@ -283,7 +306,6 @@ def consume_events(self):

def poll_events(self):
while True:

# IMPORTANT: the subscription starts immediately so you must consume before waiting for the first signal
while True:
# https://docs.microsoft.com/en-us/windows/win32/api/winevt/nf-winevt-evtnext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ def shared_legacy_mode():
return True


def shared_legacy_mode_v2():
return False


def shared_tag_event_id():
return False

Expand Down Expand Up @@ -64,6 +68,10 @@ def instance_legacy_mode():
return True


def instance_legacy_mode_v2():
return False


def instance_min_collection_interval():
return 15

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class InstanceConfig(BaseModel):
included_messages: Optional[tuple[str, ...]] = None
interpret_messages: Optional[bool] = None
legacy_mode: Optional[bool] = None
legacy_mode_v2: Optional[bool] = None
log_file: Optional[tuple[str, ...]] = None
message_filters: Optional[tuple[str, ...]] = None
metric_patterns: Optional[MetricPatterns] = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class SharedConfig(BaseModel):
event_priority: Optional[Literal['normal', 'low']] = None
interpret_messages: Optional[bool] = None
legacy_mode: Optional[bool] = None
legacy_mode_v2: Optional[bool] = None
service: Optional[str] = None
tag_event_id: Optional[bool] = None
tag_sid: Optional[bool] = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,21 @@ init_config:
## @param legacy_mode - boolean - optional - default: true
## Whether or not to use a mode of operation that is now unmaintained and will be removed in a future version.
##
## When set to `true`, WMI is used to collect events.
## /\ WARNING /\
## This mode, by nature of the underlying technology, is significantly more resource intensive.
##
## Setting this option to `false` is only supported on Agent versions 7 and above.
#
legacy_mode: false

## @param legacy_mode_v2 - boolean - optional - default: false
## Whether or not to use a mode of operation that is now unmaintained and will be removed in a future version.
##
## When set to `true`, the Python Event Log API is used to collect events.
#
# legacy_mode_v2: false

## @param service - string - optional
## Attach the tag `service:<SERVICE>` to every metric, event, and service check emitted by this integration.
##
Expand Down Expand Up @@ -222,11 +230,6 @@ instances:
#
# domain: <DOMAIN>

## @param timeout - integer - optional - default: 5
## The number of seconds to wait for new event signals.
#
# timeout: 5

## @param payload_size - integer - optional - default: 10
## The number of events to request at a time.
##
Expand All @@ -242,9 +245,25 @@ instances:
#
# bookmark_frequency: <BOOKMARK_FREQUENCY>

## @param legacy_mode_v2 - boolean - optional - default: false
## Whether or not to use a mode of operation that is now unmaintained and will be removed in a future version.
##
## When set to `true`, the Python Event Log API is used to collect events.
#
# legacy_mode_v2: false

## @param timeout - integer - optional - default: 5
## The number of seconds to wait for new event signals.
##
## Note: This is only used when `legacy_mode_v2` is set to `true`.
## Starting in Agent version 7.50.0 this option is no longer applicable and can be removed.
#
# timeout: 5

## @param legacy_mode - boolean - optional - default: true
## Whether or not to use a mode of operation that is now unmaintained and will be removed in a future version.
##
## When set to `true`, WMI is used to collect events.
## /\ WARNING /\
## This mode, by nature of the underlying technology, is significantly more resource intensive.
##
Expand Down
2 changes: 1 addition & 1 deletion win32_event_log/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ classifiers = [
"Private :: Do Not Upload",
]
dependencies = [
"datadog-checks-base>=32.6.0",
"datadog-checks-base>=34.2.0",
]
dynamic = [
"version",
Expand Down
2 changes: 1 addition & 1 deletion win32_event_log/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
EVENT_CATEGORY = 42

INSTANCE = {
'legacy_mode': False,
'legacy_mode_v2': True,
'timeout': 2,
'path': 'Application',
'filters': {'source': [EVENT_SOURCE]},
Expand Down
80 changes: 61 additions & 19 deletions win32_event_log/tests/legacy/test_win32_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import pytest
from six import PY2

from datadog_checks.base import ConfigurationError
from datadog_checks.base.errors import SkipInstanceError
from datadog_checks.win32_event_log import Win32EventLogCheck
from datadog_checks.win32_event_log.legacy import Win32EventLogWMI

Expand All @@ -30,43 +32,83 @@ def test_deprecation_notice(dd_run_check):

@pytest.mark.parametrize('shared_legacy_mode', [None, False, True])
@pytest.mark.parametrize('instance_legacy_mode', [None, False, True])
def test_legacy_mode_select(new_check, shared_legacy_mode, instance_legacy_mode):
@pytest.mark.parametrize('shared_legacy_mode_v2', [None, False, True])
@pytest.mark.parametrize('instance_legacy_mode_v2', [None, False, True])
def test_legacy_mode_select(
new_check, shared_legacy_mode, instance_legacy_mode, shared_legacy_mode_v2, instance_legacy_mode_v2
):
instance = {}
init_config = None

init_config = {}
if shared_legacy_mode is not None:
init_config = {'legacy_mode': shared_legacy_mode}
init_config['legacy_mode'] = shared_legacy_mode
if instance_legacy_mode is not None:
instance['legacy_mode'] = instance_legacy_mode
if shared_legacy_mode_v2 is not None:
init_config['legacy_mode_v2'] = shared_legacy_mode_v2
if instance_legacy_mode_v2 is not None:
instance['legacy_mode_v2'] = instance_legacy_mode_v2

print(init_config, instance)

legacy_mode_opts = [
# legacy mode set by init_config
shared_legacy_mode and instance_legacy_mode is None,
# legacy_mode set by instance
instance_legacy_mode,
]
legacy_mode_v2_opts = [
# legacy mode v2 set by init_config
shared_legacy_mode_v2 and instance_legacy_mode_v2 is None,
# legacy_mode v2 set by instance
instance_legacy_mode_v2,
]
# default to legacy_mode=True if other opts are unset
if not any(legacy_mode_v2_opts):
selected_default = shared_legacy_mode is not False and instance_legacy_mode is None
else:
selected_default = False

# Must only set a single mode
if any(legacy_mode_opts) and any(legacy_mode_v2_opts):
with pytest.raises(ConfigurationError, match="are both true"):
check = new_check(instance, init_config=init_config)
return

check = new_check(instance, init_config=init_config)
# legacy_mode_v2 is not supported on Python2
if PY2 and any(legacy_mode_v2_opts):
with pytest.raises(ConfigurationError, match="not supported on Python2"):
check = new_check(instance, init_config=init_config)
return

# if python2 should alawys choose legacy mode
if PY2:
assert type(check) is Win32EventLogWMI
# Must raise SkipInstanceError if both options are False
if not selected_default and not any(legacy_mode_opts) and not any(legacy_mode_v2_opts):
with pytest.raises(SkipInstanceError):
check = new_check(instance, init_config=init_config)
return

# Check constructor must now succeed without raising an exception
check = new_check(instance, init_config=init_config)

# if instance option is set it should take precedence
if instance_legacy_mode:
assert type(check) is Win32EventLogWMI
return
elif instance_legacy_mode is False:
elif instance_legacy_mode_v2:
assert type(check) is Win32EventLogCheck
return

# instance option is unset
assert instance_legacy_mode is None

# shared/init_config option should apply now
if shared_legacy_mode:
# shared/init_config option should apply now, instance option must be None
if shared_legacy_mode and instance_legacy_mode is None:
assert type(check) is Win32EventLogWMI
return
elif shared_legacy_mode is False:
elif shared_legacy_mode_v2 and instance_legacy_mode_v2 is None:
assert type(check) is Win32EventLogCheck
return

# shared/init_config option is unset
assert shared_legacy_mode is None
# No option was selected, confirm the default type
if selected_default:
assert type(check) is Win32EventLogWMI
return

# should default to true for backwards compatibility
assert type(check) is Win32EventLogWMI
# We should have covered all the cases above and should not reach here
raise AssertionError("Case was not tested")

0 comments on commit cc63b0f

Please sign in to comment.