From dfaf5e89da219fb76e5fda9920c5d67e855bc18d Mon Sep 17 00:00:00 2001 From: Tammy Baylis Date: Wed, 18 Oct 2023 18:09:56 -0700 Subject: [PATCH] Add SW_APM_EXPERIMENTAL_OTEL_COLLECTOR config --- solarwinds_apm/apm_config.py | 28 +++++- solarwinds_apm/configurator.py | 6 ++ .../test_apm_config_experimental.py | 98 +++++++++++++++++++ 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 tests/unit/test_apm_config/test_apm_config_experimental.py diff --git a/solarwinds_apm/apm_config.py b/solarwinds_apm/apm_config.py index a0cabcbb..44dde186 100644 --- a/solarwinds_apm/apm_config.py +++ b/solarwinds_apm/apm_config.py @@ -73,9 +73,12 @@ class SolarWindsApmConfig: _CONFIG_FILE_DEFAULT = "./solarwinds-apm-config.json" _DELIMITER = "." + _EXP_KEYS = ["otel_collector"] + _EXP_PREFIX = "experimental_" _KEY_MASK = "{}...{}:{}" _KEY_MASK_BAD_FORMAT = "{}..." _KEY_MASK_BAD_FORMAT_SHORT = "{}" + _SW_PREFIX = "sw_apm_" def __init__( self, @@ -109,6 +112,7 @@ def __init__( "reporter_file_single": 0, "proxy": "", "transaction_filters": [], + "experimental": {}, } self.agent_enabled = True self.update_with_cnf_file() @@ -611,7 +615,10 @@ def update_with_env_var(self) -> None: if key == "transaction": # we do not allow complex config options to be set via environment variables continue - env = "SW_APM_" + key.upper() + if key == "experimental": + # but we do allow flat SW_APM_EXPERIMENTAL_OTEL_COLLECTOR setting to match js + key = self._EXP_PREFIX + "otel_collector" + env = (self._SW_PREFIX + key).upper() val = os.environ.get(env) if val is not None: self._set_config_value(key, val) @@ -708,6 +715,25 @@ def _set_config_value(self, keys_str: str, val: Any) -> Any: self.__config[key] = val # update logging level of agent logger apm_logging.set_sw_log_level(val) + elif keys == ["experimental"]: + for exp_k, exp_v in val.items(): + if exp_k in self._EXP_KEYS: + exp_v = self.convert_to_bool(exp_v) + if exp_v is None: + logger.warning( + "Ignore invalid config of experimental %s", + exp_k, + ) + else: + self.__config["experimental"][exp_k] = exp_v + elif keys == ["experimental_otel_collector"]: + val = self.convert_to_bool(val) + if val is None: + logger.warning( + "Ignore invalid config of experimental otel_collector" + ) + else: + self.__config["experimental"]["otel_collector"] = val elif isinstance(sub_dict, dict) and keys[-1] in sub_dict: if isinstance(sub_dict[keys[-1]], bool): val = self.convert_to_bool(val) diff --git a/solarwinds_apm/configurator.py b/solarwinds_apm/configurator.py index f92a4603..a86338de 100644 --- a/solarwinds_apm/configurator.py +++ b/solarwinds_apm/configurator.py @@ -224,6 +224,12 @@ def _configure_metrics_exporter( logger.error("Tracing disabled. Cannot set metrics exporter.") return + if not apm_config.get("experimental").get("otel_collector") is True: + logger.debug( + "Experimental otel_collector flag not configured. Not setting metrics exporter." + ) + return + # SolarWindsDistro._configure does not setdefault so this # could be None environ_exporter = os.environ.get( diff --git a/tests/unit/test_apm_config/test_apm_config_experimental.py b/tests/unit/test_apm_config/test_apm_config_experimental.py new file mode 100644 index 00000000..b46fd387 --- /dev/null +++ b/tests/unit/test_apm_config/test_apm_config_experimental.py @@ -0,0 +1,98 @@ +# © 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 + +from solarwinds_apm import apm_config + + +def helper_mock_common(mocker): + mock_iter_entry_points = mocker.patch( + "solarwinds_apm.apm_config.iter_entry_points" + ) + mock_points = mocker.MagicMock() + mock_points.__iter__.return_value = ["foo"] + mock_iter_entry_points.configure_mock( + return_value=mock_points + ) + +class TestSolarWindsApmConfigExperimental: + def test_experimental_default( + self, + mocker, + ): + helper_mock_common(mocker) + mocker.patch.dict(os.environ, { + "SW_APM_SERVICE_KEY": "valid:key", + }) + resulting_config = apm_config.SolarWindsApmConfig() + assert resulting_config.get("experimental") == {} + + def test_experimental_env_var_otelcol_not_bool( + self, + mocker, + ): + helper_mock_common(mocker) + mocker.patch.dict(os.environ, { + "SW_APM_SERVICE_KEY": "valid:key", + "SW_APM_EXPERIMENTAL_OTEL_COLLECTOR": "foo" + }) + resulting_config = apm_config.SolarWindsApmConfig() + assert resulting_config.get("experimental") == {} + + def test_experimental_env_var_otelcol_true( + self, + mocker, + ): + helper_mock_common(mocker) + mocker.patch.dict(os.environ, { + "SW_APM_SERVICE_KEY": "valid:key", + "SW_APM_EXPERIMENTAL_OTEL_COLLECTOR": "true" + }) + resulting_config = apm_config.SolarWindsApmConfig() + assert resulting_config.get("experimental") == {"otel_collector": True} + + def test_experimental_cnf_file_otelcol_not_bool( + self, + mocker, + ): + helper_mock_common(mocker) + mocker.patch.dict(os.environ, { + "SW_APM_SERVICE_KEY": "valid:key", + }) + mock_get_cnf_dict = mocker.patch( + "solarwinds_apm.apm_config.SolarWindsApmConfig.get_cnf_dict" + ) + mock_get_cnf_dict.configure_mock( + return_value={ + "experimental": { + "otel_collector": "foo", + }, + } + ) + resulting_config = apm_config.SolarWindsApmConfig() + assert resulting_config.get("experimental") == {} + + def test_experimental_cnf_file_otelcol_true( + self, + mocker, + ): + helper_mock_common(mocker) + mocker.patch.dict(os.environ, { + "SW_APM_SERVICE_KEY": "valid:key", + }) + mock_get_cnf_dict = mocker.patch( + "solarwinds_apm.apm_config.SolarWindsApmConfig.get_cnf_dict" + ) + mock_get_cnf_dict.configure_mock( + return_value={ + "experimental": { + "otel_collector": True, + }, + } + ) + resulting_config = apm_config.SolarWindsApmConfig() + assert resulting_config.get("experimental") == {"otel_collector": True}