Skip to content

Commit

Permalink
[k8s][EdgeAgent] Planners and commands for Kubernetes (#1442)
Browse files Browse the repository at this point in the history
* Pulled code in from POC Planners, commands and other useful classes going from edge deployment to CRD.
* Cleaned up and added tests.

* Command Factory, ImagePullSecret and CombinedConfig tests
  • Loading branch information
darobs authored Jul 26, 2019
1 parent 96bbda2 commit b96e83a
Show file tree
Hide file tree
Showing 14 changed files with 1,387 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright (c) Microsoft. All rights reserved.

namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using global::Docker.DotNet.Models;
using Microsoft.Azure.Devices.Edge.Agent.Core;
using Microsoft.Azure.Devices.Edge.Agent.Docker;
using Microsoft.Azure.Devices.Edge.Util;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;

public class CombinedKubernetesConfigProvider : CombinedDockerConfigProvider
{
readonly IConfigSource configSource;

public CombinedKubernetesConfigProvider(IEnumerable<AuthConfig> authConfigs, IConfigSource configSource)
: base(authConfigs)
{
this.configSource = Preconditions.CheckNotNull(configSource, nameof(configSource));
}

static CreateContainerParameters CloneOrCreateParams(CreateContainerParameters createOptions) =>
createOptions != null
? JsonConvert.DeserializeObject<CreateContainerParameters>(JsonConvert.SerializeObject(createOptions))
: new CreateContainerParameters();

public override CombinedDockerConfig GetCombinedConfig(IModule module, IRuntimeInfo runtimeInfo)
{
CombinedDockerConfig combinedConfig = base.GetCombinedConfig(module, runtimeInfo);

// if the workload URI is a Unix domain socket then volume mount it into the container
CreateContainerParameters createOptions = CloneOrCreateParams(combinedConfig.CreateOptions);
this.MountSockets(module, createOptions);
this.InjectNetworkAliases(module, createOptions);

return new CombinedDockerConfig(combinedConfig.Image, createOptions, combinedConfig.AuthConfig);
}

void InjectNetworkAliases(IModule module, CreateContainerParameters createOptions)
{
if (createOptions.NetworkingConfig?.EndpointsConfig == null)
{
string networkId = this.configSource.Configuration.GetValue<string>(Core.Constants.NetworkIdKey);
string edgeDeviceHostName = this.configSource.Configuration.GetValue<string>(Core.Constants.EdgeDeviceHostNameKey);

if (!string.IsNullOrWhiteSpace(networkId))
{
var endpointSettings = new EndpointSettings();
if (module.Name.Equals(Core.Constants.EdgeHubModuleName, StringComparison.OrdinalIgnoreCase)
&& !string.IsNullOrWhiteSpace(edgeDeviceHostName))
{
endpointSettings.Aliases = new List<string>
{
edgeDeviceHostName
};
}

IDictionary<string, EndpointSettings> endpointsConfig = new Dictionary<string, EndpointSettings>
{
[networkId] = endpointSettings
};
createOptions.NetworkingConfig = new NetworkingConfig
{
EndpointsConfig = endpointsConfig
};
}
}
}

void MountSockets(IModule module, CreateContainerParameters createOptions)
{
var workloadUri = new Uri(this.configSource.Configuration.GetValue<string>(Core.Constants.EdgeletWorkloadUriVariableName));
if (string.Equals(workloadUri.Scheme, "unix", StringComparison.OrdinalIgnoreCase))
{
SetMountOptions(createOptions, workloadUri);
}

// If Management URI is Unix domain socket, and the module is the EdgeAgent, then mount it ino the container.
var managementUri = new Uri(this.configSource.Configuration.GetValue<string>(Core.Constants.EdgeletManagementUriVariableName));
if (string.Equals(managementUri.Scheme, "unix", StringComparison.OrdinalIgnoreCase)
&& module.Name.Equals(Core.Constants.EdgeAgentModuleName, StringComparison.OrdinalIgnoreCase))
{
SetMountOptions(createOptions, managementUri);
}
}

static void SetMountOptions(CreateContainerParameters createOptions, Uri uri)
{
HostConfig hostConfig = createOptions.HostConfig ?? new HostConfig();
IList<string> binds = hostConfig.Binds ?? new List<string>();
string path = BindPath(uri);
binds.Add($"{path}:{path}");

hostConfig.Binds = binds;
createOptions.HostConfig = hostConfig;
}

static string BindPath(Uri uri)
{
// On Windows we need to bind to the parent folder. We can't bind
// directly to the socket file.
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? Path.GetDirectoryName(uri.LocalPath)
: uri.AbsolutePath;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes
using Microsoft.Azure.Devices.Edge.Util;
using Newtonsoft.Json;

class EdgeDeploymentDefinition<TConfig> : IEdgeDeploymentDefinition<TConfig>
public class EdgeDeploymentDefinition<TConfig> : IEdgeDeploymentDefinition<TConfig>
{
[JsonProperty(PropertyName = "apiVersion")]
public string ApiVersion { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) Microsoft. All rights reserved.

namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes
{
using System;
using System.Collections.Generic;
using System.Text;
using global::Docker.DotNet.Models;
using Newtonsoft.Json;

public class ImagePullSecret
{
class AuthEntry
{
[JsonProperty(Required = Required.Always, PropertyName = "username")]
public readonly string Username;
[JsonProperty(Required = Required.Always, PropertyName = "password")]
public readonly string Password;
[JsonProperty(Required = Required.Always, PropertyName = "auth")]
public readonly string Auth;

public AuthEntry(string username, string password)
{
this.Username = username;
this.Password = password;
byte[] auth = Encoding.UTF8.GetBytes($"{username}:{password}");
this.Auth = Convert.ToBase64String(auth);
}
}

class Auth
{
[JsonProperty(Required = Required.Always, PropertyName = "auths")]
public Dictionary<string, AuthEntry> Auths;

public Auth()
{
this.Auths = new Dictionary<string, AuthEntry>();
}

public Auth(string registry, AuthEntry entry)
: this()
{
this.Auths.Add(registry, entry);
}
}

public string Name { get; }

readonly AuthConfig dockerAuth;

public string GenerateSecret()
{
// JSON struct is
// { "auths":
// { "<registry>" :
// { "username":"<user>",
// "password":"<password>",
// "email":"<email>" (not needed)
// "auth":"<base 64 of '<user>:<password>'>"
// }
// }
// }
var auths = new Auth(
this.dockerAuth.ServerAddress,
new AuthEntry(this.dockerAuth.Username, this.dockerAuth.Password));
string authString = JsonConvert.SerializeObject(auths);
return authString;
}

public ImagePullSecret(AuthConfig dockerAuth)
{
this.dockerAuth = dockerAuth;
this.Name = $"{dockerAuth.Username.ToLower()}-{dockerAuth.ServerAddress.ToLower()}";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft. All rights reserved.
namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes
{
using System;

[Serializable]
public class InvalidModuleException : Exception
{
public InvalidModuleException(string message)
: base(message)
{
}

public InvalidModuleException(string message, Exception inner)
: base(message, inner)
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft. All rights reserved.

namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes
{
using System.Threading.Tasks;
using Microsoft.Azure.Devices.Edge.Agent.Core;
using Microsoft.Azure.Devices.Edge.Agent.Core.Commands;

public class KubernetesCommandFactory : ICommandFactory
{
public KubernetesCommandFactory()
{
}

public Task<ICommand> UpdateEdgeAgentAsync(IModuleWithIdentity module, IRuntimeInfo runtimeInfo) => Task.FromResult(NullCommand.Instance as ICommand);

public Task<ICommand> CreateAsync(IModuleWithIdentity module, IRuntimeInfo runtimeInfo) =>
Task.FromResult((ICommand)NullCommand.Instance);

public Task<ICommand> UpdateAsync(IModule current, IModuleWithIdentity next, IRuntimeInfo runtimeInfo) =>
Task.FromResult((ICommand)NullCommand.Instance);

public Task<ICommand> RemoveAsync(IModule module) =>
Task.FromResult((ICommand)NullCommand.Instance);

public Task<ICommand> StartAsync(IModule module) =>
Task.FromResult((ICommand)NullCommand.Instance);

public Task<ICommand> StopAsync(IModule module) =>
Task.FromResult((ICommand)NullCommand.Instance);

public Task<ICommand> RestartAsync(IModule module) =>
Task.FromResult((ICommand)NullCommand.Instance);

public Task<ICommand> WrapAsync(ICommand command) => Task.FromResult(command);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.

namespace Microsoft.Azure.Devices.Edge.Agent.Core
namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes
{
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.Azure.Devices.Edge.Agent.Core;
using Microsoft.Azure.Devices.Edge.Agent.Docker;
using Microsoft.Azure.Devices.Edge.Util;

Expand Down Expand Up @@ -39,7 +40,7 @@ public KubernetesModule(IModule<TConfig> module)

public ImagePullPolicy ImagePullPolicy { get; set; }

public TConfig Config { get; }
public TConfig Config { get; set; }

public virtual bool Equals(IModule other) => this.Equals(other as KubernetesModule<TConfig>);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.

namespace Microsoft.Azure.Devices.Edge.Agent.Core
namespace Microsoft.Azure.Devices.Edge.Agent.Kubernetes
{
using Microsoft.Azure.Devices.Edge.Agent.Core;
using Microsoft.Azure.Devices.Edge.Util;

public class KubernetesModuleIdentity
Expand Down
Loading

0 comments on commit b96e83a

Please sign in to comment.