Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

NH-65713 Add configurator trace exporter tests #225

Merged
merged 9 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions solarwinds_apm/configurator.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,8 @@ def _configure_traces_exporter(
# been checked by ApmConfig so exception here means
# something quite wrong
logger.exception(
"Failed to load configured exporter %s. "
"Failed to load configured OTEL_TRACES_EXPORTER. "
"Please reinstall or contact %s.",
exporter_name,
INTL_SWO_SUPPORT_EMAIL,
)
raise
Expand Down
Empty file.
Empty file.
49 changes: 49 additions & 0 deletions tests/unit/test_configurator/fixtures/apm_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# © 2023 SolarWinds Worldwide, LLC. All rights reserved.
#
# 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.

import pytest


def get_apmconfig_mocks(mocker, enabled=True, exp_otel_col=True):
mock_get_otelcol = mocker.Mock()
if exp_otel_col == True:
mock_get_otelcol.configure_mock(return_value=True)
else:
mock_get_otelcol.configure_mock(return_value=False)

mock_get_exp = mocker.Mock()
mock_get_exp.configure_mock(
return_value=mocker.Mock(
**{
"get": mock_get_otelcol
}
)
)

mock_apmconfig = mocker.Mock()
mock_apmconfig.configure_mock(
**{
"agent_enabled": enabled,
"get": mock_get_exp
}
)
return mock_apmconfig


@pytest.fixture(name="mock_apmconfig_disabled")
def fixture_mock_apmconfig_disabled(mocker):
return mocker.patch(
"solarwinds_apm.configurator.SolarWindsApmConfig",
get_apmconfig_mocks(mocker, False, False)
)


@pytest.fixture(name="mock_apmconfig_enabled")
def fixture_mock_apmconfig_enabled(mocker):
return mocker.patch(
"solarwinds_apm.configurator.SolarWindsApmConfig",
get_apmconfig_mocks(mocker, True, False)
)
16 changes: 16 additions & 0 deletions tests/unit/test_configurator/fixtures/batch_span_processor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# © 2023 SolarWinds Worldwide, LLC. All rights reserved.
#
# 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.

import pytest

def mock_batch_span_processor(mocker):
return mocker.patch(
"solarwinds_apm.configurator.BatchSpanProcessor",
)

@pytest.fixture(name="mock_bsprocessor")
def fixture_mock_bsprocessor(mocker):
return mock_batch_span_processor(mocker)
17 changes: 17 additions & 0 deletions tests/unit/test_configurator/fixtures/extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# © 2023 SolarWinds Worldwide, LLC. All rights reserved.
#
# 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.

import pytest

@pytest.fixture(name="mock_extension")
def fixture_mock_extension(mocker):
return mocker.patch(
"solarwinds_apm.extension"
)

@pytest.fixture(name="mock_reporter")
def fixture_mock_reporter(mocker):
return mocker.Mock()
14 changes: 14 additions & 0 deletions tests/unit/test_configurator/fixtures/fwkv_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# © 2023 SolarWinds Worldwide, LLC. All rights reserved.
#
# 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.

import pytest


@pytest.fixture(name="mock_fwkv_manager")
def fixture_mock_fwkv_manager(mocker):
return mocker.patch(
"solarwinds_apm.apm_fwkv_manager.SolarWindsFrameworkKvManager"
)
14 changes: 14 additions & 0 deletions tests/unit/test_configurator/fixtures/meter_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# © 2023 SolarWinds Worldwide, LLC. All rights reserved.
#
# 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.

import pytest


@pytest.fixture(name="mock_meter_manager")
def fixture_mock_meter_manager(mocker):
return mocker.patch(
"solarwinds_apm.apm_meter_manager.SolarWindsMeterManager"
)
26 changes: 26 additions & 0 deletions tests/unit/test_configurator/fixtures/trace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# © 2023 SolarWinds Worldwide, LLC. All rights reserved.
#
# 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.

def get_trace_mocks(mocker):
mock_add_span_processor = mocker.Mock()
mock_tracer_provider = mocker.Mock()
mock_tracer_provider.configure_mock(
**{
"add_span_processor": mock_add_span_processor
}
)
mock_get_tracer_provider = mocker.Mock(
return_value=mock_tracer_provider
)
mock_trace = mocker.patch(
"solarwinds_apm.configurator.trace",
)
mock_trace.configure_mock(
**{
"get_tracer_provider": mock_get_tracer_provider
}
)
return mock_trace, mock_get_tracer_provider, mock_add_span_processor
14 changes: 14 additions & 0 deletions tests/unit/test_configurator/fixtures/txn_name_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# © 2023 SolarWinds Worldwide, LLC. All rights reserved.
#
# 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.

import pytest


@pytest.fixture(name="mock_txn_name_manager")
def fixture_mock_txn_name_manager(mocker):
return mocker.patch(
"solarwinds_apm.apm_txname_manager.SolarWindsTxnNameManager"
)
188 changes: 188 additions & 0 deletions tests/unit/test_configurator/test_configurator_traces_exporter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# © 2023 SolarWinds Worldwide, LLC. All rights reserved.
#
# 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.

import os
import pytest

from solarwinds_apm import configurator

# otel fixtures
from .fixtures.batch_span_processor import fixture_mock_bsprocessor

Check notice

Code scanning / CodeQL

Unused import

Import of 'fixture_mock_bsprocessor' is not used.
from .fixtures.trace import get_trace_mocks

# apm python fixtures
from .fixtures.apm_config import (
fixture_mock_apmconfig_disabled,
fixture_mock_apmconfig_enabled,
)
Comment on lines +17 to +20

Check notice

Code scanning / CodeQL

Unused import

Import of 'fixture_mock_apmconfig_disabled' is not used. Import of 'fixture_mock_apmconfig_enabled' is not used.
from .fixtures.extension import (
fixture_mock_reporter,
)
Comment on lines +21 to +23

Check notice

Code scanning / CodeQL

Unused import

Import of 'fixture_mock_reporter' is not used.
from .fixtures.fwkv_manager import fixture_mock_fwkv_manager

Check notice

Code scanning / CodeQL

Unused import

Import of 'fixture_mock_fwkv_manager' is not used.
from .fixtures.meter_manager import fixture_mock_meter_manager

Check notice

Code scanning / CodeQL

Unused import

Import of 'fixture_mock_meter_manager' is not used.
from .fixtures.txn_name_manager import fixture_mock_txn_name_manager

Check notice

Code scanning / CodeQL

Unused import

Import of 'fixture_mock_txn_name_manager' is not used.


class TestConfiguratorTracesExporter:
def test_configure_traces_exporter_disabled(
self,
mocker,
mock_reporter,
mock_txn_name_manager,
mock_fwkv_manager,
mock_apmconfig_disabled,
mock_bsprocessor,
):
# Mock Otel
mock_trace, mock_get_tracer_provider, mock_add_span_processor = get_trace_mocks(mocker)

test_configurator = configurator.SolarWindsConfigurator()
test_configurator._configure_traces_exporter(
mock_reporter,
mock_txn_name_manager,
mock_fwkv_manager,
mock_apmconfig_disabled,
)
mock_bsprocessor.assert_not_called()
mock_get_tracer_provider.assert_not_called()
mock_add_span_processor.assert_not_called()

def test_configure_traces_exporter_none(
self,
mocker,
mock_reporter,
mock_txn_name_manager,
mock_fwkv_manager,
mock_apmconfig_enabled,
mock_bsprocessor,
):
# Save any EXPORTER env var for later
old_traces_exporter = os.environ.get("OTEL_TRACES_EXPORTER", None)
if old_traces_exporter:
del os.environ["OTEL_TRACES_EXPORTER"]

# Mock Otel
mock_trace, mock_get_tracer_provider, mock_add_span_processor = get_trace_mocks(mocker)

test_configurator = configurator.SolarWindsConfigurator()
test_configurator._configure_traces_exporter(
mock_reporter,
mock_txn_name_manager,
mock_fwkv_manager,
mock_apmconfig_enabled,
)
mock_bsprocessor.assert_not_called()
mock_get_tracer_provider.assert_not_called()
mock_add_span_processor.assert_not_called()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this traces_exporter_none test, does it mean if OTEL_TRACES_EXPORTER env var is not set, our custom distro does not configure the solarwinds_exporter either?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooh yes good question.

For _configure_traces_exporter in complete isolation, yes it could be that OTEL_TRACES_EXPORTER is None so this test is for that scenario.

For _configure_traces_exporter running at distro startup, that scenario shouldn't happen. SolarWindsDistro._configure is called first to set env var defaults. Outside lambda the default is the SW exporter. Then SolarWindsConfigurator._configure is called to init the exporters etc. If OTEL_TRACES_EXPORTER wasn't set by user, then default from distro is used instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah gotcha, thanks for the pointers :)


# Restore old EXPORTER
if old_traces_exporter:
os.environ["OTEL_TRACES_EXPORTER"] = old_traces_exporter

def test_configure_traces_exporter_invalid(
self,
mocker,
mock_reporter,
mock_txn_name_manager,
mock_fwkv_manager,
mock_apmconfig_enabled,
mock_bsprocessor,
):
# Save any EXPORTER env var for later
old_traces_exporter = os.environ.get("OTEL_TRACES_EXPORTER", None)
if old_traces_exporter:
del os.environ["OTEL_TRACES_EXPORTER"]

# Mock entry points
mock_iter_entry_points = mocker.patch(
"solarwinds_apm.apm_config.iter_entry_points"
)
mock_iter_entry_points.configure_mock(
side_effect=StopIteration("mock error")
)
mocker.patch.dict(
os.environ,
{
"OTEL_TRACES_EXPORTER": "invalid_exporter"
}
)

# Mock Otel
mock_trace, mock_get_tracer_provider, mock_add_span_processor = get_trace_mocks(mocker)

# Test!
test_configurator = configurator.SolarWindsConfigurator()

with pytest.raises(Exception):
test_configurator._configure_traces_exporter(
mock_reporter,
mock_txn_name_manager,
mock_fwkv_manager,
mock_apmconfig_enabled,
)

mock_bsprocessor.assert_not_called()
mock_get_tracer_provider.assert_not_called()
mock_add_span_processor.assert_not_called()

# Restore old EXPORTER
if old_traces_exporter:
os.environ["OTEL_TRACES_EXPORTER"] = old_traces_exporter

def test_configure_traces_exporter_valid(
self,
mocker,
mock_reporter,
mock_txn_name_manager,
mock_fwkv_manager,
mock_apmconfig_enabled,
mock_bsprocessor,
):
# Save any EXPORTER env var for later
old_traces_exporter = os.environ.get("OTEL_TRACES_EXPORTER", None)
if old_traces_exporter:
del os.environ["OTEL_TRACES_EXPORTER"]

# Mock entry points
mock_exporter_class = mocker.MagicMock()
mock_exporter_entry_point = mocker.Mock()
mock_exporter_entry_point.configure_mock(
**{
"load": mock_exporter_class
}
)
mock_points = iter([mock_exporter_entry_point])
mock_iter_entry_points = mocker.patch(
"solarwinds_apm.configurator.iter_entry_points"
)
mock_iter_entry_points.configure_mock(
return_value=mock_points
)
mocker.patch.dict(
os.environ,
{
"OTEL_TRACES_EXPORTER": "valid_exporter"
}
)

# Mock Otel
mock_trace, mock_get_tracer_provider, mock_add_span_processor = get_trace_mocks(mocker)

# Test!
test_configurator = configurator.SolarWindsConfigurator()
test_configurator._configure_traces_exporter(
mock_reporter,
mock_txn_name_manager,
mock_fwkv_manager,
mock_apmconfig_enabled,
)
mock_bsprocessor.assert_called_once()
mock_get_tracer_provider.assert_called_once()
mock_add_span_processor.assert_called_once()

# Restore old EXPORTER
if old_traces_exporter:
os.environ["OTEL_TRACES_EXPORTER"] = old_traces_exporter