From fc7381ad8a5499fec249d2c4f9e918f16b29000f Mon Sep 17 00:00:00 2001 From: Hannah Stepanek Date: Wed, 17 Apr 2024 16:27:08 -0700 Subject: [PATCH 1/2] Add NEW_RELIC_K8S_OPERATOR_ENABLED --- newrelic/bootstrap/sitecustomize.py | 3 ++- newrelic/config.py | 1 + newrelic/core/config.py | 10 +++++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/newrelic/bootstrap/sitecustomize.py b/newrelic/bootstrap/sitecustomize.py index 6640af0e9..19abbc31f 100644 --- a/newrelic/bootstrap/sitecustomize.py +++ b/newrelic/bootstrap/sitecustomize.py @@ -121,8 +121,9 @@ def log_message(text, *args, **kwargs): log_message("python_prefix_matches = %r", python_prefix_matches) log_message("python_version_matches = %r", python_version_matches) +k8s_operator_enabled = os.environ.get("NEW_RELIC_K8S_OPERATOR_ENABLED", False) -if python_prefix_matches and python_version_matches: +if k8s_operator_enabled or (python_prefix_matches and python_version_matches): # We also need to skip agent initialisation if neither the license # key or config file environment variables are set. We do this as # some people like to use a common startup script which always uses diff --git a/newrelic/config.py b/newrelic/config.py index 8e10218f9..5b9634594 100644 --- a/newrelic/config.py +++ b/newrelic/config.py @@ -564,6 +564,7 @@ def _process_configuration(section): _process_setting(section, "ai_monitoring.enabled", "getboolean", None) _process_setting(section, "ai_monitoring.record_content.enabled", "getboolean", None) _process_setting(section, "ai_monitoring.streaming.enabled", "getboolean", None) + _process_setting(section, "k8s_operator.enabled", "getboolean", None) _process_setting(section, "package_reporting.enabled", "getboolean", None) diff --git a/newrelic/core/config.py b/newrelic/core/config.py index 677f278aa..3939d40e6 100644 --- a/newrelic/core/config.py +++ b/newrelic/core/config.py @@ -162,6 +162,10 @@ class AIMonitoringRecordContentSettings(Settings): pass +class K8sOperatorSettings(Settings): + pass + + class PackageReportingSettings(Settings): pass @@ -430,6 +434,7 @@ class EventHarvestConfigHarvestLimitSettings(Settings): _settings.ai_monitoring = AIMonitoringSettings() _settings.ai_monitoring.streaming = AIMonitoringStreamingSettings() _settings.ai_monitoring.record_content = AIMonitoringRecordContentSettings() +_settings.k8s_operator = K8sOperatorSettings() _settings.package_reporting = PackageReportingSettings() _settings.attributes = AttributesSettings() _settings.browser_monitoring = BrowserMonitorSettings() @@ -745,7 +750,9 @@ def default_otlp_host(host): _settings.gc_runtime_metrics.enabled = False _settings.gc_runtime_metrics.top_object_count_limit = 5 -_settings.memory_runtime_pid_metrics.enabled = _environ_as_bool("NEW_RELIC_MEMORY_RUNTIME_METRICS_ENABLED", default=True) +_settings.memory_runtime_pid_metrics.enabled = _environ_as_bool( + "NEW_RELIC_MEMORY_RUNTIME_METRICS_ENABLED", default=True +) _settings.transaction_events.enabled = True _settings.transaction_events.attributes.enabled = True @@ -953,6 +960,7 @@ def default_otlp_host(host): "NEW_RELIC_AI_MONITORING_RECORD_CONTENT_ENABLED", default=True ) _settings.ai_monitoring._llm_token_count_callback = None +_settings.k8s_operator.enabled = _environ_as_bool("NEW_RELIC_K8S_OPERATOR_ENABLED", default=False) _settings.package_reporting.enabled = _environ_as_bool("NEW_RELIC_PACKAGE_REPORTING_ENABLED", default=True) _settings.ml_insights_events.enabled = _environ_as_bool("NEW_RELIC_ML_INSIGHTS_EVENTS_ENABLED", default=False) From 250ca8c0b1bac92f237b4ca04b7d4bd35410cb20 Mon Sep 17 00:00:00 2001 From: Timothy Pansino <11214426+TimPansino@users.noreply.github.com> Date: Mon, 20 May 2024 10:36:55 -0700 Subject: [PATCH 2/2] K8s sitecustomize changes (#1144) * Add sitecustomizes changes for k8s operator to agent * Bump tests * Port error message fix * Fixup: lint errors --------- Co-authored-by: Hannah Stepanek --- newrelic/bootstrap/sitecustomize.py | 111 +++++++++++++++++++--------- 1 file changed, 75 insertions(+), 36 deletions(-) diff --git a/newrelic/bootstrap/sitecustomize.py b/newrelic/bootstrap/sitecustomize.py index 19abbc31f..43a0d6d18 100644 --- a/newrelic/bootstrap/sitecustomize.py +++ b/newrelic/bootstrap/sitecustomize.py @@ -16,13 +16,12 @@ import sys import time -# Define some debug logging routines to help sort out things when this -# all doesn't work as expected. - - # Avoiding additional imports by defining PY2 manually PY2 = sys.version_info[0] == 2 +# Define some debug logging routines to help sort out things when this +# all doesn't work as expected. + startup_debug = os.environ.get("NEW_RELIC_STARTUP_DEBUG", "off").lower() in ("on", "true", "1") @@ -35,6 +34,14 @@ def log_message(text, *args, **kwargs): sys.stdout.flush() +def del_sys_path_entry(path): + if path and path in sys.path: + try: + del sys.path[sys.path.index(path)] + except Exception: + pass + + log_message("New Relic Bootstrap (%s)", __file__) log_message("working_directory = %r", os.getcwd()) @@ -70,25 +77,19 @@ def log_message(text, *args, **kwargs): # the search, and then load what was found. boot_directory = os.path.dirname(__file__) -root_directory = os.path.dirname(os.path.dirname(boot_directory)) - -log_message("root_directory = %r", root_directory) log_message("boot_directory = %r", boot_directory) -path = list(sys.path) - -if boot_directory in path: - del path[path.index(boot_directory)] +del_sys_path_entry(boot_directory) try: if PY2: import imp - module_spec = imp.find_module("sitecustomize", path) + module_spec = imp.find_module("sitecustomize", sys.path) else: from importlib.machinery import PathFinder - module_spec = PathFinder.find_spec("sitecustomize", path=path) + module_spec = PathFinder.find_spec("sitecustomize", path=sys.path) except ImportError: pass @@ -118,10 +119,11 @@ def log_message(text, *args, **kwargs): python_prefix_matches = expected_python_prefix == actual_python_prefix python_version_matches = expected_python_version == actual_python_version +k8s_operator_enabled = os.environ.get("NEW_RELIC_K8S_OPERATOR_ENABLED", "off").lower() in ("on", "true", "1") log_message("python_prefix_matches = %r", python_prefix_matches) log_message("python_version_matches = %r", python_version_matches) -k8s_operator_enabled = os.environ.get("NEW_RELIC_K8S_OPERATOR_ENABLED", False) +log_message("k8s_operator_enabled = %r", k8s_operator_enabled) if k8s_operator_enabled or (python_prefix_matches and python_version_matches): # We also need to skip agent initialisation if neither the license @@ -131,40 +133,77 @@ def log_message(text, *args, **kwargs): # actually run based on the presence of the environment variables. license_key = os.environ.get("NEW_RELIC_LICENSE_KEY", None) - + developer_mode = os.environ.get("NEW_RELIC_DEVELOPER_MODE", "off").lower() in ("on", "true", "1") config_file = os.environ.get("NEW_RELIC_CONFIG_FILE", None) environment = os.environ.get("NEW_RELIC_ENVIRONMENT", None) + initialize_agent = bool(license_key or config_file or developer_mode) + + log_message("initialize_agent = %r", initialize_agent) + + if initialize_agent: + if not k8s_operator_enabled: + # When installed as an egg with buildout, the root directory for + # packages is not listed in sys.path and scripts instead set it + # after Python has started up. This will cause importing of + # 'newrelic' module to fail. What we do is see if the root + # directory where the package is held is in sys.path and if not + # insert it. For good measure we remove it after having imported + # 'newrelic' module to reduce chance that will cause any issues. + # If it is a buildout created script, it will replace the whole + # sys.path again later anyway. + root_directory = os.path.dirname(os.path.dirname(boot_directory)) + log_message("root_directory = %r", root_directory) + + new_relic_path = root_directory + do_insert_path = root_directory not in sys.path + else: + # When installed with the kubernetes operator, we need to attempt + # to find a distribution from our initcontainer that matches the + # current environment. For wheels, this is platform dependent and we + # rely on pip to identify the correct wheel to use. If no suitable + # wheel can be found, we will fall back to the sdist and disable + # extensions. Once the appropriate distribution is found, we import + # it and leave the entry in sys.path. This allows users to import + # the 'newrelic' module later and use our APIs in their code. + try: + sys.path.insert(0, boot_directory) + from newrelic_k8s_operator import find_supported_newrelic_distribution + finally: + del_sys_path_entry(boot_directory) - log_message("initialize_agent = %r", bool(license_key or config_file)) + new_relic_path = find_supported_newrelic_distribution() + do_insert_path = True - if license_key or config_file: - # When installed as an egg with buildout, the root directory for - # packages is not listed in sys.path and scripts instead set it - # after Python has started up. This will cause importing of - # 'newrelic' module to fail. What we do is see if the root - # directory where the package is held is in sys.path and if not - # insert it. For good measure we remove it after having imported - # 'newrelic' module to reduce chance that will cause any issues. - # If it is a buildout created script, it will replace the whole - # sys.path again later anyway. + # Now that the appropriate location of the module has been identified, + # either by the kubernetes operator or this script, we are ready to import + # the 'newrelic' module to make it available in sys.modules. If the location + # containing it was not found on sys.path, do_insert_path will be set and + # the location will be inserted into sys.path. The module is then imported, + # and the sys.path entry is removed afterwards to reduce chance that will + # cause any issues. - do_insert_path = root_directory not in sys.path - if do_insert_path: - sys.path.insert(0, root_directory) + log_message("new_relic_path = %r" % new_relic_path) + log_message("do_insert_path = %r" % do_insert_path) - import newrelic.config + try: + if do_insert_path: + sys.path.insert(0, new_relic_path) - log_message("agent_version = %r", newrelic.version) + import newrelic - if do_insert_path: - try: - del sys.path[sys.path.index(root_directory)] - except Exception: - pass + log_message("agent_version = %r", newrelic.version) + finally: + if do_insert_path: + del_sys_path_entry(new_relic_path) # Finally initialize the agent. + import newrelic.config newrelic.config.initialize(config_file, environment) + else: + log_message( + "New Relic could not start due to missing configuration. Either NEW_RELIC_LICENSE_KEY or NEW_RELIC_CONFIG_FILE are required." + ) else: log_message( """New Relic could not start because the newrelic-admin script was called from a Python installation that is different from the Python installation that is currently running. To fix this problem, call the newrelic-admin script from the Python installation that is currently running (details below).