Skip to content

Commit 17d636f

Browse files
committed
refactor(controller): Refactoring to scoped controllers.
This closes #138. BREAKING CHANGE: Refactoring of the whole controller instantation. This removes the `ResourceControllerBase` class. Please migrate to the new interface `IResourceController`. The basic idea is to provide scoped access like asp.net api controller. ALL non abstract classes that implement that interface are registered as controllers on startup. Whenever an event for a resource happens, a dependency injection scope is created and the controller is called with the specific action. There is no need for a base class anymore.
1 parent 807e614 commit 17d636f

37 files changed

+844
-1533
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
![.NET Release](https://github.com/buehler/dotnet-operator-sdk/workflows/.NET%20Release/badge.svg)
55
![.NET Testing](https://github.com/buehler/dotnet-operator-sdk/workflows/.NET%20Testing/badge.svg)
66
[![Nuget](https://img.shields.io/nuget/v/KubeOps)](https://www.nuget.org/packages/KubeOps/)
7+
[![Nuget](https://img.shields.io/nuget/vpre/KubeOps?label=nuget%20prerelease)](https://www.nuget.org/packages/KubeOps/absoluteLatest)
78

89
This is the repository of the dotnet kubernetes operator sdk.
910

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
![.NET Release](https://github.com/buehler/dotnet-operator-sdk/workflows/.NET%20Release/badge.svg)
55
![.NET Testing](https://github.com/buehler/dotnet-operator-sdk/workflows/.NET%20Testing/badge.svg)
66
[![Nuget](https://img.shields.io/nuget/v/KubeOps)](https://www.nuget.org/packages/KubeOps/)
7+
[![Nuget](https://img.shields.io/nuget/vpre/KubeOps?label=nuget%20prerelease)](https://www.nuget.org/packages/KubeOps/absoluteLatest)
78

89
Welcome to the documentation of `KubeOps`.
910

src/KubeOps/KubeOps.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
<PackageReference Include="prometheus-net.AspNetCore" Version="4.1.1" />
3434
<PackageReference Include="prometheus-net.AspNetCore.HealthChecks" Version="4.1.1" />
3535
<PackageReference Include="SimpleBase" Version="3.0.2" />
36+
<PackageReference Include="System.Reactive" Version="5.0.0" />
3637
<PackageReference Include="YamlDotNet" Version="9.1.4" />
3738
</ItemGroup>
3839

src/KubeOps/Operator/Builder/IOperatorBuilder.cs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Reflection;
22
using k8s;
3+
using k8s.Models;
34
using KubeOps.Operator.Controller;
45
using KubeOps.Operator.Finalizer;
56
using KubeOps.Operator.Webhooks;
@@ -51,15 +52,6 @@ IOperatorBuilder AddReadinessCheck<TReadinessCheck>(string? name = default)
5152
IOperatorBuilder AddLivenessCheck<TLivenessCheck>(string? name = default)
5253
where TLivenessCheck : class, IHealthCheck;
5354

54-
/// <summary>
55-
/// Adds a resource controller for an entity of <see cref="IKubernetesObject{TMetadata}"/>.
56-
/// This controller is taking care for the reconciliation of that particular entity.
57-
/// </summary>
58-
/// <typeparam name="TController">The type of the controller to add.</typeparam>
59-
/// <returns>The builder for chaining.</returns>
60-
IOperatorBuilder AddController<TController>()
61-
where TController : class, IResourceController;
62-
6355
/// <summary>
6456
/// Adds a resource finalizer. Finalizers take care of "to be deleted" instances of
6557
/// an entity that needs to asynchronously perfrom some tasks.

src/KubeOps/Operator/Builder/OperatorBuilder.cs

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34
using System.Reflection;
45
using DotnetKubernetesClient;
6+
using k8s;
7+
using k8s.Models;
58
using KubeOps.Operator.Caching;
69
using KubeOps.Operator.Controller;
710
using KubeOps.Operator.DevOps;
811
using KubeOps.Operator.Events;
912
using KubeOps.Operator.Finalizer;
13+
using KubeOps.Operator.Kubernetes;
1014
using KubeOps.Operator.Leadership;
11-
using KubeOps.Operator.Queue;
1215
using KubeOps.Operator.Serialization;
1316
using KubeOps.Operator.Services;
14-
using KubeOps.Operator.Watcher;
1517
using KubeOps.Operator.Webhooks;
1618
using Microsoft.Extensions.DependencyInjection;
19+
using Microsoft.Extensions.DependencyInjection.Extensions;
1720
using Microsoft.Extensions.Diagnostics.HealthChecks;
1821
using Microsoft.Rest.Serialization;
1922
using Newtonsoft.Json;
@@ -28,19 +31,18 @@ internal class OperatorBuilder : IOperatorBuilder
2831
internal const string LivenessTag = "liveness";
2932
internal const string ReadinessTag = "readiness";
3033

34+
internal static readonly Assembly[] Assemblies =
35+
{
36+
Assembly.GetEntryAssembly() ?? throw new Exception("No Entry Assembly found."),
37+
Assembly.GetExecutingAssembly(),
38+
};
39+
3140
private readonly IResourceTypeService _resourceTypeService;
3241

3342
public OperatorBuilder(IServiceCollection services)
3443
{
3544
Services = services;
36-
37-
var entryAssembly = Assembly.GetEntryAssembly();
38-
if (entryAssembly == null)
39-
{
40-
throw new Exception("No Entry Assembly found.");
41-
}
42-
43-
_resourceTypeService = new ResourceTypeService(entryAssembly, Assembly.GetExecutingAssembly());
45+
_resourceTypeService = new ResourceTypeService(Assemblies);
4446
}
4547

4648
public IServiceCollection Services { get; }
@@ -81,14 +83,6 @@ public IOperatorBuilder AddLivenessCheck<TLivenessCheck>(string? name = default)
8183
return this;
8284
}
8385

84-
public IOperatorBuilder AddController<TController>()
85-
where TController : class, IResourceController
86-
{
87-
Services.AddHostedService<TController>();
88-
89-
return this;
90-
}
91-
9286
public IOperatorBuilder AddFinalizer<TFinalizer>()
9387
where TFinalizer : class, IResourceFinalizer
9488
{
@@ -113,13 +107,24 @@ public IOperatorBuilder AddValidationWebhook<TWebhook>()
113107
return this;
114108
}
115109

110+
internal static IEnumerable<(Type ControllerType, Type EntityType)> GetControllers() => Assemblies
111+
.SelectMany(a => a.GetTypes())
112+
.Where(
113+
t => t.IsClass &&
114+
!t.IsAbstract &&
115+
t.GetInterfaces()
116+
.Any(
117+
i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IResourceController<>)))
118+
.Select(
119+
t => (t,
120+
t.GetInterfaces()
121+
.First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IResourceController<>))
122+
.GenericTypeArguments[0]));
123+
116124
internal IOperatorBuilder AddOperatorBase(OperatorSettings settings)
117125
{
118126
Services.AddSingleton(settings);
119127

120-
// support lazy service resolution
121-
Services.AddTransient(typeof(Lazy<>), typeof(LazyService<>));
122-
123128
var jsonSettings = new JsonSerializerSettings
124129
{
125130
DateFormatHandling = DateFormatHandling.IsoDateFormat,
@@ -141,33 +146,43 @@ internal IOperatorBuilder AddOperatorBase(OperatorSettings settings)
141146
_ => new SerializerBuilder()
142147
.ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull)
143148
.WithNamingConvention(new NamingConvention())
144-
.WithTypeConverter(new YamlIntOrStrTypeConverter())
145-
.WithTypeConverter(new YamlByteArrayTypeConverter())
149+
.WithTypeConverter(new Yaml.ByteArrayStringYamlConverter())
150+
.WithTypeConverter(new IntOrStringYamlConverter())
146151
.Build());
147152

148153
Services.AddTransient<EntitySerializer>();
149154

150155
Services.AddTransient<IKubernetesClient, KubernetesClient>();
151156
Services.AddTransient<IEventManager, EventManager>();
152157

153-
Services.AddSingleton(typeof(IResourceCache<>), typeof(ResourceCache<>));
154-
Services.AddTransient(typeof(IResourceWatcher<>), typeof(ResourceWatcher<>));
155-
Services.AddTransient(typeof(IResourceEventQueue<>), typeof(ResourceEventQueue<>));
156-
Services.AddTransient(typeof(IResourceServices<>), typeof(ResourceServices<>));
158+
Services.AddTransient(typeof(ResourceCache<>));
159+
Services.AddTransient(typeof(ResourceWatcher<>));
160+
Services.AddTransient(typeof(ManagedResourceController<>));
161+
162+
// Support all the metrics
163+
Services.AddSingleton(typeof(ResourceWatcherMetrics<>));
164+
Services.AddSingleton(typeof(ResourceCacheMetrics<>));
165+
Services.AddSingleton(typeof(ResourceControllerMetrics<>));
157166

158167
// Support for healthchecks and prometheus.
159168
Services
160169
.AddHealthChecks()
161170
.ForwardToPrometheus();
162171

163-
// Add the default controller liveness check.
164-
AddHealthCheck<ControllerLivenessCheck>();
165-
166172
// Support for leader election via V1Leases.
167173
Services.AddHostedService<LeaderElector>();
168174
Services.AddSingleton<ILeaderElection, LeaderElection>();
169175

170176
Services.AddSingleton(_resourceTypeService);
177+
Services.AddHostedService<ResourceControllerManager>();
178+
179+
// Add the service provider (for instantiation)
180+
// and all found controller types.
181+
Services.TryAddSingleton(sp => sp);
182+
foreach (var (controllerType, _) in GetControllers())
183+
{
184+
Services.TryAddScoped(controllerType);
185+
}
171186

172187
return this;
173188
}

src/KubeOps/Operator/Caching/CacheComparisonResult.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
namespace KubeOps.Operator.Caching
22
{
33
/// <summary>
4-
/// Result for the <see cref="IResourceCache{TEntity}.Upsert"/> when comparison is done.
4+
/// Result for the <see cref="ResourceCache{TEntity}.Upsert"/> when comparison is done.
55
/// </summary>
6-
public enum CacheComparisonResult
6+
internal enum CacheComparisonResult
77
{
88
/// <summary>
99
/// The resource is new to the cache

src/KubeOps/Operator/Caching/IResourceCache.cs

Lines changed: 0 additions & 53 deletions
This file was deleted.

src/KubeOps/Operator/Caching/ResourceCache.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
namespace KubeOps.Operator.Caching
1111
{
12-
internal class ResourceCache<TEntity> : IResourceCache<TEntity>
12+
internal class ResourceCache<TEntity>
1313
where TEntity : IKubernetesObject<V1ObjectMeta>
1414
{
1515
private const string ResourceVersion = "ResourceVersion";
@@ -29,9 +29,9 @@ internal class ResourceCache<TEntity> : IResourceCache<TEntity>
2929

3030
private readonly ResourceCacheMetrics<TEntity> _metrics;
3131

32-
public ResourceCache(OperatorSettings settings)
32+
public ResourceCache(ResourceCacheMetrics<TEntity> metrics)
3333
{
34-
_metrics = new(settings);
34+
_metrics = metrics;
3535
}
3636

3737
public TEntity Get(string id) => _cache[id];
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
4+
namespace KubeOps.Operator.Controller
5+
{
6+
internal interface IManagedResourceController : IDisposable
7+
{
8+
Type ControllerType { get; set; }
9+
10+
Task Start();
11+
12+
Task Stop();
13+
}
14+
}
Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,73 @@
1-
using k8s;
1+
using System.Threading.Tasks;
2+
using k8s;
23
using k8s.Models;
3-
using Microsoft.Extensions.Hosting;
4+
using KubeOps.Operator.Controller.Results;
5+
using KubeOps.Operator.Kubernetes;
46

57
namespace KubeOps.Operator.Controller
68
{
7-
/// <summary>
8-
/// Resource controller interface.
9-
/// This interface is primarily used for generic type help.
10-
/// </summary>
11-
public interface IResourceController : IHostedService
12-
{
13-
internal bool Running { get; }
14-
}
15-
169
/// <summary>
1710
/// Generic resource controller interface.
1811
/// This interface is primarily used for generic type help.
1912
/// </summary>
20-
/// <typeparam name="TEntity">The type of the entity.</typeparam>
21-
public interface IResourceController<TEntity> : IResourceController
22-
where TEntity : IKubernetesObject<V1ObjectMeta>
13+
/// <typeparam name="TResource">The type of the kubernetes resource.</typeparam>
14+
public interface IResourceController<in TResource>
15+
where TResource : IKubernetesObject<V1ObjectMeta>
2316
{
17+
/// <summary>
18+
/// Called for <see cref="ResourceEventType.Created"/> events for a given resource.
19+
/// </summary>
20+
/// <param name="resource">The resource that fired the created event.</param>
21+
/// <returns>
22+
/// A task with an optional <see cref="ResourceControllerResult"/>.
23+
/// Use the static constructors on the <see cref="ResourceControllerResult"/> class
24+
/// to create your controller function result.
25+
/// </returns>
26+
Task<ResourceControllerResult?> CreatedAsync(TResource resource) =>
27+
Task.FromResult<ResourceControllerResult?>(null);
28+
29+
/// <summary>
30+
/// Called for <see cref="ResourceEventType.Updated"/> events for a given resource.
31+
/// </summary>
32+
/// <param name="resource">The resource that fired the updated event.</param>
33+
/// <returns>
34+
/// A task with an optional <see cref="ResourceControllerResult"/>.
35+
/// Use the static constructors on the <see cref="ResourceControllerResult"/> class
36+
/// to create your controller function result.
37+
/// </returns>
38+
Task<ResourceControllerResult?> UpdatedAsync(TResource resource) =>
39+
Task.FromResult<ResourceControllerResult?>(null);
40+
41+
/// <summary>
42+
/// Called for <see cref="ResourceEventType.NotModified"/> events for a given resource.
43+
/// </summary>
44+
/// <param name="resource">The resource that fired the not-modified event.</param>
45+
/// <returns>
46+
/// A task with an optional <see cref="ResourceControllerResult"/>.
47+
/// Use the static constructors on the <see cref="ResourceControllerResult"/> class
48+
/// to create your controller function result.
49+
/// </returns>
50+
Task<ResourceControllerResult?> NotModifiedAsync(TResource resource) =>
51+
Task.FromResult<ResourceControllerResult?>(null);
52+
53+
/// <summary>
54+
/// Called for <see cref="ResourceEventType.StatusUpdated"/> events for a given resource.
55+
/// </summary>
56+
/// <param name="resource">The resource that fired the status-modified event.</param>
57+
/// <returns>
58+
/// A task that completes, when the reconciliation is done.
59+
/// </returns>
60+
Task StatusModifiedAsync(TResource resource) =>
61+
Task.CompletedTask;
62+
63+
/// <summary>
64+
/// Called for <see cref="ResourceEventType.Deleted"/> events for a given resource.
65+
/// </summary>
66+
/// <param name="resource">The resource that fired the deleted event.</param>
67+
/// <returns>
68+
/// A task that completes, when the reconciliation is done.
69+
/// </returns>
70+
Task DeletedAsync(TResource resource) =>
71+
Task.CompletedTask;
2472
}
2573
}

0 commit comments

Comments
 (0)