diff --git a/src/Agent/NewRelic/Agent/Core/AgentHealth/AgentHealthReporter.cs b/src/Agent/NewRelic/Agent/Core/AgentHealth/AgentHealthReporter.cs index 541b5fe870..c4cbbe0fae 100644 --- a/src/Agent/NewRelic/Agent/Core/AgentHealth/AgentHealthReporter.cs +++ b/src/Agent/NewRelic/Agent/Core/AgentHealth/AgentHealthReporter.cs @@ -17,6 +17,8 @@ using System.Linq; using System.Net; using System.Threading; +using System.IO; +using System.Text; namespace NewRelic.Agent.Core.AgentHealth { @@ -26,6 +28,8 @@ public class AgentHealthReporter : ConfigurationBasedService, IAgentHealthReport private readonly IMetricBuilder _metricBuilder; private readonly IScheduler _scheduler; + private readonly IFileWrapper _fileWrapper; + private readonly IDirectoryWrapper _directoryWrapper; private readonly IList _recurringLogData = new ConcurrentList(); private readonly IDictionary _agentHealthEventCounters = new Dictionary(); private readonly ConcurrentDictionary _logLinesCountByLevel = new ConcurrentDictionary(); @@ -38,10 +42,30 @@ public class AgentHealthReporter : ConfigurationBasedService, IAgentHealthReport private InterlockedCounter _traceContextCreateSuccessCounter; private InterlockedCounter _traceContextAcceptSuccessCounter; - public AgentHealthReporter(IMetricBuilder metricBuilder, IScheduler scheduler) + private HealthCheck _healthCheck; + private bool _healthChecksInitialized; + private bool _healthChecksFailed; + private string _healthCheckPath; + + public AgentHealthReporter(IMetricBuilder metricBuilder, IScheduler scheduler, IFileWrapper fileWrapper, IDirectoryWrapper directoryWrapper) { _metricBuilder = metricBuilder; _scheduler = scheduler; + _fileWrapper = fileWrapper; + _directoryWrapper = directoryWrapper; + + if (!_configuration.AgentControlEnabled) + Log.Debug("Agent Control is disabled. Health checks will not be reported."); + else + { + Log.Debug("Agent Control health checks will be published every {HealthCheckInterval} seconds", _configuration.HealthFrequency); + + _healthCheck = new() { IsHealthy = true, Status = "Agent starting", LastError = string.Empty }; + + // schedule the health check and issue the first one immediately + _scheduler.ExecuteEvery(PublishAgentControlHealthCheck, TimeSpan.FromSeconds(_configuration.HealthFrequency), TimeSpan.Zero); + } + _scheduler.ExecuteEvery(LogPeriodicReport, _timeBetweenExecutions); var agentHealthEvents = Enum.GetValues(typeof(AgentHealthEvent)) as AgentHealthEvent[]; foreach (var agentHealthEvent in agentHealthEvents) @@ -258,9 +282,9 @@ public void ReportIfHostIsLinuxOs() { #if NETSTANDARD2_0 - bool isLinux = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux); - var metric =_metricBuilder.TryBuildLinuxOsMetric(isLinux); - TrySend(metric); + bool isLinux = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux); + var metric = _metricBuilder.TryBuildLinuxOsMetric(isLinux); + TrySend(metric); #endif } @@ -667,6 +691,107 @@ public void ReportLogForwardingConfiguredValues() #endregion + #region Agent Control + + private void ReportIfAgentControlHealthEnabled() + { + if (_configuration.AgentControlEnabled) + { + ReportSupportabilityCountMetric(MetricNames.SupportabilityAgentControlHealthEnabled); + } + } + + public void SetAgentControlStatus((bool IsHealthy, string Code, string Status) healthStatus, params string[] statusParams) + { + // Do nothing if agent control is not enabled + if (!_configuration.AgentControlEnabled) + return; + + if (healthStatus.Equals(HealthCodes.AgentShutdownHealthy)) + { + if (_healthCheck.IsHealthy) + { + _healthCheck.TrySetHealth(healthStatus); + } + } + else + { + _healthCheck.TrySetHealth(healthStatus, statusParams); + } + } + + public void PublishAgentControlHealthCheck() + { + if (!_healthChecksInitialized) // initialize on first invocation + { + InitializeHealthChecks(); + _healthChecksInitialized = true; + } + + // stop the scheduled task if agent control isn't enabled or health checks fail for any reason + if (!_configuration.AgentControlEnabled || _healthChecksFailed) + { + _scheduler.StopExecuting(PublishAgentControlHealthCheck); + return; + } + + var healthCheckYaml = _healthCheck.ToYaml(); + + Log.Finest("Publishing Agent Control health check report: {HealthCheckYaml}", healthCheckYaml); + + try + { + using var fs = _fileWrapper.OpenWrite(Path.Combine(_healthCheckPath, _healthCheck.FileName)); + var payloadBytes = Encoding.UTF8.GetBytes(healthCheckYaml); + fs.Write(payloadBytes, 0, payloadBytes.Length); + fs.Flush(); + } + catch (Exception ex) + { + Log.Warn(ex, "Failed to write Agent Control health check report. Health checks will be disabled."); + _healthChecksFailed = true; + } + } + + private void InitializeHealthChecks() + { + if (!_configuration.AgentControlEnabled) + { + Log.Debug("Agent Control is disabled. Health checks will not be reported."); + return; + } + + Log.Debug("Initializing Agent Control health checks"); + + // make sure the delivery location is a file URI + var fileUri = new Uri(_configuration.HealthDeliveryLocation); + if (fileUri.Scheme != Uri.UriSchemeFile) + { + Log.Warn( + "Agent Control is enabled but the provided agent_control.health.delivery_location is not a file URL. Health checks will be disabled."); + _healthChecksFailed = true; + return; + } + + _healthCheckPath = fileUri.LocalPath; + + // verify the directory exists + if (!_directoryWrapper.Exists(_healthCheckPath)) + { + Log.Warn("Agent Control is enabled but the path specified in agent_control.health.delivery_location does not exist. Health checks will be disabled."); + _healthChecksFailed = true; + } + + // verify we can write a file to the directory + var testFile = Path.Combine(_healthCheckPath, Path.GetRandomFileName()); + if (!_fileWrapper.TryCreateFile(testFile)) + { + Log.Warn("Agent Control is enabled but the agent is unable to create files in the directory specified in agent_control.health.delivery_location. Health checks will be disabled."); + _healthChecksFailed = true; + } + } + #endregion + public void ReportSupportabilityPayloadsDroppeDueToMaxPayloadSizeLimit(string endpoint) { TrySend(_metricBuilder.TryBuildSupportabilityPayloadsDroppedDueToMaxPayloadLimit(endpoint)); @@ -686,6 +811,7 @@ private void CollectOneTimeMetrics() ReportIfInstrumentationIsDisabled(); ReportIfGCSamplerV2IsEnabled(); ReportIfAwsAccountIdProvided(); + ReportIfAgentControlHealthEnabled(); } public void CollectMetrics() @@ -857,5 +983,10 @@ private void ReportIfAwsAccountIdProvided() ReportSupportabilityCountMetric(MetricNames.SupportabilityAwsAccountIdProvided); } } + + /// + /// FOR UNIT TESTING ONLY + /// + public bool HealthCheckFailed => _healthChecksFailed; } } diff --git a/src/Agent/NewRelic/Agent/Core/AgentHealth/HealthCheck.cs b/src/Agent/NewRelic/Agent/Core/AgentHealth/HealthCheck.cs new file mode 100644 index 0000000000..794d005aa6 --- /dev/null +++ b/src/Agent/NewRelic/Agent/Core/AgentHealth/HealthCheck.cs @@ -0,0 +1,61 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using NewRelic.Agent.Core.Utilities; + +namespace NewRelic.Agent.Core.AgentHealth +{ + public class HealthCheck + { + private const int NanoSecondsPerMillisecond = 1000000; + + public bool IsHealthy { get; internal set; } + public string Status { get; internal set; } + public string LastError { get; internal set; } + public DateTime StartTime { get; } = DateTime.UtcNow; + public DateTime StatusTime { get; internal set; } + public string FileName { get; } = "health-" + System.Guid.NewGuid().ToString("N") + ".yml"; + + /// + /// Set the health status of the agent, but only update changed values. + /// + /// + /// + /// + public void TrySetHealth((bool IsHealthy, string Code, string Status) healthStatus, params string[] statusParams) + { + lock (this) + { + if (IsHealthy != healthStatus.IsHealthy) + { + IsHealthy = healthStatus.IsHealthy; + } + + if (string.IsNullOrEmpty(Status) || !Status.Equals(healthStatus.Code, StringComparison.OrdinalIgnoreCase)) + { + Status = statusParams is { Length: > 0 } ? + string.Format(healthStatus.Status, statusParams) + : + healthStatus.Status; + } + + if (string.IsNullOrEmpty(LastError) || !LastError.Equals(healthStatus.Code, StringComparison.OrdinalIgnoreCase)) + { + LastError = healthStatus.Code; + } + + StatusTime = DateTime.UtcNow; + } + } + + public string ToYaml() + { + lock (this) + { + return + $"healthy: {IsHealthy}\nstatus: {Status}\nlast_error: {LastError}\nstart_time_unix_nano: {StartTime.ToUnixTimeMilliseconds() * NanoSecondsPerMillisecond}\nstatus_time_unix_nano: {StatusTime.ToUnixTimeMilliseconds() * NanoSecondsPerMillisecond}"; + } + } + } +} diff --git a/src/Agent/NewRelic/Agent/Core/AgentHealth/HealthCodes.cs b/src/Agent/NewRelic/Agent/Core/AgentHealth/HealthCodes.cs new file mode 100644 index 0000000000..5c60eaf6fe --- /dev/null +++ b/src/Agent/NewRelic/Agent/Core/AgentHealth/HealthCodes.cs @@ -0,0 +1,84 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +namespace NewRelic.Agent.Core.AgentHealth +{ + public static class HealthCodes + { + /// + /// Healthy + /// + public static readonly (bool IsHealthy, string Code, string Status) Healthy = (true, "NR-APM-000", + "Healthy"); + + /// + /// Invalid license key (HTTP status code 401) + /// + public static readonly (bool IsHealthy, string Code, string Status) LicenseKeyInvalid = (false, "NR-APM-001", + "Invalid license key (HTTP status code 401)"); + + /// + /// License key missing in configuration + /// + public static readonly (bool IsHealthy, string Code, string Status) LicenseKeyMissing = (false, "NR-APM-002", + "License key missing in configuration"); + + /// + /// Forced disconnect received from New Relic (HTTP status code 410) + /// + public static readonly (bool IsHealthy, string Code, string Status) ForceDisconnect = (false, "NR-APM-003", + "Forced disconnect received from New Relic (HTTP status code 410)"); + + /// + /// HTTP error response code [%s] received from New Relic while sending data type [%s] + /// + public static readonly (bool IsHealthy, string Code, string Status) HttpError = (false, "NR-APM-004", + "HTTP error response code {0} received from New Relic while sending data type {1}"); + + /// + /// Missing application name in agent configuration + /// + public static readonly (bool IsHealthy, string Code, string Status) ApplicationNameMissing = (false, "NR-APM-005", + "Missing application name in agent configuration"); + + /// + /// The maximum number of configured app names (3) exceeded + /// + public static readonly (bool IsHealthy, string Code, string Status) MaxApplicationNamesExceeded = (false, "NR-APM-006", + "The maximum number of configured app names (3) exceeded"); + + /// + /// HTTP Proxy configuration error; response code [%s] + /// + public static readonly (bool IsHealthy, string Code, string Status) HttpProxyError = (false, "NR-APM-007", + "HTTP Proxy configuration error; response code {0}"); + + /// + /// Agent is disabled via configuration + /// + public static readonly (bool IsHealthy, string Code, string Status) AgentDisabledByConfiguration = (false, "NR-APM-008", + "Agent is disabled via configuration"); + + /// + /// Failed to connect to New Relic data collector + /// + public static readonly (bool IsHealthy, string Code, string Status) FailedToConnect = (false, "NR-APM-009", + "Failed to connect to New Relic data collector"); + + /// + /// Agent has shutdown + /// Only be reported if agent is "healthy" on shutdown. + /// If the agent status is not Healthy on agent shutdown, the existing error MUST not be overwritten. + /// + public static readonly (bool IsHealthy, string Code, string Status) AgentShutdownHealthy = (true, "NR-APM-099", + "Agent has shutdown"); + + // Agent health codes for the .NET agent are 200-299 + + /// + /// Agent has shutdown with exception [%s] + /// + public static readonly (bool IsHealthy, string Code, string Status) AgentShutdownError = (false, "NR-APM-200", + "Agent has shutdown with exception {0}"); + } +} diff --git a/src/Agent/NewRelic/Agent/Core/AgentHealth/IAgentHealthReporter.cs b/src/Agent/NewRelic/Agent/Core/AgentHealth/IAgentHealthReporter.cs index 23eed86cf7..1af18ed006 100644 --- a/src/Agent/NewRelic/Agent/Core/AgentHealth/IAgentHealthReporter.cs +++ b/src/Agent/NewRelic/Agent/Core/AgentHealth/IAgentHealthReporter.cs @@ -151,5 +151,7 @@ public interface IAgentHealthReporter : IOutOfBandMetricSource void ReportLogForwardingEnabledWithFramework(string logFramework); void ReportByteMetric(string metricName, long totalBytes, long? exclusiveBytes = null); void ReportLoggingEventsEmpty(int count = 1); + void SetAgentControlStatus((bool IsHealthy, string Code, string Status) healthStatus, params string[] statusParams); + void PublishAgentControlHealthCheck(); } } diff --git a/src/Agent/NewRelic/Agent/Core/AgentManager.cs b/src/Agent/NewRelic/Agent/Core/AgentManager.cs index 2fccadd0c7..473993ab95 100644 --- a/src/Agent/NewRelic/Agent/Core/AgentManager.cs +++ b/src/Agent/NewRelic/Agent/Core/AgentManager.cs @@ -3,6 +3,7 @@ using NewRelic.Agent.Api; using NewRelic.Agent.Configuration; +using NewRelic.Agent.Core.AgentHealth; using NewRelic.Agent.Core.Commands; using NewRelic.Agent.Core.Config; using NewRelic.Agent.Core.Configuration; @@ -81,6 +82,7 @@ public static IAgentManager Instance private IConfiguration Configuration { get { return _configurationSubscription.Configuration; } } private ThreadProfilingService _threadProfilingService; private readonly IWrapperService _wrapperService; + private readonly IAgentHealthReporter _agentHealthReporter; private volatile bool _shutdownEventReceived; private volatile bool _isInitialized; @@ -154,6 +156,9 @@ private AgentManager() var agentApi = _container.Resolve(); _wrapperService = _container.Resolve(); + // Start the AgentHealthReporter early so that we can potentially report health issues during startup + _agentHealthReporter = _container.Resolve(); + // Attempt to auto start the agent once all services have resolved, except in serverless mode if (!bootstrapConfig.ServerlessModeEnabled) _container.Resolve().AttemptAutoStart(); @@ -288,6 +293,9 @@ private void LogInitialized() "NEW_RELIC_SEND_DATA_ON_EXIT", "NEW_RELIC_SEND_DATA_ON_EXIT_THRESHOLD_MS", "NEW_RELIC_AZURE_FUNCTION_MODE_ENABLED", + "NEW_RELIC_AGENT_CONTROL_ENABLED", + "NEW_RELIC_AGENT_CONTROL_HEALTH_DELIVERY_LOCATION", + "NEW_RELIC_AGENT_CONTROL_HEALTH_FREQUENCY" }; List environmentVariablesSensitive = new List { @@ -409,7 +417,7 @@ public ITracer GetTracerImpl(string tracerFactoryName, uint tracerArguments, str private void ProcessExit(object sender, EventArgs e) { Log.Debug("Received a ProcessExit CLR event for the application domain. About to shut down the .NET Agent..."); - + Shutdown(true); } @@ -437,13 +445,16 @@ private void Shutdown(bool cleanShutdown) Log.Debug("Shutting down public agent services..."); StopServices(); + _agentHealthReporter?.SetAgentControlStatus(HealthCodes.AgentShutdownHealthy); } catch (Exception e) { + _agentHealthReporter?.SetAgentControlStatus(HealthCodes.AgentShutdownError, e.Message); Log.Info(e, "Unexpected exception during agent shutdown"); } finally { + _agentHealthReporter?.PublishAgentControlHealthCheck(); Log.Debug("Shutting down internal agent services..."); Dispose(); diff --git a/src/Agent/NewRelic/Agent/Core/Config/BootstrapConfiguration.cs b/src/Agent/NewRelic/Agent/Core/Config/BootstrapConfiguration.cs index 677896d980..319d7f7cfd 100644 --- a/src/Agent/NewRelic/Agent/Core/Config/BootstrapConfiguration.cs +++ b/src/Agent/NewRelic/Agent/Core/Config/BootstrapConfiguration.cs @@ -25,6 +25,10 @@ public interface IBootstrapConfiguration string ServerlessFunctionVersion { get; } bool AzureFunctionModeDetected { get; } bool GCSamplerV2Enabled { get; } + + bool AgentControlEnabled { get; } + string HealthDeliveryLocation { get; } + int HealthFrequency { get; } } /// @@ -139,6 +143,17 @@ public string AgentEnabledAt public bool AzureFunctionModeDetected => ConfigLoaderHelpers.GetEnvironmentVar("FUNCTIONS_WORKER_RUNTIME") != null; public bool GCSamplerV2Enabled { get; private set;} + public bool AgentControlEnabled => ConfigLoaderHelpers.GetEnvironmentVar("NEW_RELIC_AGENT_CONTROL_ENABLED").TryToBoolean(out var enabled) && enabled; + public string HealthDeliveryLocation => ConfigLoaderHelpers.GetEnvironmentVar("NEW_RELIC_AGENT_CONTROL_HEALTH_DELIVERY_LOCATION"); + + public int HealthFrequency + { + get + { + var healthFrequencyString = ConfigLoaderHelpers.GetEnvironmentVar("NEW_RELIC_AGENT_CONTROL_HEALTH_FREQUENCY"); + return int.TryParse(healthFrequencyString, out var healthFrequency) ? healthFrequency : 5; + } + } private bool CheckServerlessModeEnabled(configuration localConfiguration) { diff --git a/src/Agent/NewRelic/Agent/Core/Configuration/ConfigurationService.cs b/src/Agent/NewRelic/Agent/Core/Configuration/ConfigurationService.cs index 9bf25bfd2d..8dfc961c8b 100644 --- a/src/Agent/NewRelic/Agent/Core/Configuration/ConfigurationService.cs +++ b/src/Agent/NewRelic/Agent/Core/Configuration/ConfigurationService.cs @@ -11,6 +11,7 @@ using NewRelic.Agent.Core.SharedInterfaces.Web; using System; using System.Linq; +using NewRelic.Agent.Core.AgentHealth; namespace NewRelic.Agent.Core.Configuration { @@ -27,6 +28,7 @@ public class ConfigurationService : IConfigurationService, IDisposable private readonly IHttpRuntimeStatic _httpRuntimeStatic; private readonly IConfigurationManagerStatic _configurationManagerStatic; private readonly IDnsStatic _dnsStatic; + private readonly IAgentHealthReporter _agentHealthReporter; /// /// Do not use this field outside of this class. It only exists for testing purposes. @@ -35,15 +37,16 @@ public class ConfigurationService : IConfigurationService, IDisposable public IConfiguration Configuration { get; private set; } - public ConfigurationService(IEnvironment environment, IProcessStatic processStatic, IHttpRuntimeStatic httpRuntimeStatic, IConfigurationManagerStatic configurationManagerStatic, IDnsStatic dnsStatic) + public ConfigurationService(IEnvironment environment, IProcessStatic processStatic, IHttpRuntimeStatic httpRuntimeStatic, IConfigurationManagerStatic configurationManagerStatic, IDnsStatic dnsStatic, IAgentHealthReporter agentHealthReporter) { _environment = environment; _processStatic = processStatic; _httpRuntimeStatic = httpRuntimeStatic; _configurationManagerStatic = configurationManagerStatic; _dnsStatic = dnsStatic; + _agentHealthReporter = agentHealthReporter; - Configuration = new InternalConfiguration(_environment, _localConfiguration, _serverConfiguration, _runTimeConfiguration, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, dnsStatic); + Configuration = new InternalConfiguration(_environment, _localConfiguration, _serverConfiguration, _runTimeConfiguration, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, dnsStatic, _agentHealthReporter); _subscriptions.Add(OnConfigurationDeserialized); _subscriptions.Add(OnServerConfigurationUpdated); @@ -122,7 +125,7 @@ private void UpdateAndPublishConfiguration(ConfigurationUpdateSource configurati { var previousLogLevel = Configuration.LoggingLevel; - Configuration = new InternalConfiguration(_environment, _localConfiguration, _serverConfiguration, _runTimeConfiguration, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + Configuration = new InternalConfiguration(_environment, _localConfiguration, _serverConfiguration, _runTimeConfiguration, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); UpdateLogLevel(previousLogLevel); diff --git a/src/Agent/NewRelic/Agent/Core/Configuration/DefaultConfiguration.cs b/src/Agent/NewRelic/Agent/Core/Configuration/DefaultConfiguration.cs index df2518d29b..48d81ac78f 100644 --- a/src/Agent/NewRelic/Agent/Core/Configuration/DefaultConfiguration.cs +++ b/src/Agent/NewRelic/Agent/Core/Configuration/DefaultConfiguration.cs @@ -18,6 +18,7 @@ using System.Linq; using System.Text.RegularExpressions; using System.Threading; +using NewRelic.Agent.Core.AgentHealth; namespace NewRelic.Agent.Core.Configuration { @@ -47,6 +48,7 @@ public class DefaultConfiguration : IConfiguration private readonly IHttpRuntimeStatic _httpRuntimeStatic = new HttpRuntimeStatic(); private readonly IConfigurationManagerStatic _configurationManagerStatic = new ConfigurationManagerStaticMock(); private readonly IDnsStatic _dnsStatic; + private readonly IAgentHealthReporter _agentHealthReporter; /// /// Default configuration. It will contain reasonable default values for everything and never anything more. Useful when you don't have configuration off disk or a collector response yet. @@ -72,7 +74,7 @@ private DefaultConfiguration() ConfigurationVersion = Interlocked.Increment(ref _currentConfigurationVersion); } - protected DefaultConfiguration(IEnvironment environment, configuration localConfiguration, ServerConfiguration serverConfiguration, RunTimeConfiguration runTimeConfiguration, SecurityPoliciesConfiguration securityPoliciesConfiguration, IBootstrapConfiguration bootstrapConfiguration, IProcessStatic processStatic, IHttpRuntimeStatic httpRuntimeStatic, IConfigurationManagerStatic configurationManagerStatic, IDnsStatic dnsStatic) + protected DefaultConfiguration(IEnvironment environment, configuration localConfiguration, ServerConfiguration serverConfiguration, RunTimeConfiguration runTimeConfiguration, SecurityPoliciesConfiguration securityPoliciesConfiguration, IBootstrapConfiguration bootstrapConfiguration, IProcessStatic processStatic, IHttpRuntimeStatic httpRuntimeStatic, IConfigurationManagerStatic configurationManagerStatic, IDnsStatic dnsStatic, IAgentHealthReporter agentHealthReporter) : this() { _environment = environment; @@ -84,7 +86,7 @@ protected DefaultConfiguration(IEnvironment environment, configuration localConf _utilizationFullHostName = new Lazy(_dnsStatic.GetFullHostName); _utilizationHostName = new Lazy(_dnsStatic.GetHostName); - + _agentHealthReporter = agentHealthReporter; if (localConfiguration != null) { @@ -214,8 +216,12 @@ public virtual string AgentLicenseKey _agentLicenseKey = _configurationManagerStatic.GetAppSetting(Constants.AppSettingsLicenseKey) ?? EnvironmentOverrides(_localConfiguration.service.licenseKey, "NEW_RELIC_LICENSE_KEY", "NEWRELIC_LICENSEKEY"); - if (_agentLicenseKey != null) - _agentLicenseKey = _agentLicenseKey.Trim(); + _agentLicenseKey = _agentLicenseKey?.Trim(); + + if (string.IsNullOrEmpty(_agentLicenseKey) && !ServerlessModeEnabled) + { + TrySetAgentControlStatus(HealthCodes.LicenseKeyMissing); + } return _agentLicenseKey; } @@ -316,6 +322,7 @@ private IEnumerable GetApplicationNames() return new List { _processStatic.GetCurrentProcess().ProcessName }; } + TrySetAgentControlStatus(HealthCodes.ApplicationNameMissing); throw new Exception("An application name must be provided"); } @@ -2495,7 +2502,17 @@ public TimeSpan StackExchangeRedisCleanupCycle public bool GCSamplerV2Enabled => _bootstrapConfiguration.GCSamplerV2Enabled; - #endregion + #region Agent Control + + public bool AgentControlEnabled => _bootstrapConfiguration.AgentControlEnabled; + + public string HealthDeliveryLocation => _bootstrapConfiguration.HealthDeliveryLocation; + + public int HealthFrequency => _bootstrapConfiguration.HealthFrequency; + + #endregion Agent Control + + #endregion Properties #region Helpers @@ -2859,6 +2876,18 @@ private static string TryGetValidPrefix(string prefix) return specified ? value : default(int?); } + // Since the configuration is initialized before the AgentHealthReporter, needed a way to not call it till it was ready + private void TrySetAgentControlStatus((bool IsHealthy, string Code, string Status) healthStatus) + { + if (_agentHealthReporter == null) + { + Log.Debug("DefaultConfiguration: Unable to set Agent Control status {status} because agent health reporter has not been initialized.", healthStatus); + return; + } + + _agentHealthReporter.SetAgentControlStatus(healthStatus); + } + #endregion #region deprecated/disabled parameter group settings diff --git a/src/Agent/NewRelic/Agent/Core/Configuration/InternalConfiguration.cs b/src/Agent/NewRelic/Agent/Core/Configuration/InternalConfiguration.cs index cbe0332ac6..f61859fdf3 100644 --- a/src/Agent/NewRelic/Agent/Core/Configuration/InternalConfiguration.cs +++ b/src/Agent/NewRelic/Agent/Core/Configuration/InternalConfiguration.cs @@ -1,6 +1,7 @@ // Copyright 2020 New Relic, Inc. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +using NewRelic.Agent.Core.AgentHealth; using NewRelic.Agent.Core.Config; using NewRelic.Agent.Core.SharedInterfaces; using NewRelic.Agent.Core.SharedInterfaces.Web; @@ -12,8 +13,8 @@ namespace NewRelic.Agent.Core.Configuration /// internal class InternalConfiguration : DefaultConfiguration { - public InternalConfiguration(IEnvironment environment, configuration localConfiguration, ServerConfiguration serverConfiguration, RunTimeConfiguration runTimeConfiguration, SecurityPoliciesConfiguration securityPoliciesConfiguration, IBootstrapConfiguration bootstrapConfiguration, IProcessStatic processStatic, IHttpRuntimeStatic httpRuntimeStatic, IConfigurationManagerStatic configurationManagerStatic, IDnsStatic dnsStatic) : - base(environment, localConfiguration, serverConfiguration, runTimeConfiguration, securityPoliciesConfiguration, bootstrapConfiguration, processStatic, httpRuntimeStatic, configurationManagerStatic, dnsStatic) + public InternalConfiguration(IEnvironment environment, configuration localConfiguration, ServerConfiguration serverConfiguration, RunTimeConfiguration runTimeConfiguration, SecurityPoliciesConfiguration securityPoliciesConfiguration, IBootstrapConfiguration bootstrapConfiguration, IProcessStatic processStatic, IHttpRuntimeStatic httpRuntimeStatic, IConfigurationManagerStatic configurationManagerStatic, IDnsStatic dnsStatic, IAgentHealthReporter agentHealthReporter) : + base(environment, localConfiguration, serverConfiguration, runTimeConfiguration, securityPoliciesConfiguration, bootstrapConfiguration, processStatic, httpRuntimeStatic, configurationManagerStatic, dnsStatic, agentHealthReporter) { } } } diff --git a/src/Agent/NewRelic/Agent/Core/Configuration/ReportedConfiguration.cs b/src/Agent/NewRelic/Agent/Core/Configuration/ReportedConfiguration.cs index 8aae7c13f7..78ed566344 100644 --- a/src/Agent/NewRelic/Agent/Core/Configuration/ReportedConfiguration.cs +++ b/src/Agent/NewRelic/Agent/Core/Configuration/ReportedConfiguration.cs @@ -728,6 +728,15 @@ public IReadOnlyDictionary GetAppSettings() return _configuration.GetAppSettings(); } + [JsonProperty("agent_control.enabled")] + public bool AgentControlEnabled => _configuration.AgentControlEnabled; + + [JsonProperty("agent_control.health.delivery_location")] + public string HealthDeliveryLocation => _configuration.HealthDeliveryLocation; + + [JsonProperty("agent_control.health.frequency")] + public int HealthFrequency => _configuration.HealthFrequency; + #endregion } } diff --git a/src/Agent/NewRelic/Agent/Core/DataTransport/ConnectionHandler.cs b/src/Agent/NewRelic/Agent/Core/DataTransport/ConnectionHandler.cs index e92f15b655..8ad7564bef 100644 --- a/src/Agent/NewRelic/Agent/Core/DataTransport/ConnectionHandler.cs +++ b/src/Agent/NewRelic/Agent/Core/DataTransport/ConnectionHandler.cs @@ -51,7 +51,7 @@ public class ConnectionHandler : ConfigurationBasedService, IConnectionHandler private readonly IAgentHealthReporter _agentHealthReporter; private readonly IEnvironment _environmentVariableHelper; - public ConnectionHandler(ISerializer serializer, ICollectorWireFactory collectorWireFactory, IProcessStatic processStatic, IDnsStatic dnsStatic, ILabelsService labelsService, Environment environment, ISystemInfo systemInfo, IAgentHealthReporter agentHealthReporter, IEnvironment environmentVariableHelper) + public ConnectionHandler(ISerializer serializer, ICollectorWireFactory collectorWireFactory, IProcessStatic processStatic, IDnsStatic dnsStatic, ILabelsService labelsService, Environment environment, ISystemInfo systemInfo, IAgentHealthReporter agentHealthReporter, IEnvironment environmentVariableHelper, ICollectorWire dataRequestWire = null) { _serializer = serializer; _collectorWireFactory = collectorWireFactory; @@ -64,7 +64,7 @@ public ConnectionHandler(ISerializer serializer, ICollectorWireFactory collector _environmentVariableHelper = environmentVariableHelper; _connectionInfo = new ConnectionInfo(_configuration); - _dataRequestWire = new NoOpCollectorWire(); + _dataRequestWire = dataRequestWire ?? new NoOpCollectorWire(); } #region Public API @@ -108,6 +108,7 @@ public void Connect() EventBus.Publish(new AgentConnectedEvent()); Log.Info("Agent fully connected."); + _agentHealthReporter.SetAgentControlStatus(HealthCodes.Healthy); } catch (Exception e) @@ -444,19 +445,54 @@ private T SendDataOverWire(ICollectorWire wire, string method, params object[ Log.Debug("Request({0}): Received a {1} {2} response invoking method \"{3}\" with payload \"{4}\"", requestGuid, (int)ex.StatusCode, ex.StatusCode, method, serializedData); if (ex.StatusCode == HttpStatusCode.Gone) - { - Log.Info("Request({0}): The server has requested that the agent disconnect. The agent is shutting down.", requestGuid); - } + Log.Info(ex, "Request({0}): The server has requested that the agent disconnect. The agent is shutting down.", requestGuid); + + SetAgentControlStatus(requestGuid, method, ex); throw; } catch (Exception ex) { Log.Debug("Request({0}): An error occurred invoking method \"{1}\" with payload \"{2}\": {3}", requestGuid, method, serializedData, ex); // log message only since exception is rethrown + + SetAgentControlStatus(requestGuid, method, null); + throw; } } + private void SetAgentControlStatus(Guid requestGuid, string method, HttpException httpException) + { + if (method.Equals("connect")) + { + _agentHealthReporter.SetAgentControlStatus(HealthCodes.FailedToConnect); + } + else + { + if (httpException == null) // this shouldn't happen, but... + { + _agentHealthReporter.SetAgentControlStatus(HealthCodes.HttpError, "unknown", method); + return; + } + + switch (httpException.StatusCode) + { + case HttpStatusCode.Unauthorized: + _agentHealthReporter.SetAgentControlStatus(HealthCodes.LicenseKeyInvalid); + break; + case HttpStatusCode.Gone: + _agentHealthReporter.SetAgentControlStatus(HealthCodes.ForceDisconnect); + break; + case HttpStatusCode.ProxyAuthenticationRequired: + _agentHealthReporter.SetAgentControlStatus(HealthCodes.HttpProxyError, httpException.StatusCode.ToString()); + break; + default: + _agentHealthReporter.SetAgentControlStatus(HealthCodes.HttpError, httpException.StatusCode.ToString(), method); + break; + } + } + } + private T ParseResponse(string responseBody) { var responseEnvelope = _serializer.Deserialize>(responseBody); diff --git a/src/Agent/NewRelic/Agent/Core/DataTransport/ConnectionManager.cs b/src/Agent/NewRelic/Agent/Core/DataTransport/ConnectionManager.cs index 11f0b1e2e8..8d4ed8b72c 100644 --- a/src/Agent/NewRelic/Agent/Core/DataTransport/ConnectionManager.cs +++ b/src/Agent/NewRelic/Agent/Core/DataTransport/ConnectionManager.cs @@ -45,7 +45,6 @@ public ConnectionManager(IConnectionHandler connectionHandler, IScheduler schedu { _connectionHandler = connectionHandler; _scheduler = scheduler; - _subscriptions.Add(OnStartAgent); _subscriptions.Add(OnRestartAgent); diff --git a/src/Agent/NewRelic/Agent/Core/DependencyInjection/AgentServices.cs b/src/Agent/NewRelic/Agent/Core/DependencyInjection/AgentServices.cs index 0b424e3434..5647839fc4 100644 --- a/src/Agent/NewRelic/Agent/Core/DependencyInjection/AgentServices.cs +++ b/src/Agent/NewRelic/Agent/Core/DependencyInjection/AgentServices.cs @@ -129,10 +129,12 @@ public static void RegisterServices(IContainer container, bool serverlessModeEna else { container.Register(); - container.Register(); container.Register(); } + container.Register(); + container.Register(); + container.Register(); container.Register(); container.Register(); @@ -243,7 +245,6 @@ public static void StartServices(IContainer container, bool serverlessModeEnable container.Resolve(); container.Resolve(); - container.Resolve(); #if NETFRAMEWORK // Start GCSampler on separate thread due to delay in collecting Instance Names, // which can stall application startup and cause the app start to timeout diff --git a/src/Agent/NewRelic/Agent/Core/Metrics/MetricNames.cs b/src/Agent/NewRelic/Agent/Core/Metrics/MetricNames.cs index adb0c82471..38ffbdff98 100644 --- a/src/Agent/NewRelic/Agent/Core/Metrics/MetricNames.cs +++ b/src/Agent/NewRelic/Agent/Core/Metrics/MetricNames.cs @@ -708,7 +708,7 @@ public static string GetSupportabilityAgentApi(string methodName) }; - ///DistributedTracing + //DistributedTracing private const string SupportabilityDistributedTracePs = SupportabilityPs + "DistributedTrace" + PathSeparator; @@ -761,7 +761,7 @@ public static string GetSupportabilityAgentApi(string methodName) public const string SupportabilityDistributedTraceCreatePayloadException = SupportabilityDistributedTraceCreatePayloadPs + "Exception"; - ///Trace Context + //Trace Context private const string SupportabilityTraceContextPs = SupportabilityPs + "TraceContext" + PathSeparator; @@ -1155,5 +1155,14 @@ public static string GetSupportabilityLogForwardingEnabledWithFrameworkName(stri } #endregion + + #region Agent Control + + private const string AgentControl = "AgentControl"; + private const string Health = "Health"; + private const string SupportabilityAgentControlPs = SupportabilityPs + AgentControl + PathSeparator; + public const string SupportabilityAgentControlHealthEnabled = SupportabilityAgentControlPs + Health + PathSeparator + Enabled; + + #endregion } } diff --git a/src/Agent/NewRelic/Agent/Core/Utilities/ConfigurationBasedService.cs b/src/Agent/NewRelic/Agent/Core/Utilities/ConfigurationBasedService.cs index 237f4c6b4f..a8a88b5e2a 100644 --- a/src/Agent/NewRelic/Agent/Core/Utilities/ConfigurationBasedService.cs +++ b/src/Agent/NewRelic/Agent/Core/Utilities/ConfigurationBasedService.cs @@ -50,5 +50,11 @@ public override void Dispose() _configurationUpdatedEventSubscription.Dispose(); base.Dispose(); } + + /// + /// For testing only! + /// + /// + public void OverrideConfigForTesting(IConfiguration config) => _configuration = config; } } diff --git a/src/Agent/NewRelic/Agent/Core/Utilities/DirectoryWrapper.cs b/src/Agent/NewRelic/Agent/Core/Utilities/DirectoryWrapper.cs new file mode 100644 index 0000000000..4f7fa89bcc --- /dev/null +++ b/src/Agent/NewRelic/Agent/Core/Utilities/DirectoryWrapper.cs @@ -0,0 +1,30 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System.IO; + +namespace NewRelic.Agent.Core.Utilities +{ + /// + /// Wraps some Directory methods to allow for unit testing + /// + public interface IDirectoryWrapper + { + bool Exists(string path); + string[] GetFiles(string readOnlyPath, string yml); + } + + [NrExcludeFromCodeCoverage] + public class DirectoryWrapper : IDirectoryWrapper + { + public bool Exists(string path) + { + return Directory.Exists(path); + } + + public string[] GetFiles(string path, string searchPattern) + { + return Directory.GetFiles(path, searchPattern); + } + } +} diff --git a/src/Agent/NewRelic/Agent/Core/Utilities/FileWrapper.cs b/src/Agent/NewRelic/Agent/Core/Utilities/FileWrapper.cs index 14ca72a8d9..6c054c11b5 100644 --- a/src/Agent/NewRelic/Agent/Core/Utilities/FileWrapper.cs +++ b/src/Agent/NewRelic/Agent/Core/Utilities/FileWrapper.cs @@ -12,8 +12,10 @@ public interface IFileWrapper { bool Exists(string path); FileStream OpenWrite(string path); + bool TryCreateFile(string path, bool deleteOnSuccess = true); } + [NrExcludeFromCodeCoverage] public class FileWrapper : IFileWrapper { public bool Exists(string path) @@ -27,5 +29,17 @@ public FileStream OpenWrite(string path) return File.OpenWrite(path); } + public bool TryCreateFile(string path, bool deleteOnSuccess = true) + { + try + { + using var fs = File.Create(path, 1, deleteOnSuccess ? FileOptions.DeleteOnClose : FileOptions.None); + return true; + } + catch + { + return false; + } + } } } diff --git a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Configuration/IConfiguration.cs b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Configuration/IConfiguration.cs index 95d45a6b32..17db8b10c4 100644 --- a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Configuration/IConfiguration.cs +++ b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Configuration/IConfiguration.cs @@ -236,5 +236,9 @@ public interface IConfiguration string AwsAccountId { get; } bool GCSamplerV2Enabled { get; } + + bool AgentControlEnabled { get; } + string HealthDeliveryLocation { get; } + int HealthFrequency { get; } } } diff --git a/tests/Agent/UnitTests/Core.UnitTest/AgentHealth/AgentHealthHeartbeatTests.cs b/tests/Agent/UnitTests/Core.UnitTest/AgentHealth/AgentHealthHeartbeatTests.cs index 97f866bfd6..8a06cc75d2 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/AgentHealth/AgentHealthHeartbeatTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/AgentHealth/AgentHealthHeartbeatTests.cs @@ -1,9 +1,10 @@ -// Copyright 2020 New Relic, Inc. All rights reserved. +// Copyright 2020 New Relic, Inc. All rights reserved. // SPDX-License-Identifier: Apache-2.0 using System; using NewRelic.Agent.Core.Metrics; using NewRelic.Agent.Core.Time; +using NewRelic.Agent.Core.Utilities; using NewRelic.Agent.Core.WireModels; using NewRelic.Testing.Assertions; using NUnit.Framework; @@ -49,7 +50,7 @@ private static IMetricBuilder GetSimpleMetricBuilder() public void HeartbeatTest() { var scheduler = new FakeScheduler(); - var reporter = new AgentHealthReporter(GetSimpleMetricBuilder(), scheduler); + var reporter = new AgentHealthReporter(GetSimpleMetricBuilder(), scheduler, Mock.Create(), Mock.Create()); using (var logger = new TestUtilities.Logging()) { // Make sure all the event types get seen in the log diff --git a/tests/Agent/UnitTests/Core.UnitTest/AgentHealth/AgentHealthReporterAgentControlTests.cs b/tests/Agent/UnitTests/Core.UnitTest/AgentHealth/AgentHealthReporterAgentControlTests.cs new file mode 100644 index 0000000000..880b0e1159 --- /dev/null +++ b/tests/Agent/UnitTests/Core.UnitTest/AgentHealth/AgentHealthReporterAgentControlTests.cs @@ -0,0 +1,328 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.IO; +using System.Linq; +using System.Text; +using NewRelic.Agent.Configuration; +using NewRelic.Agent.Core.Fixtures; +using NewRelic.Agent.Core.Time; +using NewRelic.Agent.Core.Utilities; +using NewRelic.Agent.Core.WireModels; +using NUnit.Framework; +using Telerik.JustMock; + +namespace NewRelic.Agent.Core.AgentHealth +{ + public class AgentHealthReporterAgentControlTests + { + private AgentHealthReporter _agentHealthReporter; + private ConfigurationAutoResponder _configurationAutoResponder; + private List _publishedMetrics; + private IScheduler _scheduler; + + private void Setup(bool agentControlEnabled, string deliveryLocation, int frequency, IFileWrapper fileWrapper = null, IDirectoryWrapper directoryWrapper = null) + { + var configuration = Mock.Create(); + Mock.Arrange(() => configuration.AgentControlEnabled).Returns(agentControlEnabled); + Mock.Arrange(() => configuration.HealthDeliveryLocation).Returns(deliveryLocation); + Mock.Arrange(() => configuration.HealthFrequency).Returns(frequency); + _configurationAutoResponder = new ConfigurationAutoResponder(configuration); + + var metricBuilder = WireModels.Utilities.GetSimpleMetricBuilder(); + _publishedMetrics = new List(); + _scheduler = Mock.Create(); + + if (fileWrapper == null) + { + fileWrapper = Mock.Create(); + Mock.Arrange(() => fileWrapper.Exists(Arg.IsAny())).Returns(true); + Mock.Arrange(() => fileWrapper.TryCreateFile(Arg.IsAny(), Arg.IsAny())).Returns(true); + } + + if (directoryWrapper == null) + { + directoryWrapper = Mock.Create(); + Mock.Arrange(() => directoryWrapper.Exists(Arg.IsAny())).Returns(true); + } + + _agentHealthReporter = new AgentHealthReporter(metricBuilder, _scheduler, fileWrapper, directoryWrapper); + _agentHealthReporter.RegisterPublishMetricHandler(metric => _publishedMetrics.Add(metric)); + } + + [TearDown] + public void TearDown() + { + _agentHealthReporter?.Dispose(); + _configurationAutoResponder?.Dispose(); + } + + [Test] + public void AgentControlMetricPresent_WhenAgentControlEnabled() + { + Setup(true, "file://foo", 5); + + _agentHealthReporter.CollectMetrics(); + Assert.That(_publishedMetrics.Any(x => x.MetricNameModel.Name == "Supportability/AgentControl/Health/enabled"), Is.True); + } + + [Test] + public void AgentControl_HealthChecksSucceeded() + { + // Arrange + // path must be absolute, not relative + var executingPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)!; + var readOnlyPath = Path.Combine(executingPath, Path.GetRandomFileName(), "readonly"); + + var actualMS = new MemoryStream(); + FileStream fs = Mock.Create(Constructor.Mocked); + Mock.Arrange(() => fs.Write(null, 0, 0)).IgnoreArguments() + .DoInstead((byte[] content, int offset, int len) => actualMS.Write(content, 0, content.Length)); + Mock.Arrange(() => fs.CanWrite).Returns(true); + + var fileWrapper = Mock.Create(); + Mock.Arrange(() => fileWrapper.TryCreateFile(Arg.IsAny(), Arg.IsAny())).Returns(true); + Mock.Arrange(() => fileWrapper.OpenWrite(Arg.IsAny())).Returns(fs); + + var directoryWrapper = Mock.Create(); + Mock.Arrange(() => directoryWrapper.Exists(Arg.IsAny())).Returns(true); + + Setup(true, $"file://{readOnlyPath}", 12, fileWrapper, directoryWrapper); + + // Act + _agentHealthReporter.PublishAgentControlHealthCheck(); + + // Assert + // verify the health check hasn't failed + Assert.That(_agentHealthReporter.HealthCheckFailed, Is.False); + + actualMS.Position = 0; + var actualBytes = actualMS.ToArray(); + // convert actualBytes to a string + var payload = Encoding.UTF8.GetString(actualBytes); + + var parsedObject = new SimpleYamlParser().ParseYaml(payload); + Assert.That(parsedObject.healthy, Is.EqualTo("True")); + Assert.That(parsedObject.status, Is.EqualTo("Agent starting")); + Assert.That(parsedObject.last_error, Is.Empty); + Assert.That(parsedObject.start_time_unix_nano, Is.Not.Empty); + Assert.That(parsedObject.status_time_unix_nano, Is.Not.Empty); + } + + [Test] + public void AgentControl_PublishAgentControlHealthCheckScheduledTaskIsStopped_WhenAgentControlDisabled() + { + // Arrange + Setup(false, "file://foo", 5); + + // Act + _agentHealthReporter.PublishAgentControlHealthCheck(); + + //Assert + Mock.Assert(() => _scheduler.StopExecuting(_agentHealthReporter.PublishAgentControlHealthCheck, Arg.IsAny()), Occurs.Once()); + } + + [Test] + public void AgentControl_HealthChecksFailed_WhenDeliveryLocationDoesNotExist() + { + // Arrange + // path must be absolute, not relative + var executingPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)!; + var readOnlyPath = Path.Combine(executingPath, Path.GetRandomFileName(), "readonly"); + + var fileWrapper = Mock.Create(); + + var directoryWrapper = Mock.Create(); + Mock.Arrange(() => directoryWrapper.Exists(Arg.IsAny())).Returns(false); + + Setup(true, $"file://{readOnlyPath}", 12, fileWrapper, directoryWrapper); + + // Act + _agentHealthReporter.PublishAgentControlHealthCheck(); + + // Assert + Assert.That(_agentHealthReporter.HealthCheckFailed, Is.True); + } + + [Test] + public void AgentControl_HealthChecksFailed_WhenDeliveryLocationIsNotAFileURI() + { + // Arrange + Setup(true, "http://foo", 12); + + // Act + _agentHealthReporter.PublishAgentControlHealthCheck(); + + // Assert + Assert.That(_agentHealthReporter.HealthCheckFailed, Is.True); + } + + [Test] + public void AgentControl_HealthChecksFailed_WhenDeliveryLocationIsReadOnly() + { + // Arrange + // path must be absolute, not relative + var executingPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)!; + var readOnlyPath = Path.Combine(executingPath, Path.GetRandomFileName(), "readonly"); + + var fileWrapper = Mock.Create(); + Mock.Arrange(() => fileWrapper.TryCreateFile(Arg.IsAny(), Arg.IsAny())).Returns(false); + + var directoryWrapper = Mock.Create(); + Mock.Arrange(() => directoryWrapper.Exists(Arg.IsAny())).Returns(true); + + Setup(true, $"file://{readOnlyPath}", 12, fileWrapper, directoryWrapper); + + // Act + _agentHealthReporter.PublishAgentControlHealthCheck(); + + // Assert + // verify the health check failed + Assert.That(_agentHealthReporter.HealthCheckFailed, Is.True); + } + + [Test] + public void AgentControl_HealthChecksFailed_WhenHealthFileCannotBeWritten() + { + // Arrange + // path must be absolute, not relative + var executingPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)!; + var readOnlyPath = Path.Combine(executingPath, Path.GetRandomFileName(), "readonly"); + + var fileWrapper = Mock.Create(); + Mock.Arrange(() => fileWrapper.TryCreateFile(Arg.IsAny(), Arg.IsAny())).Returns(true); + Mock.Arrange(() => fileWrapper.OpenWrite(Arg.IsAny())).Throws(new IOException()); + + var directoryWrapper = Mock.Create(); + Mock.Arrange(() => directoryWrapper.Exists(Arg.IsAny())).Returns(true); + + Setup(true, $"file://{readOnlyPath}", 12, fileWrapper, directoryWrapper); + + // Act + _agentHealthReporter.PublishAgentControlHealthCheck(); + + // Assert + // verify the health check failed + Assert.That(_agentHealthReporter.HealthCheckFailed, Is.True); + } + + [Test] + public void AgentControl_SetAgentControlStatus_SetsShutdownHealthy_WhenHealthCheckIsHealthy() + { + // Arrange + // path must be absolute, not relative + var executingPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)!; + var readOnlyPath = Path.Combine(executingPath, Path.GetRandomFileName(), "readonly"); + + var actualMS = new MemoryStream(); + FileStream fs = Mock.Create(Constructor.Mocked); + Mock.Arrange(() => fs.Write(null, 0, 0)).IgnoreArguments() + .DoInstead((byte[] content, int offset, int len) => actualMS.Write(content, 0, content.Length)); + Mock.Arrange(() => fs.CanWrite).Returns(true); + + var fileWrapper = Mock.Create(); + Mock.Arrange(() => fileWrapper.TryCreateFile(Arg.IsAny(), Arg.IsAny())).Returns(true); + Mock.Arrange(() => fileWrapper.OpenWrite(Arg.IsAny())).Returns(fs); + + var directoryWrapper = Mock.Create(); + Mock.Arrange(() => directoryWrapper.Exists(Arg.IsAny())).Returns(true); + + Setup(true, $"file://{readOnlyPath}", 12, fileWrapper, directoryWrapper); + + // Act + _agentHealthReporter.SetAgentControlStatus(HealthCodes.AgentShutdownHealthy); + _agentHealthReporter.PublishAgentControlHealthCheck(); + + // Assert + // verify the health check hasn't failed + Assert.That(_agentHealthReporter.HealthCheckFailed, Is.False); + + actualMS.Position = 0; + var actualBytes = actualMS.ToArray(); + // convert actualBytes to a string + var payload = Encoding.UTF8.GetString(actualBytes); + + var parsedObject = new SimpleYamlParser().ParseYaml(payload); + Assert.That(parsedObject.healthy, Is.EqualTo("True")); + Assert.That(parsedObject.status, Is.EqualTo("Agent has shutdown")); + Assert.That(parsedObject.last_error, Is.EqualTo("NR-APM-099")); + + } + + [Test] + public void AgentControl_SetAgentControlStatus_DoesNotOverwriteLastStatusOnHealthyShutdown_WhenAgentIsNotHealthy() + { + // Arrange + // path must be absolute, not relative + var executingPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)!; + var readOnlyPath = Path.Combine(executingPath, Path.GetRandomFileName(), "readonly"); + + var actualMS = new MemoryStream(); + FileStream fs = Mock.Create(Constructor.Mocked); + Mock.Arrange(() => fs.Write(null, 0, 0)).IgnoreArguments() + .DoInstead((byte[] content, int offset, int len) => actualMS.Write(content, 0, content.Length)); + Mock.Arrange(() => fs.CanWrite).Returns(true); + + var fileWrapper = Mock.Create(); + Mock.Arrange(() => fileWrapper.TryCreateFile(Arg.IsAny(), Arg.IsAny())).Returns(true); + Mock.Arrange(() => fileWrapper.OpenWrite(Arg.IsAny())).Returns(fs); + + var directoryWrapper = Mock.Create(); + Mock.Arrange(() => directoryWrapper.Exists(Arg.IsAny())).Returns(true); + + Setup(true, $"file://{readOnlyPath}", 12, fileWrapper, directoryWrapper); + + // Act + _agentHealthReporter.SetAgentControlStatus(HealthCodes.FailedToConnect); + _agentHealthReporter.PublishAgentControlHealthCheck(); + _agentHealthReporter.SetAgentControlStatus(HealthCodes.AgentShutdownHealthy); + _agentHealthReporter.PublishAgentControlHealthCheck(); + + // Assert + // verify the health check hasn't failed + Assert.That(_agentHealthReporter.HealthCheckFailed, Is.False); + + actualMS.Position = 0; + var actualBytes = actualMS.ToArray(); + // convert actualBytes to a string + var payload = Encoding.UTF8.GetString(actualBytes); + + var parsedObject = new SimpleYamlParser().ParseYaml(payload); + Assert.That(parsedObject.healthy, Is.EqualTo("False")); + Assert.That(parsedObject.status, Is.EqualTo("Failed to connect to New Relic data collector")); + Assert.That(parsedObject.last_error, Is.EqualTo("NR-APM-009")); + + } + + } + + public class SimpleYamlParser + { + public dynamic ParseYaml(string yamlContent) + { + var result = new ExpandoObject() as IDictionary; + using (var reader = new StringReader(yamlContent)) + { + string line; + while ((line = reader.ReadLine()) != null) + { + if (string.IsNullOrWhiteSpace(line)) + continue; + + var parts = line.Split(new[] { ':' }, 2); + if (parts.Length == 2) + { + var key = parts[0].Trim(); + var value = parts[1].Trim(); + result[key] = value; + } + } + } + + return result; + } + } +} diff --git a/tests/Agent/UnitTests/Core.UnitTest/AgentHealth/AgentHealthReporterTests.cs b/tests/Agent/UnitTests/Core.UnitTest/AgentHealth/AgentHealthReporterTests.cs index 1da15a9534..34c6c691a8 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/AgentHealth/AgentHealthReporterTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/AgentHealth/AgentHealthReporterTests.cs @@ -3,20 +3,18 @@ using Grpc.Core; using NewRelic.Agent.Configuration; -using NewRelic.Agent.Core.Events; using NewRelic.Agent.Core.Fixtures; using NewRelic.Agent.Core.Time; -using NewRelic.Agent.Core.Utilities; using NewRelic.Agent.Core.WireModels; using NewRelic.Agent.Extensions.Providers.Wrapper; using NewRelic.Agent.Extensions.Logging; -using NewRelic.Agent.Core.SharedInterfaces; using NewRelic.Testing.Assertions; using NUnit.Framework; using System; using System.Collections.Generic; using System.Linq; using System.Net; +using NewRelic.Agent.Core.Utilities; using Telerik.JustMock; namespace NewRelic.Agent.Core.AgentHealth @@ -39,7 +37,7 @@ public void SetUp() _configurationAutoResponder = new ConfigurationAutoResponder(configuration); var metricBuilder = WireModels.Utilities.GetSimpleMetricBuilder(); - _agentHealthReporter = new AgentHealthReporter(metricBuilder, Mock.Create()); + _agentHealthReporter = new AgentHealthReporter(metricBuilder, Mock.Create(), Mock.Create(), Mock.Create()); _publishedMetrics = new List(); _agentHealthReporter.RegisterPublishMetricHandler(metric => _publishedMetrics.Add(metric)); } @@ -64,6 +62,10 @@ private IConfiguration GetDefaultConfiguration() Mock.Arrange(() => configuration.AwsAccountId).Returns("123456789012"); Mock.Arrange(() => configuration.LabelsEnabled).Returns(true); + Mock.Arrange(() => configuration.AgentControlEnabled).Returns(true); + Mock.Arrange(() => configuration.HealthDeliveryLocation).Returns("file://foo"); + Mock.Arrange(() => configuration.HealthFrequency).Returns(12); + return configuration; } @@ -508,7 +510,7 @@ public void LoggingDisabledSupportabilityMetricsMissing() } [Test] - public void IgnoredInstrumentationSupportabiltyMetricPresent() + public void IgnoredInstrumentationSupportabilityMetricPresent() { var expectedMetricName = new MetricNameWireModel("Supportability/Dotnet/IgnoredInstrumentation", null); var expectedMetricData = MetricDataWireModel.BuildGaugeValue(1); @@ -522,7 +524,7 @@ public void IgnoredInstrumentationSupportabiltyMetricPresent() } [Test] - public void IgnoredInstrumentationSupportabiltyMetricMissing() + public void IgnoredInstrumentationSupportabilityMetricMissing() { _agentHealthReporter.CollectMetrics(); @@ -530,14 +532,14 @@ public void IgnoredInstrumentationSupportabiltyMetricMissing() } [Test] - public void GCSamplerV2EnabledSupportabiliityMetricPresent() + public void GCSamplerV2EnabledSupportabilityMetricPresent() { _agentHealthReporter.CollectMetrics(); Assert.That(_publishedMetrics.Any(x => x.MetricNameModel.Name == "Supportability/Dotnet/GCSamplerV2/Enabled"), Is.True); } [Test] - public void AwsAccountIdSupportabiliityMetricPresent() + public void AwsAccountIdSupportabilityMetricPresent() { _agentHealthReporter.CollectMetrics(); Assert.That(_publishedMetrics.Any(x => x.MetricNameModel.Name == "Supportability/Dotnet/AwsAccountId/Config"), Is.True); diff --git a/tests/Agent/UnitTests/Core.UnitTest/AgentHealth/HealthCheckTests.cs b/tests/Agent/UnitTests/Core.UnitTest/AgentHealth/HealthCheckTests.cs new file mode 100644 index 0000000000..ef0cd40096 --- /dev/null +++ b/tests/Agent/UnitTests/Core.UnitTest/AgentHealth/HealthCheckTests.cs @@ -0,0 +1,355 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using NUnit.Framework; +using NewRelic.Agent.Core.AgentHealth; +using NUnit.Framework.Legacy; + +namespace NewRelic.Agent.Core.UnitTests.AgentHealth +{ + [TestFixture] + public class HealthCheckTests + { + private HealthCheck _healthCheck; + + [SetUp] + public void SetUp() + { + _healthCheck = new HealthCheck(); + } + + [Test] + public void HealthCheck_InitialValues_AreCorrect() + { + Assert.Multiple(() => + { + Assert.That(_healthCheck.IsHealthy, Is.False); + Assert.That(_healthCheck.Status, Is.Null); + Assert.That(_healthCheck.LastError, Is.Null); + Assert.That(_healthCheck.StartTime.Date, Is.EqualTo(DateTime.UtcNow.Date)); + Assert.That(_healthCheck.StatusTime, Is.EqualTo(default(DateTime))); + Assert.That(_healthCheck.FileName, Does.StartWith("health-")); + Assert.That(_healthCheck.FileName, Does.EndWith(".yml")); + }); + } + + [Test] + public void TrySetHealth_UpdatesValuesCorrectly() + { + var healthStatus = (IsHealthy: true, Code: "200", Status: "OK"); + + _healthCheck.TrySetHealth(healthStatus); + + Assert.Multiple(() => + { + Assert.That(_healthCheck.IsHealthy, Is.True); + Assert.That(_healthCheck.Status, Is.EqualTo("OK")); + Assert.That(_healthCheck.LastError, Is.EqualTo("200")); + }); + } + + [Test] + public void TrySetHealth_DoesNotUpdateUnchangedValues() + { + var initialStatus = (IsHealthy: false, Code: "500", Status: "Error"); + _healthCheck.TrySetHealth(initialStatus); + + var newStatus = (IsHealthy: false, Code: "500", Status: "Error"); + _healthCheck.TrySetHealth(newStatus); + + Assert.Multiple(() => + { + Assert.That(_healthCheck.IsHealthy, Is.False); + Assert.That(_healthCheck.Status, Is.EqualTo("Error")); + Assert.That(_healthCheck.LastError, Is.EqualTo("500")); + }); + } + + [Test] + public void ToYaml_ReturnsCorrectYamlString() + { + var healthStatus = (IsHealthy: true, Code: "200", Status: "OK"); + _healthCheck.TrySetHealth(healthStatus); + + var yaml = _healthCheck.ToYaml(); + + Assert.Multiple(() => + { + Assert.That(yaml, Does.Contain("healthy: True")); + Assert.That(yaml, Does.Contain("status: OK")); + Assert.That(yaml, Does.Contain("last_error: 200")); + Assert.That(yaml, Does.Contain("start_time_unix_nano:")); + Assert.That(yaml, Does.Contain("status_time_unix_nano:")); + }); + } + + [Test] + public void TrySetHealth_UpdatesStatusTime() + { + var healthStatus = (IsHealthy: true, Code: "200", Status: "OK"); + + _healthCheck.TrySetHealth(healthStatus); + + Assert.That(_healthCheck.StatusTime, Is.Not.EqualTo(default(DateTime))); + } + + [Test] + public void TrySetHealth_HandlesNullStatus() + { + (bool IsHealthy, string Code, string Status) healthStatus = (IsHealthy: true, Code: "200", Status: null); + + _healthCheck.TrySetHealth(healthStatus); + + Assert.Multiple(() => + { + Assert.That(_healthCheck.IsHealthy, Is.True); + Assert.That(_healthCheck.Status, Is.Null); + Assert.That(_healthCheck.LastError, Is.EqualTo("200")); + }); + } + + [Test] + public void TrySetHealth_HandlesEmptyStatus() + { + var healthStatus = (IsHealthy: true, Code: "200", Status: string.Empty); + + _healthCheck.TrySetHealth(healthStatus); + + Assert.Multiple(() => + { + Assert.That(_healthCheck.IsHealthy, Is.True); + Assert.That(_healthCheck.Status, Is.Empty); + Assert.That(_healthCheck.LastError, Is.EqualTo("200")); + }); + } + + [Test] + public void TrySetHealth_HandlesNullCode() + { + var healthStatus = (IsHealthy: true, Code: (string)null, Status: "OK"); + + _healthCheck.TrySetHealth(healthStatus); + + Assert.Multiple(() => + { + Assert.That(_healthCheck.IsHealthy, Is.True); + Assert.That(_healthCheck.Status, Is.EqualTo("OK")); + Assert.That(_healthCheck.LastError, Is.Null); + }); + } + + [Test] + public void TrySetHealth_HandlesEmptyCode() + { + var healthStatus = (IsHealthy: true, Code: string.Empty, Status: "OK"); + + _healthCheck.TrySetHealth(healthStatus); + + Assert.Multiple(() => + { + Assert.That(_healthCheck.IsHealthy, Is.True); + Assert.That(_healthCheck.Status, Is.EqualTo("OK")); + Assert.That(_healthCheck.LastError, Is.Empty); + }); + } + + [Test] + public void TrySetHealth_HandlesFalseIsHealthy() + { + var healthStatus = (IsHealthy: false, Code: "500", Status: "Error"); + + _healthCheck.TrySetHealth(healthStatus); + + Assert.Multiple(() => + { + Assert.That(_healthCheck.IsHealthy, Is.False); + Assert.That(_healthCheck.Status, Is.EqualTo("Error")); + Assert.That(_healthCheck.LastError, Is.EqualTo("500")); + }); + } + + [Test] + public void ToYaml_HandlesNullValues() + { + var yaml = _healthCheck.ToYaml(); + + Assert.Multiple(() => + { + Assert.That(yaml, Does.Match(@"healthy:\sFalse")); + Assert.That(yaml, Does.Match(@"status:\s\n")); + Assert.That(yaml, Does.Match(@"last_error:\s\n")); + Assert.That(yaml, Does.Match(@"start_time_unix_nano:\s\d+")); + Assert.That(yaml, Does.Match(@"status_time_unix_nano:\s-?\d+")); + }); + } + + [Test] + public void TrySetHealth_Healthy() + { + _healthCheck.TrySetHealth(HealthCodes.Healthy); + + Assert.Multiple(() => + { + Assert.That(_healthCheck.IsHealthy, Is.True); + Assert.That(_healthCheck.Status, Is.EqualTo("Healthy")); + Assert.That(_healthCheck.LastError, Is.EqualTo("NR-APM-000")); + Assert.That(_healthCheck.StatusTime, Is.EqualTo(DateTime.UtcNow).Within(TimeSpan.FromSeconds(5))); + }); + } + + [Test] + public void TrySetHealth_LicenseKeyInvalid() + { + _healthCheck.TrySetHealth(HealthCodes.LicenseKeyInvalid); + + Assert.Multiple(() => + { + Assert.That(_healthCheck.IsHealthy, Is.False); + Assert.That(_healthCheck.Status, Is.EqualTo("Invalid license key (HTTP status code 401)")); + Assert.That(_healthCheck.LastError, Is.EqualTo("NR-APM-001")); + Assert.That(_healthCheck.StatusTime, Is.EqualTo(DateTime.UtcNow).Within(TimeSpan.FromSeconds(5))); + }); + } + + [Test] + public void TrySetHealth_LicenseKeyMissing() + { + _healthCheck.TrySetHealth(HealthCodes.LicenseKeyMissing); + + Assert.Multiple(() => + { + Assert.That(_healthCheck.IsHealthy, Is.False); + Assert.That(_healthCheck.Status, Is.EqualTo("License key missing in configuration")); + Assert.That(_healthCheck.LastError, Is.EqualTo("NR-APM-002")); + Assert.That(_healthCheck.StatusTime, Is.EqualTo(DateTime.UtcNow).Within(TimeSpan.FromSeconds(5))); + }); + } + + [Test] + public void TrySetHealth_ForceDisconnect() + { + _healthCheck.TrySetHealth(HealthCodes.ForceDisconnect); + + Assert.Multiple(() => + { + Assert.That(_healthCheck.IsHealthy, Is.False); + Assert.That(_healthCheck.Status, Is.EqualTo("Forced disconnect received from New Relic (HTTP status code 410)")); + Assert.That(_healthCheck.LastError, Is.EqualTo("NR-APM-003")); + Assert.That(_healthCheck.StatusTime, Is.EqualTo(DateTime.UtcNow).Within(TimeSpan.FromSeconds(5))); + }); + } + + [Test] + public void TrySetHealth_HttpError() + { + _healthCheck.TrySetHealth(HealthCodes.HttpError, "500", "metric"); + + Assert.Multiple(() => + { + Assert.That(_healthCheck.IsHealthy, Is.False); + Assert.That(_healthCheck.Status, Is.EqualTo("HTTP error response code 500 received from New Relic while sending data type metric")); + Assert.That(_healthCheck.LastError, Is.EqualTo("NR-APM-004")); + Assert.That(_healthCheck.StatusTime, Is.EqualTo(DateTime.UtcNow).Within(TimeSpan.FromSeconds(5))); + }); + } + + [Test] + public void TrySetHealth_ApplicationNameMissing() + { + _healthCheck.TrySetHealth(HealthCodes.ApplicationNameMissing); + + Assert.Multiple(() => + { + Assert.That(_healthCheck.IsHealthy, Is.False); + Assert.That(_healthCheck.Status, Is.EqualTo("Missing application name in agent configuration")); + Assert.That(_healthCheck.LastError, Is.EqualTo("NR-APM-005")); + Assert.That(_healthCheck.StatusTime, Is.EqualTo(DateTime.UtcNow).Within(TimeSpan.FromSeconds(5))); + }); + } + + [Test] + public void TrySetHealth_MaxApplicationNamesExceeded() + { + _healthCheck.TrySetHealth(HealthCodes.MaxApplicationNamesExceeded); + + Assert.Multiple(() => + { + Assert.That(_healthCheck.IsHealthy, Is.False); + Assert.That(_healthCheck.Status, Is.EqualTo("The maximum number of configured app names (3) exceeded")); + Assert.That(_healthCheck.LastError, Is.EqualTo("NR-APM-006")); + Assert.That(_healthCheck.StatusTime, Is.EqualTo(DateTime.UtcNow).Within(TimeSpan.FromSeconds(5))); + }); + } + + [Test] + public void TrySetHealth_HttpProxyError() + { + _healthCheck.TrySetHealth(HealthCodes.HttpProxyError, "407"); + + Assert.Multiple(() => + { + Assert.That(_healthCheck.IsHealthy, Is.False); + Assert.That(_healthCheck.Status, Is.EqualTo("HTTP Proxy configuration error; response code 407")); + Assert.That(_healthCheck.LastError, Is.EqualTo("NR-APM-007")); + Assert.That(_healthCheck.StatusTime, Is.EqualTo(DateTime.UtcNow).Within(TimeSpan.FromSeconds(5))); + }); + } + + [Test] + public void TrySetHealth_AgentDisabledByConfiguration() + { + _healthCheck.TrySetHealth(HealthCodes.AgentDisabledByConfiguration); + + Assert.Multiple(() => + { + Assert.That(_healthCheck.IsHealthy, Is.False); + Assert.That(_healthCheck.Status, Is.EqualTo("Agent is disabled via configuration")); + Assert.That(_healthCheck.LastError, Is.EqualTo("NR-APM-008")); + Assert.That(_healthCheck.StatusTime, Is.EqualTo(DateTime.UtcNow).Within(TimeSpan.FromSeconds(5))); + }); + } + + [Test] + public void TrySetHealth_FailedToConnect() + { + _healthCheck.TrySetHealth(HealthCodes.FailedToConnect); + + Assert.Multiple(() => + { + Assert.That(_healthCheck.IsHealthy, Is.False); + Assert.That(_healthCheck.Status, Is.EqualTo("Failed to connect to New Relic data collector")); + Assert.That(_healthCheck.LastError, Is.EqualTo("NR-APM-009")); + Assert.That(_healthCheck.StatusTime, Is.EqualTo(DateTime.UtcNow).Within(TimeSpan.FromSeconds(5))); + }); + } + + [Test] + public void TrySetHealth_AgentShutdownHealthy() + { + _healthCheck.TrySetHealth(HealthCodes.AgentShutdownHealthy); + + Assert.Multiple(() => + { + Assert.That(_healthCheck.IsHealthy, Is.True); + Assert.That(_healthCheck.Status, Is.EqualTo("Agent has shutdown")); + Assert.That(_healthCheck.LastError, Is.EqualTo("NR-APM-099")); + Assert.That(_healthCheck.StatusTime, Is.EqualTo(DateTime.UtcNow).Within(TimeSpan.FromSeconds(5))); + }); + } + + [Test] + public void TrySetHealth_AgentShutdownError() + { + _healthCheck.TrySetHealth(HealthCodes.AgentShutdownError, "Exception message"); + + Assert.Multiple(() => + { + Assert.That(_healthCheck.IsHealthy, Is.False); + Assert.That(_healthCheck.Status, Is.EqualTo("Agent has shutdown with exception Exception message")); + Assert.That(_healthCheck.LastError, Is.EqualTo("NR-APM-200")); + Assert.That(_healthCheck.StatusTime, Is.EqualTo(DateTime.UtcNow).Within(TimeSpan.FromSeconds(5))); + }); + } + } +} diff --git a/tests/Agent/UnitTests/Core.UnitTest/Attributes/AttributeDefinitionServiceTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Attributes/AttributeDefinitionServiceTests.cs index e95b099766..ccf7a47ba0 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Attributes/AttributeDefinitionServiceTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Attributes/AttributeDefinitionServiceTests.cs @@ -19,6 +19,7 @@ using NewRelic.Agent.Core.WireModels; using NewRelic.Agent.Core.Segments; using System.Collections.Generic; +using NewRelic.Agent.Core.AgentHealth; namespace NewRelic.Agent.Core.Attributes.Tests { @@ -43,6 +44,7 @@ public class AttributeDefinitionServiceTests private IProcessStatic _processStatic; private IConfigurationManagerStatic _configurationManagerStatic; private IDnsStatic _dnsStatic; + private IAgentHealthReporter _agentHealthReporter; private ConfigurationAutoResponder _configAutoResponder; @@ -63,7 +65,7 @@ public void SetUp() _dnsStatic = Mock.Create(); _securityPoliciesConfiguration = new SecurityPoliciesConfiguration(); _bootstrapConfiguration = Mock.Create(); - + _agentHealthReporter = Mock.Create(); _runTimeConfiguration = new RunTimeConfiguration(); _serverConfig = new ServerConfiguration(); _localConfig = new configuration(); @@ -86,7 +88,7 @@ public void SetUp() private void UpdateConfig() { - _configuration = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfiguration, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + _configuration = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfiguration, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Mock.Arrange(() => _configurationService.Configuration).Returns(_configuration); EventBus.Publish(new ConfigurationUpdatedEvent(_configuration, ConfigurationUpdateSource.Local)); } diff --git a/tests/Agent/UnitTests/Core.UnitTest/Attributes/LogContextDataFilterTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Attributes/LogContextDataFilterTests.cs index ec5c4527b2..ca37a34444 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Attributes/LogContextDataFilterTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Attributes/LogContextDataFilterTests.cs @@ -15,6 +15,7 @@ using NewRelic.Agent.Core.Config; using NewRelic.Agent.Core.Configuration.UnitTest; using NUnit.Framework.Constraints; +using NewRelic.Agent.Core.AgentHealth; namespace NewRelic.Agent.Core.Attributes.Tests { @@ -35,6 +36,7 @@ public class LogContextDataFilterTests private IProcessStatic _processStatic; private IConfigurationManagerStatic _configurationManagerStatic; private IDnsStatic _dnsStatic; + private IAgentHealthReporter _agentHealthReporter; private Dictionary _unfilteredContextData = new Dictionary() @@ -57,7 +59,7 @@ public void SetUp() _dnsStatic = Mock.Create(); _securityPoliciesConfiguration = new SecurityPoliciesConfiguration(); _bootstrapConfiguration = Mock.Create(); - + _agentHealthReporter = Mock.Create(); _runTimeConfiguration = new RunTimeConfiguration(); _serverConfig = new ServerConfiguration(); _localConfig = new configuration(); @@ -158,7 +160,7 @@ public void LogContextDataFilterRule(string inputRuleText, bool isInclude, strin } private void UpdateConfig() { - _configuration = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfiguration, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + _configuration = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfiguration, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Mock.Arrange(() => _configurationService.Configuration).Returns(_configuration); EventBus.Publish(new ConfigurationUpdatedEvent(_configuration, ConfigurationUpdateSource.Local)); } diff --git a/tests/Agent/UnitTests/Core.UnitTest/Config/BootstrapConfigurationTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Config/BootstrapConfigurationTests.cs index 20a8bcdda1..ac6e25b55c 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Config/BootstrapConfigurationTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Config/BootstrapConfigurationTests.cs @@ -38,6 +38,9 @@ public void TestDefaultBootstrapConfiguration() Assert.That(config.ServerlessFunctionName, Is.Null); Assert.That(config.ServerlessFunctionVersion, Is.Null); Assert.That(config.GCSamplerV2Enabled, Is.False); + Assert.That(config.AgentControlEnabled, Is.False); + Assert.That(config.HealthDeliveryLocation, Is.Null); + Assert.That(config.HealthFrequency, Is.EqualTo(5)); }); } @@ -203,6 +206,72 @@ public void GCSamplerV2_EnabledViaEnvironmentVariable() } } + [Test] + public void TestAgentControlEnabled_EnabledViaEnvironmentVariable() + { + _originalEnvironment = ConfigLoaderHelpers.EnvironmentVariableProxy; + try + { + var environmentMock = Mock.Create(); + Mock.Arrange(() => environmentMock.GetEnvironmentVariable(Arg.IsAny())).Returns(MockGetEnvironmentVar); + ConfigLoaderHelpers.EnvironmentVariableProxy = environmentMock; + + SetEnvironmentVar("NEW_RELIC_AGENT_CONTROL_ENABLED", "true"); + + var config = CreateBootstrapConfiguration(); + + Assert.That(config.AgentControlEnabled, Is.True); + } + finally + { + ConfigLoaderHelpers.EnvironmentVariableProxy = _originalEnvironment; + } + } + + [Test] + public void TestHealthDeliveryLocation_SetViaEnvironmentVariable() + { + _originalEnvironment = ConfigLoaderHelpers.EnvironmentVariableProxy; + try + { + var environmentMock = Mock.Create(); + Mock.Arrange(() => environmentMock.GetEnvironmentVariable(Arg.IsAny())).Returns(MockGetEnvironmentVar); + ConfigLoaderHelpers.EnvironmentVariableProxy = environmentMock; + + SetEnvironmentVar("NEW_RELIC_AGENT_CONTROL_HEALTH_DELIVERY_LOCATION", "http://example.com"); + + var config = CreateBootstrapConfiguration(); + + Assert.That(config.HealthDeliveryLocation, Is.EqualTo("http://example.com")); + } + finally + { + ConfigLoaderHelpers.EnvironmentVariableProxy = _originalEnvironment; + } + } + + [Test] + public void TestHealthFrequency_SetViaEnvironmentVariable() + { + _originalEnvironment = ConfigLoaderHelpers.EnvironmentVariableProxy; + try + { + var environmentMock = Mock.Create(); + Mock.Arrange(() => environmentMock.GetEnvironmentVariable(Arg.IsAny())).Returns(MockGetEnvironmentVar); + ConfigLoaderHelpers.EnvironmentVariableProxy = environmentMock; + + SetEnvironmentVar("NEW_RELIC_AGENT_CONTROL_HEALTH_FREQUENCY", "10"); + + var config = CreateBootstrapConfiguration(); + + Assert.That(config.HealthFrequency, Is.EqualTo(10)); + } + finally + { + ConfigLoaderHelpers.EnvironmentVariableProxy = _originalEnvironment; + } + } + private BootstrapConfiguration CreateBootstrapConfiguration() { return new BootstrapConfiguration(_localConfiguration, TestFileName, _ => _webConfigValueWithProvenance, _configurationManagerStatic, new ProcessStatic(), Directory.Exists, Path.GetFullPath); diff --git a/tests/Agent/UnitTests/Core.UnitTest/Configuration/ConfigurationService.cs b/tests/Agent/UnitTests/Core.UnitTest/Configuration/ConfigurationService.cs index 987fb13df9..00ea0de93e 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Configuration/ConfigurationService.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Configuration/ConfigurationService.cs @@ -12,6 +12,7 @@ using System; using System.Collections.Generic; using Telerik.JustMock; +using NewRelic.Agent.Core.AgentHealth; namespace NewRelic.Agent.Core.Configuration.UnitTest { @@ -27,7 +28,8 @@ public class Event_ConfigurationDeserialized public void SetUp() { _configurationService = new ConfigurationService(Mock.Create(), Mock.Create(), - Mock.Create(), Mock.Create(), Mock.Create()); + Mock.Create(), Mock.Create(), Mock.Create(), + Mock.Create()); } [TearDown] @@ -57,7 +59,7 @@ public class Event_ConnectedToCollector public void publishes_ConfigurationUpdatedEvent() { var wasCalled = false; - using (new ConfigurationService(Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create())) + using (new ConfigurationService(Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create())) using (new EventSubscription(_ => wasCalled = true)) { EventBus.Publish(new ServerConfigurationUpdatedEvent(new ServerConfiguration @@ -78,7 +80,7 @@ public class Event_AppNameUpdateEvent public void publishes_AppNameUpdateEvent() { var wasCalled = false; - using (new ConfigurationService(Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create())) + using (new ConfigurationService(Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create())) using (new EventSubscription(_ => wasCalled = true)) { EventBus.Publish(new AppNameUpdateEvent(new[] { "NewAppName" })); @@ -99,7 +101,7 @@ public class Event_ErrorGroupCallbackUpdateEvent [SetUp] public void SetUp() { - _configurationService = new ConfigurationService(Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create()); + _configurationService = new ConfigurationService(Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create()); } [TearDown] @@ -144,7 +146,7 @@ public class Request_GetCurrentConfiguration [SetUp] public void SetUp() { - _configurationService = new ConfigurationService(Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create()); + _configurationService = new ConfigurationService(Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create()); } [TearDown] @@ -192,7 +194,7 @@ public class ConfigurationSeviceUpdatesLogLevel [SetUp] public void SetUp() { - _configurationService = new ConfigurationService(Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create()); + _configurationService = new ConfigurationService(Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create()); _logLevelChanged = false; _newLogLevel = null; diff --git a/tests/Agent/UnitTests/Core.UnitTest/Configuration/DefaultConfigurationTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Configuration/DefaultConfigurationTests.cs index d6559d45ff..3028fa1d6f 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Configuration/DefaultConfigurationTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Configuration/DefaultConfigurationTests.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Xml.Serialization; +using NewRelic.Agent.Core.AgentHealth; using NewRelic.Agent.Core.Config; using NewRelic.Agent.Core.DataTransport; using NewRelic.Agent.Core.SharedInterfaces; @@ -18,8 +19,8 @@ namespace NewRelic.Agent.Core.Configuration.UnitTest { internal class TestableDefaultConfiguration : DefaultConfiguration { - public TestableDefaultConfiguration(IEnvironment environment, configuration localConfig, ServerConfiguration serverConfig, RunTimeConfiguration runTimeConfiguration, SecurityPoliciesConfiguration securityPoliciesConfiguration, IBootstrapConfiguration bootstrapConfiguration, IProcessStatic processStatic, IHttpRuntimeStatic httpRuntimeStatic, IConfigurationManagerStatic configurationManagerStatic, IDnsStatic dnsStatic) - : base(environment, localConfig, serverConfig, runTimeConfiguration, securityPoliciesConfiguration, bootstrapConfiguration, processStatic, httpRuntimeStatic, configurationManagerStatic, dnsStatic) { } + public TestableDefaultConfiguration(IEnvironment environment, configuration localConfig, ServerConfiguration serverConfig, RunTimeConfiguration runTimeConfiguration, SecurityPoliciesConfiguration securityPoliciesConfiguration, IBootstrapConfiguration bootstrapConfiguration, IProcessStatic processStatic, IHttpRuntimeStatic httpRuntimeStatic, IConfigurationManagerStatic configurationManagerStatic, IDnsStatic dnsStatic, IAgentHealthReporter agentHealthReporter) + : base(environment, localConfig, serverConfig, runTimeConfiguration, securityPoliciesConfiguration, bootstrapConfiguration, processStatic, httpRuntimeStatic, configurationManagerStatic, dnsStatic, agentHealthReporter) { } public static void ResetStatics() { @@ -42,6 +43,7 @@ public class DefaultConfigurationTests private IBootstrapConfiguration _bootstrapConfiguration; private DefaultConfiguration _defaultConfig; private IDnsStatic _dnsStatic; + private IAgentHealthReporter _agentHealthReporter; [SetUp] public void SetUp() @@ -56,8 +58,9 @@ public void SetUp() _securityPoliciesConfiguration = new SecurityPoliciesConfiguration(); _bootstrapConfiguration = Mock.Create(); _dnsStatic = Mock.Create(); + _agentHealthReporter = Mock.Create(); - _defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + _defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); TestableDefaultConfiguration.ResetStatics(); } @@ -94,7 +97,7 @@ public bool TransactionEventsCanBeDisabledByServer(bool? server, bool local) [Test] public void EveryConfigShouldGetNewVersionNumber() { - var newConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var newConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(newConfig.ConfigurationVersion - 1, Is.EqualTo(_defaultConfig.ConfigurationVersion)); } @@ -736,7 +739,7 @@ private void SetupNewConfigsWithSecurityPolicy(string securityPolicyName, bool s { var securityPolicies = new Dictionary { { securityPolicyName, new SecurityPolicyState(securityPolicyEnabled, false) } }; _securityPoliciesConfiguration = new SecurityPoliciesConfiguration(securityPolicies); - _defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + _defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); } [TestCase(true, true, ExpectedResult = true)] @@ -939,7 +942,7 @@ public void Decodes_IgnoreAndExpectedClasses_IgnoreAndExpectedMessages_ExpectedS localConfiguration = serializer.Deserialize(reader) as configuration; } - _defaultConfig = new TestableDefaultConfiguration(_environment, localConfiguration, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + _defaultConfig = new TestableDefaultConfiguration(_environment, localConfiguration, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(new[] { "404", "500" }, Is.EquivalentTo(_defaultConfig.ExpectedErrorStatusCodesForAgentSettings)); @@ -1356,7 +1359,7 @@ public void ThreadProfilingIgnoreMethodFromXmlDecodesIntoListOfStrings() localConfiguration = serializer.Deserialize(reader) as configuration; } - _defaultConfig = new TestableDefaultConfiguration(_environment, localConfiguration, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + _defaultConfig = new TestableDefaultConfiguration(_environment, localConfiguration, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(_defaultConfig.ThreadProfilingIgnoreMethods, Does.Contain("System.Threading.WaitHandle:WaitAny")); } @@ -2152,7 +2155,7 @@ public void ApplicationNamesUsesAzureFunctionName_IfAzureFunctionMode_IsEnabled( _runTimeConfig.ApplicationNames = new List(); _localConfig.appSettings.Add(new configurationAdd { key = "AzureFunctionModeEnabled", value = functionModeEnabled.ToString() }); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Mock.Arrange(() => _bootstrapConfiguration.AzureFunctionModeDetected).Returns(functionModeEnabled); @@ -2177,7 +2180,7 @@ public void ApplicationNameDoesNotUserAzureFunctionName_IfAzureModeIsEnabled_But _runTimeConfig.ApplicationNames = new List(); _localConfig.appSettings.Add(new configurationAdd { key = "AzureFunctionModeEnabled", value = "true" }); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Mock.Arrange(() => _bootstrapConfiguration.AzureFunctionModeDetected).Returns(true); @@ -2218,7 +2221,7 @@ public void UseResourceBasedNamingIsEnabled() value = "true" }); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(defaultConfig.UseResourceBasedNamingForWCFEnabled, Is.True); } @@ -2226,7 +2229,7 @@ public void UseResourceBasedNamingIsEnabled() [Test] public void UseResourceBasedNamingIsDisabledByDefault() { - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(defaultConfig.UseResourceBasedNamingForWCFEnabled, Is.False); } @@ -2295,7 +2298,7 @@ public void CrossApplicationTracingEnabledIsTrueWithNewServerConfig() _localConfig.crossApplicationTracer.enabled = true; _serverConfig = new ServerConfiguration(); _serverConfig.CatId = "123#456"; - _defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + _defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(_defaultConfig.CrossApplicationTracingEnabled, Is.True); } @@ -2307,7 +2310,7 @@ public void CrossApplicationTracingEnabledIsFalseWithGetDefaultServerConfig() _localConfig.crossApplicationTracer.enabled = true; _serverConfig = ServerConfiguration.GetDefault(); _serverConfig.CatId = "123#456"; - _defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + _defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(_defaultConfig.CrossApplicationTracingEnabled, Is.False); } @@ -2322,7 +2325,7 @@ public void CrossApplicationTracingEnabledIs_False_InServerlessMode() _localConfig.crossApplicationTracer.enabled = true; _serverConfig = new ServerConfiguration(); _serverConfig.CatId = "123#456"; - _defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + _defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(_defaultConfig.CrossApplicationTracingEnabled, Is.False); } @@ -2342,7 +2345,7 @@ public void DistributedTracingEnabled(bool localConfig, bool expectedResult) [Test] public void DistributedTracingEnabledIsFalseByDefault() { - _defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + _defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(_defaultConfig.DistributedTracingEnabled, Is.False); } @@ -2394,7 +2397,7 @@ public void SamplingTarget_Is10_InServerlessMode() Mock.Arrange(() => _bootstrapConfiguration.ServerlessModeEnabled).Returns(true); // Act - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); // Assert Assert.That(defaultConfig.SamplingTarget, Is.EqualTo(10)); @@ -2416,7 +2419,7 @@ public void SamplingTargetPeriodInSeconds_Is60_InServerlessMode() Mock.Arrange(() => _bootstrapConfiguration.ServerlessModeEnabled).Returns(true); // Act - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); // Assert Assert.That(defaultConfig.SamplingTargetPeriodInSeconds, Is.EqualTo(60)); @@ -2430,7 +2433,7 @@ public void PrimaryApplicationIdValueIsSetFromEnvironmentVariable_WhenInServerle Mock.Arrange(() => _environment.GetEnvironmentVariableFromList("NEW_RELIC_PRIMARY_APPLICATION_ID")).Returns("PrimaryApplicationIdValue"); // Act - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); // Assert Assert.That(defaultConfig.PrimaryApplicationId, Is.EqualTo("PrimaryApplicationIdValue")); @@ -2443,7 +2446,7 @@ public void TrustedAccountKeyValueIsSetFromEnvironmentVariable_WhenInServerlessM Mock.Arrange(() => _environment.GetEnvironmentVariableFromList("NEW_RELIC_TRUSTED_ACCOUNT_KEY")).Returns("TrustedAccountKeyValue"); // Act - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); // Assert Assert.That(defaultConfig.TrustedAccountKey, Is.EqualTo("TrustedAccountKeyValue")); @@ -2456,7 +2459,7 @@ public void AccountIdValueIsSetFromEnvironmentVariable_WhenInServerlessMode() Mock.Arrange(() => _environment.GetEnvironmentVariableFromList("NEW_RELIC_ACCOUNT_ID")).Returns("AccountIdValue"); // Act - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); // Assert Assert.That(defaultConfig.AccountId, Is.EqualTo("AccountIdValue")); @@ -2468,7 +2471,7 @@ public void PrimaryApplicationId_DefaultsToUnknown_WhenInServerlessMode() Mock.Arrange(() => _bootstrapConfiguration.ServerlessModeEnabled).Returns(true); // Act - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); // Assert Assert.That(defaultConfig.PrimaryApplicationId, Is.EqualTo("Unknown")); @@ -2481,7 +2484,7 @@ public void PrimaryApplicationId_ComesFromLocalConfig_WhenInServerlessMode() _localConfig.distributedTracing.primary_application_id = "PrimaryApplicationIdValue"; // Act - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); // Assert Assert.That(defaultConfig.PrimaryApplicationId, Is.EqualTo("PrimaryApplicationIdValue")); @@ -2494,7 +2497,7 @@ public void TrustedAccountKey_ComesFromLocalConfig_WhenInServerlessMode() _localConfig.distributedTracing.trusted_account_key = "TrustedAccountKeyValue"; // Act - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); // Assert Assert.That(defaultConfig.TrustedAccountKey, Is.EqualTo("TrustedAccountKeyValue")); @@ -2507,7 +2510,7 @@ public void AccountId_ComesFromLocalConfig_WhenInServerlessMode() _localConfig.distributedTracing.account_id = "AccountIdValue"; // Act - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); // Assert Assert.That(defaultConfig.AccountId, Is.EqualTo("AccountIdValue")); @@ -2532,7 +2535,7 @@ public bool SpanEventsEnabledHasCorrectValue(bool distributedTracingEnabled, boo _localConfig.spanEvents.enabled = spanEventsEnabled; _localConfig.distributedTracing.enabled = distributedTracingEnabled; - _defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + _defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); return _defaultConfig.SpanEventsEnabled; } @@ -2627,7 +2630,7 @@ public int InfiniteTracing_SpanQueueSize(string envConfigValue, int? localConfig _localConfig.infiniteTracing.span_events.queue_size = localConfigValue.Value; } - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); return defaultConfig.InfiniteTracingQueueSizeSpans; } @@ -2660,7 +2663,7 @@ public void InfiniteTracing_TraceObserver _localConfig.appSettings.Add(new configurationAdd() { key = "InfiniteTracingTraceObserverSsl", value = localSsl }); } - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); var expectedHost = envHost != null ? envHost @@ -2697,7 +2700,7 @@ public int InfiniteTracing_TimeoutData(string envConfigVal, string appSettingsVa _localConfig.appSettings.Add(new configurationAdd { key = "InfiniteTracingTimeoutSend", value = appSettingsValue }); Mock.Arrange(() => _environment.GetEnvironmentVariableFromList("NEW_RELIC_INFINITE_TRACING_TIMEOUT_SEND")).Returns(envConfigVal); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); return defaultConfig.InfiniteTracingTraceTimeoutMsSendData; } @@ -2715,7 +2718,7 @@ public int InfiniteTracing_TimeoutConnect(string envConfigVal, string appSetting _localConfig.appSettings.Add(new configurationAdd { key = "InfiniteTracingTimeoutConnect", value = appSettingsValue }); Mock.Arrange(() => _environment.GetEnvironmentVariableFromList("NEW_RELIC_INFINITE_TRACING_TIMEOUT_CONNECT")).Returns(envConfigVal); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); return defaultConfig.InfiniteTracingTraceTimeoutMsConnect; } @@ -2737,7 +2740,7 @@ public int InfiniteTracing_TimeoutConnect(string envConfigVal, string appSetting _localConfig.appSettings.Add(new configurationAdd { key = "InfiniteTracingSpanEventsTestFlaky", value = appSettingsValue }); Mock.Arrange(() => _environment.GetEnvironmentVariable("NEW_RELIC_INFINITE_TRACING_SPAN_EVENTS_TEST_FLAKY")).Returns(envConfigVal); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); return defaultConfig.InfiniteTracingTraceObserverTestFlaky; } @@ -2760,7 +2763,7 @@ public int InfiniteTracing_SpanBatchSize(string envConfigVal, string appSettingV _localConfig.appSettings.Add(new configurationAdd { key = "InfiniteTracingSpanEventsBatchSize", value = appSettingVal }); Mock.Arrange(() => _environment.GetEnvironmentVariableFromList("NEW_RELIC_INFINITE_TRACING_SPAN_EVENTS_BATCH_SIZE")).Returns(envConfigVal); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); return defaultConfig.InfiniteTracingBatchSizeSpans; } @@ -2783,7 +2786,7 @@ public int InfiniteTracing_SpanPartitionCount(string envConfigVal, string appSet _localConfig.appSettings.Add(new configurationAdd { key = "InfiniteTracingSpanEventsPartitionCount", value = appSettingVal }); Mock.Arrange(() => _environment.GetEnvironmentVariableFromList("NEW_RELIC_INFINITE_TRACING_SPAN_EVENTS_PARTITION_COUNT")).Returns(envConfigVal); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); return defaultConfig.InfiniteTracingPartitionCountSpans; } @@ -2802,7 +2805,7 @@ public int InfiniteTracing_SpanPartitionCount(string envConfigVal, string appSet _localConfig.appSettings.Add(new configurationAdd { key = "InfiniteTracingSpanEventsTestDelay", value = appSettingsValue }); Mock.Arrange(() => _environment.GetEnvironmentVariable("NEW_RELIC_INFINITE_TRACING_SPAN_EVENTS_TEST_DELAY")).Returns(envConfigVal); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); return defaultConfig.InfiniteTracingTraceObserverTestDelayMs; } @@ -2827,7 +2830,7 @@ public void InfiniteTracing_SpanStreamsCount _localConfig.appSettings.Add(new configurationAdd { key = "InfiniteTracingSpanEventsStreamsCount", value = appSettingsValue }); Mock.Arrange(() => _environment.GetEnvironmentVariableFromList("NEW_RELIC_INFINITE_TRACING_SPAN_EVENTS_STREAMS_COUNT")).Returns(envConfigVal); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(defaultConfig.InfiniteTracingTraceCountConsumers, Is.EqualTo(expectedResult)); } @@ -2846,7 +2849,7 @@ public bool InfiniteTracing_Compression(string envConfigVal, bool? localConfigVa _localConfig.infiniteTracing.compression = localConfigVal.Value; } - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); return defaultConfig.InfiniteTracingCompression; } @@ -3554,7 +3557,7 @@ public bool SecurityPoliciesTokenExists(string environmentValue, string localCon _localConfig.securityPoliciesToken = localConfigValue; Mock.Arrange(() => _environment.GetEnvironmentVariableFromList("NEW_RELIC_SECURITY_POLICIES_TOKEN")) .Returns(environmentValue); - _defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + _defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); return _defaultConfig.SecurityPoliciesTokenExists; } @@ -3568,7 +3571,7 @@ public bool SecurityPoliciesTokenExists(string environmentValue, string localCon public bool AsyncHttpClientSegmentsDoNotCountTowardsParentExclusiveTimeTests(string localConfigValue) { _localConfig.appSettings.Add(new configurationAdd { key = "ForceSynchronousTimingCalculation.HttpClient", value = localConfigValue }); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); return defaultConfig.ForceSynchronousTimingCalculationHttpClient; } @@ -3580,7 +3583,7 @@ public bool AsyncHttpClientSegmentsDoNotCountTowardsParentExclusiveTimeTests(str public bool AspNetCore6PlusBrowserInjectionTests(string localConfigValue) { _localConfig.appSettings.Add(new configurationAdd { key = "EnableAspNetCore6PlusBrowserInjection", value = localConfigValue }); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); return defaultConfig.EnableAspNetCore6PlusBrowserInjection; } @@ -3650,7 +3653,7 @@ public bool ShouldCodeLevelMetricsBeEnabled(bool? localConfigValue, string envCo [Test] public void HarvestCycleOverride_DefaultOrNotSet() { - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.Multiple(() => { @@ -3677,7 +3680,7 @@ public void HarvestCycleOverride_Metrics_NotValidValueSet(string value) value = value }); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(defaultConfig.MetricsHarvestCycle.TotalSeconds, Is.EqualTo(60)); } @@ -3695,7 +3698,7 @@ public void HarvestCycleOverride_TransactionTraces_NotValidValueSet(string value value = value }); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(defaultConfig.TransactionTracesHarvestCycle.TotalSeconds, Is.EqualTo(60)); } @@ -3713,7 +3716,7 @@ public void HarvestCycleOverride_ErrorTraces_NotValidValueSet(string value) value = value }); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(defaultConfig.ErrorTracesHarvestCycle.TotalSeconds, Is.EqualTo(60)); } @@ -3731,7 +3734,7 @@ public void HarvestCycleOverride_SpanEvents_NotValidValueSet(string value) value = value }); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(defaultConfig.SpanEventsHarvestCycle.TotalSeconds, Is.EqualTo(60)); } @@ -3749,7 +3752,7 @@ public void HarvestCycleOverride_GetAgentCommands_NotValidValueSet(string value) value = value }); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(defaultConfig.GetAgentCommandsCycle.TotalSeconds, Is.EqualTo(60)); } @@ -3767,7 +3770,7 @@ public void HarvestCycleOverride_SqlTraces_NotValidValueSet(string value) value = value }); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(defaultConfig.SqlTracesHarvestCycle.TotalSeconds, Is.EqualTo(60)); } @@ -3785,7 +3788,7 @@ public void HarvestCycleOverride_StackExchangeRedisCleanup_NotValidValueSet(stri value = value }); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(defaultConfig.StackExchangeRedisCleanupCycle.TotalSeconds, Is.EqualTo(60)); } @@ -3800,7 +3803,7 @@ public void HarvestCycleOverride_Metrics_ValidValueSet() value = expectedSeconds }); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(defaultConfig.MetricsHarvestCycle.TotalSeconds, Is.EqualTo(Convert.ToInt32(expectedSeconds))); @@ -3824,7 +3827,7 @@ public void HarvestCycleOverride_TransactionTraces_ValidValueSet() value = expectedSeconds }); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(defaultConfig.TransactionTracesHarvestCycle.TotalSeconds, Is.EqualTo(Convert.ToInt32(expectedSeconds))); @@ -3848,7 +3851,7 @@ public void HarvestCycleOverride_ErrorTraces_ValidValueSet() value = expectedSeconds }); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(defaultConfig.ErrorTracesHarvestCycle.TotalSeconds, Is.EqualTo(Convert.ToInt32(expectedSeconds))); @@ -3872,7 +3875,7 @@ public void HarvestCycleOverride_SpanEvents_ValidValueSet() value = expectedSeconds }); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(defaultConfig.SpanEventsHarvestCycle.TotalSeconds, Is.EqualTo(Convert.ToInt32(expectedSeconds))); @@ -3896,7 +3899,7 @@ public void HarvestCycleOverride_GetAgentCommands_ValidValueSet() value = expectedSeconds }); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(defaultConfig.GetAgentCommandsCycle.Seconds, Is.EqualTo(Convert.ToInt32(expectedSeconds))); @@ -3920,7 +3923,7 @@ public void HarvestCycleOverride_SqlTraces_ValidValueSet() value = expectedSeconds }); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(defaultConfig.SqlTracesHarvestCycle.TotalSeconds, Is.EqualTo(Convert.ToInt32(expectedSeconds))); @@ -3944,7 +3947,7 @@ public void HarvestCycleOverride_StackExchangeRedisCleanup_ValidValueSet() value = expectedSeconds }); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(defaultConfig.StackExchangeRedisCleanupCycle.TotalSeconds, Is.EqualTo(Convert.ToInt32(expectedSeconds))); @@ -4105,7 +4108,7 @@ public void AiMonitoringRecordContentDisabledWhenAiMonitoringDisabled() public void LlmTokenCountingCallbackComesFromRuntimeConfig() { var runtimeConfig = new RunTimeConfiguration(Enumerable.Empty(), null, (s1, s2) => 42); - var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, runtimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, runtimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Assert.That(defaultConfig.LlmTokenCountingCallback("foo", "bar"), Is.EqualTo(42)); } #endregion @@ -4511,6 +4514,71 @@ public void Cloud_Section_Parsing_And_Override() #endregion + [Test] + public void InvalidLicenseKey_SetsLicenseKeyMissing_AgentControlStatus() + { + // Arrange + var healthCheck = new HealthCheck(); + Mock.Arrange(() => _agentHealthReporter.SetAgentControlStatus(Arg.IsAny<(bool IsHealthy, string Code, string Status)>(), Arg.IsAny())) + .DoInstead((ValueTuple healthStatus, string[] statusParams) => + { + healthCheck.TrySetHealth(healthStatus, statusParams); + }); + + CreateDefaultConfiguration(); + + // Act + var licenseKey = _defaultConfig.AgentLicenseKey; + + // Assert + Assert.Multiple(() => + { + Assert.That(licenseKey, Is.EqualTo("")); + Assert.That(healthCheck.IsHealthy, Is.False); + Assert.That(healthCheck.Status, Is.EqualTo("License key missing in configuration")); + Assert.That(healthCheck.LastError, Is.EqualTo("NR-APM-002")); + }); + } + + [Test] + public void MissingApplicationName_SetsApplicationNameMissing_AgentControlStatus() + { + var healthCheck = new HealthCheck(); + Mock.Arrange(() => _agentHealthReporter.SetAgentControlStatus(Arg.IsAny<(bool IsHealthy, string Code, string Status)>(), Arg.IsAny())) + .DoInstead((ValueTuple healthStatus, string[] statusParams) => + { + healthCheck.TrySetHealth(healthStatus, statusParams); + }); + + _runTimeConfig.ApplicationNames = new List(); + + //Sets to default return null for all calls unless overriden by later arrange. + Mock.Arrange(() => _environment.GetEnvironmentVariable(Arg.IsAny())).Returns(null); + + Mock.Arrange(() => _configurationManagerStatic.GetAppSetting(Constants.AppSettingsAppName)) + .Returns(null); + + _localConfig.application.name = new List(); + + Mock.Arrange(() => _httpRuntimeStatic.AppDomainAppVirtualPath).Returns("NotNull"); + Mock.Arrange(() => _processStatic.GetCurrentProcess().ProcessName).Returns((string)null); + + CreateDefaultConfiguration(); + + // Act + Assert.Throws(() => _defaultConfig.ApplicationNames.ToList()); + + // Assert + Assert.Multiple(() => + { + Assert.That(healthCheck.IsHealthy, Is.False); + Assert.That(healthCheck.Status, Is.EqualTo("Missing application name in agent configuration")); + Assert.That(healthCheck.LastError, Is.EqualTo("NR-APM-005")); + }); + + } + + private DefaultConfiguration GenerateConfigFromXml(string xml) { var root = new XmlRootAttribute { ElementName = "configuration", Namespace = "urn:newrelic-config" }; @@ -4522,12 +4590,12 @@ private DefaultConfiguration GenerateConfigFromXml(string xml) localConfiguration = serializer.Deserialize(reader) as configuration; } - return new TestableDefaultConfiguration(_environment, localConfiguration, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + return new TestableDefaultConfiguration(_environment, localConfiguration, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); } private void CreateDefaultConfiguration() { - _defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); - } + _defaultConfig = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); + } } } diff --git a/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/DataTransport/CollectorHostNameTests.cs b/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/DataTransport/CollectorHostNameTests.cs index 310262fa83..693598dbd4 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/DataTransport/CollectorHostNameTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/DataTransport/CollectorHostNameTests.cs @@ -14,12 +14,14 @@ using NewRelic.Agent.Core.DataTransport; using System.Reflection; using NewRelic.Agent.TestUtilities; +using NewRelic.Agent.Core.AgentHealth; namespace NewRelic.Agent.Core.CrossAgentTests.DataTransport { internal class TestDefaultConfiguration : DefaultConfiguration { - public TestDefaultConfiguration(IEnvironment environment, configuration localConfig, ServerConfiguration serverConfig, RunTimeConfiguration runTimeConfiguration, SecurityPoliciesConfiguration _securityPoliciesConfiguration, IBootstrapConfiguration bootstrapConfiguration, IProcessStatic processStatic, IHttpRuntimeStatic httpRuntimeStatic, IConfigurationManagerStatic configurationManagerStatic, IDnsStatic dnsStatic) : base(environment, localConfig, serverConfig, runTimeConfiguration, _securityPoliciesConfiguration, bootstrapConfiguration, processStatic, httpRuntimeStatic, configurationManagerStatic, dnsStatic) { } + public TestDefaultConfiguration(IEnvironment environment, configuration localConfig, ServerConfiguration serverConfig, RunTimeConfiguration runTimeConfiguration, SecurityPoliciesConfiguration _securityPoliciesConfiguration, IBootstrapConfiguration bootstrapConfiguration, IProcessStatic processStatic, IHttpRuntimeStatic httpRuntimeStatic, IConfigurationManagerStatic configurationManagerStatic, IDnsStatic dnsStatic, IAgentHealthReporter agentHealthReporter) : + base(environment, localConfig, serverConfig, runTimeConfiguration, _securityPoliciesConfiguration, bootstrapConfiguration, processStatic, httpRuntimeStatic, configurationManagerStatic, dnsStatic, agentHealthReporter) { } } [TestFixture] @@ -47,6 +49,8 @@ public class CollectorHostNameTests private IDnsStatic _dnsStatic; + private IAgentHealthReporter _agentHealthReporter; + public static List CollectorHostnameTestData { get { return GetCollectorHostnameTestData(); } @@ -65,7 +69,8 @@ public void Setup() _securityPoliciesConfiguration = new SecurityPoliciesConfiguration(); _bootstrapConfiguration = Mock.Create(); _dnsStatic = Mock.Create(); - _defaultConfig = new TestDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + _agentHealthReporter = Mock.Create(); + _defaultConfig = new TestDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfig, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); } diff --git a/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/RumTests/RumClientConfigTests.cs b/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/RumTests/RumClientConfigTests.cs index 039a9de4c0..7af4c1e4e1 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/RumTests/RumClientConfigTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/RumTests/RumClientConfigTests.cs @@ -54,6 +54,7 @@ public class RumClientConfigTests private IProcessStatic _processStatic; private IConfigurationManagerStatic _configurationManagerStatic; private IDnsStatic _dnsStatic; + private IAgentHealthReporter _agentHealthReporter; private ConfigurationAutoResponder _configAutoResponder; @@ -88,6 +89,7 @@ private void SetUp(TestCase testCase) _dnsStatic = Mock.Create(); _securityPoliciesConfiguration = new SecurityPoliciesConfiguration(); _bootstrapConfiguration = Mock.Create(); + _agentHealthReporter = Mock.Create(); _runTimeConfiguration = new RunTimeConfiguration(); _serverConfig = new ServerConfiguration(); @@ -107,7 +109,7 @@ private void SetUp(TestCase testCase) _serverConfig.RumSettingsApplicationId = testCase.ConnectReply.ApplicationId; _localConfig.browserMonitoring.attributes.enabled = testCase.BrowserMonitoringAttributesEnabled; - _configuration = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfiguration, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + _configuration = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfiguration, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); _configurationService = Mock.Create(); Mock.Arrange(() => _configurationService.Configuration).Returns(_configuration); diff --git a/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/ServerSentEvent/ServerSentEventTests.cs b/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/ServerSentEvent/ServerSentEventTests.cs index 0bd5b7b310..197b429bdb 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/ServerSentEvent/ServerSentEventTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/CrossAgentTests/ServerSentEvent/ServerSentEventTests.cs @@ -27,12 +27,13 @@ using System.Reflection; using NewRelic.Agent.TestUtilities; using Telerik.JustMock; +using NewRelic.Agent.Core.AgentHealth; namespace NewRelic.Agent.Core.CrossAgentTests { internal class TestableDefaultConfiguration : DefaultConfiguration { - public TestableDefaultConfiguration(IEnvironment environment, configuration localConfig, ServerConfiguration serverConfig, RunTimeConfiguration runTimeConfiguration, SecurityPoliciesConfiguration securityPoliciesConfiguration, IBootstrapConfiguration bootstrapConfiguration, IProcessStatic processStatic, IHttpRuntimeStatic httpRuntimeStatic, IConfigurationManagerStatic configurationManagerStatic, IDnsStatic dnsStatic) : base(environment, localConfig, serverConfig, runTimeConfiguration, securityPoliciesConfiguration, bootstrapConfiguration, processStatic, httpRuntimeStatic, configurationManagerStatic, dnsStatic) { } + public TestableDefaultConfiguration(IEnvironment environment, configuration localConfig, ServerConfiguration serverConfig, RunTimeConfiguration runTimeConfiguration, SecurityPoliciesConfiguration securityPoliciesConfiguration, IBootstrapConfiguration bootstrapConfiguration, IProcessStatic processStatic, IHttpRuntimeStatic httpRuntimeStatic, IConfigurationManagerStatic configurationManagerStatic, IDnsStatic dnsStatic, IAgentHealthReporter agentHealthReporter) : base(environment, localConfig, serverConfig, runTimeConfiguration, securityPoliciesConfiguration, bootstrapConfiguration, processStatic, httpRuntimeStatic, configurationManagerStatic, dnsStatic, agentHealthReporter) { } } [TestFixture] @@ -82,7 +83,7 @@ public void SetUp() _bootstrapConfig = Mock.Create(); _defaultConfig = new TestableDefaultConfiguration(Mock.Create(), _localConfig, _serverConfig, _runTimeConfig, new SecurityPoliciesConfiguration(), _bootstrapConfig, Mock.Create(), Mock.Create(), Mock.Create(), - Mock.Create()); + Mock.Create(), Mock.Create()); _transactionMetricNameMaker = Mock.Create(); Mock.Arrange(() => _transactionMetricNameMaker.GetTransactionMetricName(Arg.Matches(txName => txName.IsWeb))) diff --git a/tests/Agent/UnitTests/Core.UnitTest/DataTransport/AgentSettingsTests.cs b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/AgentSettingsTests.cs index 698aa10e65..989adf183a 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/DataTransport/AgentSettingsTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/AgentSettingsTests.cs @@ -344,12 +344,17 @@ public void serializes_correctly() "ai_monitoring.enabled": true, "ai_monitoring.streaming.enabled": true, "ai_monitoring.record_content.enabled": true, - "gc_sampler_v2.enabled": true + "gc_sampler_v2.enabled": true, + "agent_control.enabled" : true, + "agent_control.health.delivery_location": "file:///tmp/health", + "agent_control.health.frequency": 5 } """; Assert.Multiple(() => { + Assert.That(json, Is.EqualTo(expectedJson.Condense())); + // Confirm that JsonIgnored properties are present, but not serialized Assert.That(agentSettings.AgentLicenseKey, Is.Not.Null); Assert.That(agentSettings.BrowserMonitoringJavaScriptAgent, Is.Not.Null); @@ -361,7 +366,6 @@ public void serializes_correctly() Assert.That(agentSettings.LoggingLevel, Is.Not.Null); Assert.That(agentSettings.ServerlessFunctionName, Is.Null); Assert.That(agentSettings.ServerlessFunctionVersion, Is.Null); - Assert.That(json, Is.EqualTo(expectedJson.Condense())); Assert.That(agentSettings.AwsAccountId, Is.Empty); }); } diff --git a/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ConnectModelTests.cs b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ConnectModelTests.cs index ecac6b540b..4516c72e2f 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ConnectModelTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ConnectModelTests.cs @@ -415,7 +415,10 @@ public void serializes_correctly() "ai_monitoring.enabled": true, "ai_monitoring.streaming.enabled": true, "ai_monitoring.record_content.enabled": true, - "gc_sampler_v2.enabled": true + "gc_sampler_v2.enabled": true, + "agent_control.enabled" : true, + "agent_control.health.delivery_location": "file:///tmp/health", + "agent_control.health.frequency": 5 }, "metadata": { "hello": "there" diff --git a/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ConnectionHandlerTests.cs b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ConnectionHandlerTests.cs new file mode 100644 index 0000000000..88b2480398 --- /dev/null +++ b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ConnectionHandlerTests.cs @@ -0,0 +1,208 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Net; +using NewRelic.Agent.Configuration; +using NewRelic.Agent.Core.AgentHealth; +using NewRelic.Agent.Core.Configuration; +using NewRelic.Agent.Core.Labels; +using NewRelic.Agent.Core.SharedInterfaces; +using NewRelic.Agent.Core.Utilities; +using Newtonsoft.Json; +using NUnit.Framework; +using Telerik.JustMock; + +namespace NewRelic.Agent.Core.DataTransport.Tests +{ + [TestFixture] + public class ConnectionHandlerTests + { + private ISerializer _serializer; + private ICollectorWireFactory _collectorWireFactory; + private IProcessStatic _processStatic; + private IDnsStatic _dnsStatic; + private ILabelsService _labelsService; + private ISystemInfo _systemInfo; + private Environment _environment; + private IAgentHealthReporter _agentHealthReporter; + private IEnvironment _environmentVariableHelper; + private IConfiguration _configuration; + private ConnectionHandler _connectionHandler; + private ICollectorWire _dataRequestWire; + + [SetUp] + public void SetUp() + { + _serializer = Mock.Create(); + _collectorWireFactory = Mock.Create(); + _processStatic = Mock.Create(); + _dnsStatic = Mock.Create(); + _labelsService = Mock.Create(); + _systemInfo = Mock.Create(); + _environment = Mock.Create(); + _agentHealthReporter = Mock.Create(); + _environmentVariableHelper = Mock.Create(); + _configuration = Mock.Create(); + _dataRequestWire = Mock.Create(); + + Mock.Arrange(() => _configuration.SecurityPoliciesTokenExists).Returns(false); + Mock.Arrange(() => _configuration.ApplicationNames).Returns(new List { "TestApp" }); + Mock.Arrange(() => _configuration.AgentRunId).Returns("12345"); + Mock.Arrange(() => _configuration.ProcessHostDisplayName).Returns("processHostDisplayName"); + Mock.Arrange(() => _dnsStatic.GetHostName()).Returns("dnsStaticHostName"); + + _connectionHandler = new ConnectionHandler( + _serializer, + _collectorWireFactory, + _processStatic, + _dnsStatic, + _labelsService, + _environment, + _systemInfo, + _agentHealthReporter, + _environmentVariableHelper, + _dataRequestWire + ); + + _connectionHandler.OverrideConfigForTesting(_configuration); + } + + [TearDown] + public void TearDown() + { + _connectionHandler.Dispose(); + _labelsService.Dispose(); + } + + [Test] + public void Connect_ShouldSetAgentControlStatusToHealthy_OnSuccess() + { + // Arrange + var preconnectResult = new PreconnectResult { RedirectHost = "redirectHost" }; + + // create and populate a server configuration + var serverConfiguration = new ServerConfiguration + { + AgentRunId = "12345", + }; + + var collectorWire = Mock.Create(); + Mock.Arrange(() => _collectorWireFactory.GetCollectorWire(Arg.IsAny(), Arg.IsAny())) + .Returns(collectorWire); + + Mock.Arrange(() => _serializer.Serialize(Arg.IsAny())).Returns("serializedData"); + Mock.Arrange(() => _serializer.Deserialize>(Arg.IsAny())).Returns(new CollectorResponseEnvelope(preconnectResult)); + Mock.Arrange(() => _serializer.Deserialize>>(Arg.IsAny())) + .Returns(new CollectorResponseEnvelope>( + JsonConvert.DeserializeObject>( + JsonConvert.SerializeObject(serverConfiguration)))); + + // Act + _connectionHandler.Connect(); + + // Assert + Mock.Assert(() => _agentHealthReporter.SetAgentControlStatus(HealthCodes.Healthy), Occurs.Once()); + } + + [Test] + public void Connect_ShouldSetAgentControlStatusToFailedToConnect_OnFailure() + { + // Arrange + var preconnectResult = new PreconnectResult { RedirectHost = "redirectHost" }; + + var collectorWire = Mock.Create(); + Mock.Arrange(() => + _collectorWireFactory.GetCollectorWire(Arg.IsAny(), + Arg.IsAny())) + .Returns(collectorWire); + + Mock.Arrange(() => _serializer.Serialize(Arg.IsAny())).Returns("serializedData"); + Mock.Arrange( + () => _serializer.Deserialize>(Arg.IsAny())) + .Returns(new CollectorResponseEnvelope(preconnectResult)); + + Mock.Arrange(() => collectorWire.SendData("connect", Arg.IsAny(), + Arg.IsAny(), Arg.IsAny())) + .Throws(new Exception("connection failed")); + + // Act & assert + var ex = Assert.Throws(() =>_connectionHandler.Connect()); + Assert.That(ex.Message, Is.EqualTo("connection failed")); + Mock.Assert(() => _agentHealthReporter.SetAgentControlStatus(HealthCodes.FailedToConnect), Occurs.Once()); + } + + [Test] + public void SendDataRequest_ShouldSetAgentControlStatusToHttpError_OnHttpException() + { + // Arrange + Mock.Arrange(() => _dataRequestWire.SendData(Arg.IsAny(), Arg.IsAny(), + Arg.IsAny(), Arg.IsAny())) + .Throws(new DataTransport.HttpException(HttpStatusCode.InternalServerError, "Internal Server Error")); + + + // Act & Assert + var ex = Assert.Throws(() => _connectionHandler.SendDataRequest("testMethod")); + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.InternalServerError)); + + Mock.Assert(() => _agentHealthReporter.SetAgentControlStatus(HealthCodes.HttpError, "InternalServerError", "testMethod"), Occurs.Once()); + } + [Test] + public void SendDataRequest_ShouldSetAgentControlStatusToLicenseKeyInvalid_OnUnauthorized() + { + // Arrange + Mock.Arrange(() => _dataRequestWire.SendData(Arg.IsAny(), Arg.IsAny(), + Arg.IsAny(), Arg.IsAny())) + .Throws(new DataTransport.HttpException(HttpStatusCode.Unauthorized, "Unauthorized")); + + // Act & Assert + var ex = Assert.Throws(() => _connectionHandler.SendDataRequest("testMethod")); + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + + Mock.Assert(() => _agentHealthReporter.SetAgentControlStatus(HealthCodes.LicenseKeyInvalid), Occurs.Once()); + } + + [Test] + public void SendDataRequest_ShouldSetAgentControlStatusToForceDisconnect_OnGone() + { + // Arrange + Mock.Arrange(() => _dataRequestWire.SendData(Arg.IsAny(), Arg.IsAny(), Arg.IsAny(), Arg.IsAny())) + .Throws(new DataTransport.HttpException(HttpStatusCode.Gone, "Gone")); + + // Act & Assert + var ex = Assert.Throws(() => _connectionHandler.SendDataRequest("testMethod")); + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.Gone)); + + Mock.Assert(() => _agentHealthReporter.SetAgentControlStatus(HealthCodes.ForceDisconnect), Occurs.Once()); + } + + [Test] + public void SendDataRequest_ShouldSetAgentControlStatusToHttpProxyError_OnProxyAuthenticationRequired() + { + // Arrange + Mock.Arrange(() => _dataRequestWire.SendData(Arg.IsAny(), Arg.IsAny(), Arg.IsAny(), Arg.IsAny())) + .Throws(new DataTransport.HttpException(HttpStatusCode.ProxyAuthenticationRequired, "Proxy Authentication Required")); + + // Act & Assert + var ex = Assert.Throws(() => _connectionHandler.SendDataRequest("testMethod")); + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.ProxyAuthenticationRequired)); + + Mock.Assert(() => _agentHealthReporter.SetAgentControlStatus(HealthCodes.HttpProxyError, "ProxyAuthenticationRequired"), Occurs.Once()); + } + + [Test] + public void SendDataRequest_ShouldSetAgentControlStatusToHttpError_OnUnknownException() + { + // Arrange + Mock.Arrange(() => _dataRequestWire.SendData(Arg.IsAny(), Arg.IsAny(), Arg.IsAny(), Arg.IsAny())) + .Throws(new Exception("Unknown exception")); + + // Act & Assert + var ex = Assert.Throws(() => _connectionHandler.SendDataRequest("testMethod")); + Assert.That(ex.Message, Is.EqualTo("Unknown exception")); + + Mock.Assert(() => _agentHealthReporter.SetAgentControlStatus(HealthCodes.HttpError, "unknown", "testMethod"), Occurs.Once()); + } + } +} diff --git a/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ExhaustiveTestConfiguration.cs b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ExhaustiveTestConfiguration.cs index 71ae896d5e..ee09df10f4 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ExhaustiveTestConfiguration.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ExhaustiveTestConfiguration.cs @@ -499,5 +499,9 @@ public IReadOnlyDictionary GetAppSettings() public string AwsAccountId => ""; public bool GCSamplerV2Enabled => true; + + public bool AgentControlEnabled => true; + public string HealthDeliveryLocation => "file:///tmp/health"; + public int HealthFrequency => 5; } } diff --git a/tests/Agent/UnitTests/Core.UnitTest/Errors/ErrorServiceTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Errors/ErrorServiceTests.cs index 310879020b..3d24267def 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Errors/ErrorServiceTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Errors/ErrorServiceTests.cs @@ -13,6 +13,7 @@ using NewRelic.Agent.Core.SharedInterfaces.Web; using NUnit.Framework; using Telerik.JustMock; +using NewRelic.Agent.Core.AgentHealth; namespace NewRelic.Agent.Core.Errors { @@ -32,7 +33,7 @@ private class ExceptionWithTypeParameter : Exception [SetUp] public void SetUp() { - _configurationService = new ConfigurationService(Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create()); + _configurationService = new ConfigurationService(Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create(), Mock.Create()); _errorService = new ErrorService(_configurationService); _customAttributes = new Dictionary() { { "custom.key", "custom.value" } }; diff --git a/tests/Agent/UnitTests/Core.UnitTest/Metrics/MetricNamesTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Metrics/MetricNamesTests.cs index c1412d1f2b..ffcaa4bfda 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Metrics/MetricNamesTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Metrics/MetricNamesTests.cs @@ -277,6 +277,8 @@ public static void MetricNamesTest_MiscellaneousSupportability() Assert.That(MetricNames.SupportabilityHtmlPageRendered, Is.EqualTo("Supportability/RUM/HtmlPage")); Assert.That(MetricNames.SupportabilityThreadProfilingSampleCount, Is.EqualTo("Supportability/ThreadProfiling/SampleCount")); + + Assert.That(MetricNames.SupportabilityAgentControlHealthEnabled, Is.EqualTo("Supportability/AgentControl/Health/enabled")); }); } diff --git a/tests/Agent/UnitTests/Core.UnitTest/Spans/SpanEventMakerTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Spans/SpanEventMakerTests.cs index 0fd6e1e378..57e2928678 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Spans/SpanEventMakerTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Spans/SpanEventMakerTests.cs @@ -29,6 +29,7 @@ using NewRelic.Testing.Assertions; using NUnit.Framework; using Telerik.JustMock; +using NewRelic.Agent.Core.AgentHealth; namespace NewRelic.Agent.Core.Spans.UnitTest { @@ -101,7 +102,7 @@ public class SpanEventMakerTests private ServerConfiguration _serverConfig; private IBootstrapConfiguration _bootstrapConfiguration; private configuration _localConfig; - + private IAgentHealthReporter _agentHealthReporter; private void SetLocalConfigurationDefaults() { @@ -123,7 +124,7 @@ private void SetLocalConfigurationDefaults() private void PublishConfig() { - var config = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfiguration, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + var config = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfiguration, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); _config = config; EventBus.Publish(new ConfigurationUpdatedEvent(_config, ConfigurationUpdateSource.Local)); } @@ -138,7 +139,7 @@ public void SetUp() _dnsStatic = Mock.Create(); _securityPoliciesConfiguration = new SecurityPoliciesConfiguration(); _bootstrapConfiguration = Mock.Create(); - + _agentHealthReporter = Mock.Create(); _runTimeConfiguration = new RunTimeConfiguration(); _serverConfig = new ServerConfiguration(); diff --git a/tests/Agent/UnitTests/Core.UnitTest/Spans/SpanStreamingServiceTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Spans/SpanStreamingServiceTests.cs index f47cec8816..2d10a8f0b2 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Spans/SpanStreamingServiceTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Spans/SpanStreamingServiceTests.cs @@ -1106,7 +1106,7 @@ public void SupportabilityMetrics_ItemsSent_BatchSizeAndCount() actualCountSent = countSent; }); - var agentHealthReporter = new AgentHealthReporter(metricBuilder, Mock.Create()); + var agentHealthReporter = new AgentHealthReporter(metricBuilder, Mock.Create(), Mock.Create(), Mock.Create()); agentHealthReporter.RegisterPublishMetricHandler(metric => { }); diff --git a/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/TransactionAttributeMakerTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/TransactionAttributeMakerTests.cs index 3baf1bad11..a28a77ed06 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/TransactionAttributeMakerTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/TransactionAttributeMakerTests.cs @@ -31,6 +31,7 @@ using System.Collections.ObjectModel; using System.Linq; using Telerik.JustMock; +using NewRelic.Agent.Core.AgentHealth; namespace NewRelic.Agent.Core.Transformers.TransactionTransformer.UnitTest { @@ -57,12 +58,13 @@ public class TransactionAttributeMakerTests private IProcessStatic _processStatic; private IConfigurationManagerStatic _configurationManagerStatic; private IDnsStatic _dnsStatic; + private IAgentHealthReporter _agentHealthReporter; private ConfigurationAutoResponder _configAutoResponder; private void UpdateConfiguration() { - _configuration = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfiguration, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic); + _configuration = new TestableDefaultConfiguration(_environment, _localConfig, _serverConfig, _runTimeConfiguration, _securityPoliciesConfiguration, _bootstrapConfiguration, _processStatic, _httpRuntimeStatic, _configurationManagerStatic, _dnsStatic, _agentHealthReporter); Mock.Arrange(() => _configurationService.Configuration).Returns(_configuration); EventBus.Publish(new ConfigurationUpdatedEvent(_configuration, ConfigurationUpdateSource.Local)); } @@ -78,7 +80,7 @@ public void SetUp() _securityPoliciesConfiguration = new SecurityPoliciesConfiguration(); _configurationService = Mock.Create(); _bootstrapConfiguration = Mock.Create(); - + _agentHealthReporter = Mock.Create(); _runTimeConfiguration = new RunTimeConfiguration(); _serverConfig = new ServerConfiguration(); _localConfig = new configuration(); @@ -1669,8 +1671,9 @@ public object HostDisplayName_WithLocalConfigurationAndEnvironmentVariableSet(st var securityPoliciesConfiguration = new SecurityPoliciesConfiguration(); var dnsStatic = Mock.Create(); var bootstrapConfiguration = Mock.Create(); + var agentHealthReporter = Mock.Create(); - _configuration = new TestableDefaultConfiguration(environment, localConfig, serverConfig, runTimeConfig, securityPoliciesConfiguration, bootstrapConfiguration, processStatic, httpRuntimeStatic, configurationManagerStatic, dnsStatic); + _configuration = new TestableDefaultConfiguration(environment, localConfig, serverConfig, runTimeConfig, securityPoliciesConfiguration, bootstrapConfiguration, processStatic, httpRuntimeStatic, configurationManagerStatic, dnsStatic, agentHealthReporter); Mock.Arrange(() => _configurationService.Configuration).Returns(_configuration); Mock.Arrange(() => dnsStatic.GetHostName()).Returns("coconut"); diff --git a/tests/Agent/UnitTests/Core.UnitTest/Wrapper/AgentWrapperApi/Builders/DatabaseStatementParserTest.cs b/tests/Agent/UnitTests/Core.UnitTest/Wrapper/AgentWrapperApi/Builders/DatabaseStatementParserTest.cs index e0d0f3c412..6a47ea88f9 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Wrapper/AgentWrapperApi/Builders/DatabaseStatementParserTest.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Wrapper/AgentWrapperApi/Builders/DatabaseStatementParserTest.cs @@ -12,6 +12,7 @@ using System.Data; using System.Threading; using Telerik.JustMock; +using NewRelic.Agent.Core.AgentHealth; namespace NewRelic.Agent.Core.Wrapper.AgentWrapperApi.Builders { @@ -88,7 +89,8 @@ public void CacheCapacity_ChangesApplied() Mock.Create(), Mock.Create(), Mock.Create(), - Mock.Create()); + Mock.Create(), + Mock.Create()); const string sql1 = "select * from table1"; const string sql2 = "select * from table2";