Skip to content

Commit

Permalink
Feature/sdk version 2 (#99)
Browse files Browse the repository at this point in the history
* Enable stub mode for the SDK

* Add some stub data

* Set internal SDK version for easy identifying sdks

* Further refactor: Use service colleciton builder

Encapsulate the diffs for service collection.

* Add some more isolation for the debugging modules

Move debugging modules into separate namespaces so the user do not use
it by accident.
  • Loading branch information
xiaomi7732 authored Mar 13, 2018
1 parent 3a23372 commit 578f20e
Show file tree
Hide file tree
Showing 14 changed files with 253 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.ApplicationInsights.Kubernetes.Entities;

namespace Microsoft.ApplicationInsights.Kubernetes.Debugging
{
internal class K8sDebuggingEnvironmentFactory : IK8sEnvironmentFactory
{
public Task<K8sEnvironment> CreateAsync(TimeSpan timeout)
{
return Task.FromResult(new K8sEnvironment()
{
ContainerID = KubeHttpDebuggingClientSettings.FakeContainerId,
myContainerStatus = new ContainerStatus()
{
ContainerID = KubeHttpDebuggingClientSettings.FakeContainerId,
Image = nameof(ContainerStatus.Image),
ImageID = nameof(ContainerStatus.ImageID),
Name = nameof(ContainerStatus.Name),
Ready = true,
},
myDeployment = new K8sDeployment()
{
Metadata = new K8sDeploymentMetadata()
{
Labels = new Dictionary<string, string>() { { "app", "stub" } },
Name = nameof(K8sDeploymentMetadata.Name),
Uid = nameof(K8sDeploymentMetadata.Uid),
},
Spec = new K8sDeploymentSpec()
{
Selector = new Selector()
{
MatchLabels = new Dictionary<string, string>() { { "app", "stub" } },
},
},
},
myNode = new K8sNode()
{
Metadata = new K8sNodeMetadata()
{
Labels = new Dictionary<string, string>() { { "app", "stub" } },
Name = nameof(K8sNodeMetadata.Name),
Uid = nameof(K8sNodeMetadata.Uid),
},
Status = new K8sNodeStatus()
{
},
},
myPod = new K8sPod()
{
Metadata = new K8sPodMetadata()
{
Uid = "StubPodId",
Name = "StubPodName",
Labels = new Dictionary<string, string>() { { "app", "stub" } },
}
},
myReplicaSet = new K8sReplicaSet()
{
Metadata = new K8sReplicaSetMetadata()
{
Name = "StubReplicaName",
}
}
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Net.Http;

namespace Microsoft.ApplicationInsights.Kubernetes.Debugging
{
internal class KubeHttpDebuggingClientSettings : IKubeHttpClientSettingsProvider
{
public const string FakeContainerId = "F8E1C6FF-2217-4962-90FF-0D9195AF0785";

public string ContainerId => FakeContainerId;

public string QueryNamespace => "063A30B8-6A62-4519-8BFE-0DE144B009A1";

public Uri ServiceBaseAddress => new Uri("http://localhost/stub");

public HttpMessageHandler CreateMessageHandler()
{
return null;
}

public string GetToken()
{
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.ApplicationInsights.Kubernetes.Debugging
{
public sealed class KubernetesDebuggingServiceCollectionBuilder : KubernetesServiceCollectionBuilder
{
#region Singleton
private KubernetesDebuggingServiceCollectionBuilder() { }
private static KubernetesDebuggingServiceCollectionBuilder _instance = new KubernetesDebuggingServiceCollectionBuilder();

[Obsolete("This instance is used only for debugging. Never use this in production!", false)]
public static KubernetesDebuggingServiceCollectionBuilder Instance => _instance;
#endregion

protected override void InjectChangableServices(IServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<IKubeHttpClientSettingsProvider, KubeHttpDebuggingClientSettings>();
serviceCollection.AddSingleton<IK8sEnvironmentFactory, K8sDebuggingEnvironmentFactory>();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
namespace Microsoft.Extensions.DependencyInjection
using System;
using System.Threading.Tasks;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Kubernetes;

namespace Microsoft.Extensions.DependencyInjection
{
using System;
using System.Threading.Tasks;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Kubernetes;
using Microsoft.Extensions.Logging;

/// <summary>
/// Extnesion method to inject Kubernetes Telemtry Initializer.
/// </summary>
public static class ApplicationInsightsExtensions
{
public static IServiceCollection EnableKubernetes(this IServiceCollection services, TimeSpan? timeout = null)
public static IServiceCollection EnableKubernetes(this IServiceCollection services, TimeSpan? timeout = null,
IKubernetesServiceCollectionBuilder kubernetesServiceCollectionBuilder = null)
{
// Dispatch this on a differnet thread to avoid blocking the main thread.
// Mainly used with K8s Readness Probe enabled, where communicating with Server will temperory be blocked.
// TODO: Instead of query the server on the start, we should depend on watch services to provide dynamic realtime data.
Task.Run(() =>
{
KubernetesModule.EnableKubernetes(services, TelemetryConfiguration.Active, timeout);
KubernetesModule.EnableKubernetes(services, TelemetryConfiguration.Active, timeout, kubernetesServiceCollectionBuilder);
});

return services;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Microsoft.Extensions.DependencyInjection
{
public interface IKubernetesServiceCollectionBuilder
{
IServiceCollection InjectServices(IServiceCollection serviceCollection);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Microsoft.ApplicationInsights.Kubernetes;
using Microsoft.Extensions.Logging;

namespace Microsoft.Extensions.DependencyInjection
{
public class KubernetesServiceCollectionBuilder : IKubernetesServiceCollectionBuilder
{
/// <summary>
/// Inject Kubernetes related service into the service collection.
/// </summary>
/// <param name="serviceCollection"></param>
/// <returns></returns>
public IServiceCollection InjectServices(IServiceCollection serviceCollection)
{
IServiceCollection services = serviceCollection ?? new ServiceCollection();
InjectCommonServices(services);

InjectChangableServices(services);

return services;
}

private static void InjectCommonServices(IServiceCollection serviceCollection)
{
// According to the code, adding logging will not overwrite existing logging classes
// https://github.com/aspnet/Logging/blob/c821494678a30c323174bea8056f43b93a3ca6f4/src/Microsoft.Extensions.Logging/LoggingServiceCollectionExtensions.cs
// Becuase it uses 'TryAdd()' extenion method on service collection.
serviceCollection.AddLogging();

serviceCollection.AddSingleton<KubeHttpClientFactory>();
serviceCollection.AddSingleton<K8sQueryClientFactory>();
}

protected virtual void InjectChangableServices(IServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<IKubeHttpClientSettingsProvider>(p => new KubeHttpClientSettingsProvider(logger: p.GetService<ILogger<KubeHttpClientSettingsProvider>>()));
serviceCollection.AddSingleton<IK8sEnvironmentFactory, K8sEnvironmentFactory>();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;
using System.Threading.Tasks;

namespace Microsoft.ApplicationInsights.Kubernetes
{
internal interface IK8sEnvironmentFactory
{
Task<K8sEnvironment> CreateAsync(TimeSpan timeout);
}
}
3 changes: 2 additions & 1 deletion src/ApplicationInsights.Kubernetes/K8sEnvironmentFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace Microsoft.ApplicationInsights.Kubernetes
{
internal class K8sEnvironmentFactory
internal class K8sEnvironmentFactory : IK8sEnvironmentFactory
{
private readonly ILogger _logger;
private readonly IKubeHttpClientSettingsProvider _httpClientSettings;
Expand All @@ -37,6 +37,7 @@ public async Task<K8sEnvironment> CreateAsync(TimeSpan timeout)
{
K8sEnvironment instance = null;
ILogger<K8sEnvironment> logger = null;

try
{
using (IKubeHttpClient httpClient = _httpClientFactory.Create(_httpClientSettings))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Kubernetes.Utilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

Expand Down Expand Up @@ -47,12 +48,15 @@ public static void Initialize(TelemetryConfiguration configuration, TimeSpan? ti
/// </summary>
/// <param name="loggerFactory"></param>
/// <param name="timeout"></param>
public static void EnableKubernetes(IServiceCollection serviceCollection, TelemetryConfiguration configuration, TimeSpan? timeout = null)
public static void EnableKubernetes(IServiceCollection serviceCollection,
TelemetryConfiguration configuration,
TimeSpan? timeout = null,
IKubernetesServiceCollectionBuilder kubernetesServiceCollectionBuilder = null)
{
// 2 minutes maximum to spin up the container.
timeout = timeout ?? TimeSpan.FromMinutes(2);

serviceCollection = BuildK8sServiceCollection(serviceCollection);
serviceCollection = BuildK8sServiceCollection(serviceCollection, kubernetesServiceCollectionBuilder);
IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
ILogger logger = serviceProvider.GetService<ILogger<KubernetesModule>>();

Expand All @@ -71,11 +75,13 @@ public static void EnableKubernetes(IServiceCollection serviceCollection, Teleme

try
{
K8sEnvironment k8sEnv = serviceProvider.GetRequiredService<K8sEnvironmentFactory>().CreateAsync(timeout.Value).ConfigureAwait(false).GetAwaiter().GetResult();
K8sEnvironment k8sEnv = serviceProvider.GetRequiredService<IK8sEnvironmentFactory>().CreateAsync(timeout.Value).ConfigureAwait(false).GetAwaiter().GetResult();
if (k8sEnv != null)
{
// Inject the telemetry initializer.
ITelemetryInitializer initializer = new KubernetesTelemetryInitializer(k8sEnv, serviceProvider.GetService<ILogger<KubernetesTelemetryInitializer>>());
ITelemetryInitializer initializer = new KubernetesTelemetryInitializer(k8sEnv,
SDKVersionUtils.Instance,
serviceProvider.GetService<ILogger<KubernetesTelemetryInitializer>>());
configuration.TelemetryInitializers.Add(initializer);
logger?.LogDebug("Application Insights Kubernetes injected the service successfully.");
}
Expand All @@ -91,23 +97,10 @@ public static void EnableKubernetes(IServiceCollection serviceCollection, Teleme
}
}

internal static IServiceCollection BuildK8sServiceCollection(IServiceCollection original)
internal static IServiceCollection BuildK8sServiceCollection(IServiceCollection services, IKubernetesServiceCollectionBuilder kubernetesServiceCollectionBuilder = null)
{
if (Services == null || Services != original)
{
Services = original ?? new ServiceCollection();
// According github code, adding logging will not overwrite existing logging classes
// https://github.com/aspnet/Logging/blob/c821494678a30c323174bea8056f43b93a3ca6f4/src/Microsoft.Extensions.Logging/LoggingServiceCollectionExtensions.cs
// Becuase it uses 'TryAdd()' extenion method on service collection.
Services.AddLogging();

Services.AddSingleton<IKubeHttpClientSettingsProvider>(p => new KubeHttpClientSettingsProvider(logger: p.GetService<ILogger<KubeHttpClientSettingsProvider>>()));
Services.AddSingleton<KubeHttpClientFactory>();
Services.AddSingleton<K8sQueryClientFactory>();

Services.AddSingleton<K8sEnvironmentFactory>();
}

kubernetesServiceCollectionBuilder = kubernetesServiceCollectionBuilder ?? new KubernetesServiceCollectionBuilder();
Services = kubernetesServiceCollectionBuilder.InjectServices(services);
return Services;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
using Microsoft.ApplicationInsights.Kubernetes.Utilities;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

Expand Down Expand Up @@ -29,14 +31,17 @@ internal class KubernetesTelemetryInitializer : ITelemetryInitializer
public const string CPU = "CPU";
public const string Memory = "Memory";

private ILogger _logger;
private readonly ILogger _logger;
private readonly SDKVersionUtils _sdkVersionUtils;
internal IK8sEnvironment K8sEnvironment { get; private set; }

public KubernetesTelemetryInitializer(
IK8sEnvironment k8sEnv,
SDKVersionUtils sdkVersionUtils,
ILogger<KubernetesTelemetryInitializer> logger)
{
_logger = logger;
_sdkVersionUtils = Arguments.IsNotNull(sdkVersionUtils, nameof(sdkVersionUtils));
this.K8sEnvironment = Arguments.IsNotNull(k8sEnv, nameof(k8sEnv));
}

Expand All @@ -59,13 +64,13 @@ public void Initialize(ITelemetry telemetry)
#else
SetCustomDimensions(telemetry);
#endif

_logger.LogTrace(JsonConvert.SerializeObject(telemetry));
}
else
{
_logger.LogError("K8s Environemnt is null.");
}
telemetry.Context.GetInternalContext().SdkVersion = _sdkVersionUtils.CurrentSDKVersion;
}

private void SetCustomDimensions(ITelemetry telemetry)
Expand Down
38 changes: 38 additions & 0 deletions src/ApplicationInsights.Kubernetes/Utilities/SDKVersionUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Reflection;

namespace Microsoft.ApplicationInsights.Kubernetes.Utilities
{
internal sealed class SDKVersionUtils
{
#region Signleton
private SDKVersionUtils() { }
private static readonly SDKVersionUtils _instance = new SDKVersionUtils();
public static SDKVersionUtils Instance => _instance;
#endregion

public string CurrentSDKVersion
{
get
{
if (string.IsNullOrEmpty(_sdkVersion))
{
_sdkVersion = $"{SdkName}:{GetSDKVersion()}";
}
return _sdkVersion;
}
}

#region private
private static string GetSDKVersion()
{
Assembly assembly = typeof(SDKVersionUtils).GetTypeInfo().Assembly;
Version version = assembly.GetName().Version;
return version.ToString();
}

private const string SdkName = "ai-k8s";
private string _sdkVersion;
#endregion
}
}
1 change: 1 addition & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<!--Package-->
<VersionSuffix Condition=" '$(VersionSuffix)' == '' ">$([System.DateTime]::Now.ToString(yyyyMMddHHmm))</VersionSuffix>
<Version Condition=" '$(Version)' == '' ">1.0.0-private-$(VersionSuffix)</Version>
<AssemblyVersion Condition=" '$(AssemblyVersion)' == '' " >1.0.0.0</AssemblyVersion>
<Authors>Microsoft</Authors>
<Company>Microsoft</Company>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
Expand Down
Loading

0 comments on commit 578f20e

Please sign in to comment.