Skip to content

Commit 6438064

Browse files
authored
feat: add preload cache mechanism via settings (#114)
This closes #58. Note that preload is done on leader election as well as "each start" of the specific controller. This could lead to "not catched" resources.
1 parent 8f6fc84 commit 6438064

File tree

6 files changed

+76
-15
lines changed

6 files changed

+76
-15
lines changed

docs/docs/commands.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Here is a brief overview over the available commands:
2323
> all commands assume either the compiled dll or you using
2424
> `dotnet run -- ` as prepended command.
2525
26-
- ` ` (empty): runs the operator (normal `dotnet run`)
26+
- `""` (empty): runs the operator (normal `dotnet run`)
2727
- `version`: prints the version information for the actual connected kubernetes cluster
2828
- `install`: install the CRDs for the solution into the cluster
2929
- `uninstall`: uninstall the CRDs for the solution from the cluster

src/KubeOps/Operator/Builder/OperatorBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ internal IOperatorBuilder AddOperatorBase(OperatorSettings settings)
137137

138138
Services.AddTransient<IKubernetesClient, KubernetesClient>();
139139

140-
Services.AddTransient(typeof(IResourceCache<>), typeof(ResourceCache<>));
140+
Services.AddSingleton(typeof(IResourceCache<>), typeof(ResourceCache<>));
141141
Services.AddTransient(typeof(IResourceWatcher<>), typeof(ResourceWatcher<>));
142142
Services.AddTransient(typeof(IResourceEventQueue<>), typeof(ResourceEventQueue<>));
143143
Services.AddTransient(typeof(IResourceServices<>), typeof(ResourceServices<>));
Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,53 @@
1-
using k8s;
1+
using System.Collections.Generic;
2+
using k8s;
23
using k8s.Models;
4+
using KubeOps.Operator.Queue;
35

46
namespace KubeOps.Operator.Caching
57
{
8+
/// <summary>
9+
/// Resource cache for comparing objects and determine
10+
/// a <see cref="CacheComparisonResult"/>. This result
11+
/// is used to determine the event type for the <see cref="ResourceEventQueue{TEntity}"/>.
12+
/// </summary>
13+
/// <typeparam name="TEntity">The type of objects that are cached.</typeparam>
614
public interface IResourceCache<TEntity>
715
where TEntity : IKubernetesObject<V1ObjectMeta>
816
{
17+
/// <summary>
18+
/// Return an object from the cache.
19+
/// </summary>
20+
/// <param name="id">String id of the object.</param>
21+
/// <returns>The found entity.</returns>
22+
/// <exception cref="KeyNotFoundException">When the item does not exist.</exception>
923
TEntity Get(string id);
1024

25+
/// <summary>
26+
/// Insert or Update a given resource in the cache and determine the
27+
/// <see cref="CacheComparisonResult"/>.
28+
/// </summary>
29+
/// <param name="resource">The resource in question.</param>
30+
/// <param name="result"><see cref="CacheComparisonResult"/> for the given resource.</param>
31+
/// <returns>The inserted resource.</returns>
1132
TEntity Upsert(TEntity resource, out CacheComparisonResult result);
1233

34+
/// <summary>
35+
/// Prefill the cache with a list of entities.
36+
/// This does not delete other items in the cache.
37+
/// </summary>
38+
/// <param name="entities">List of entities.</param>
39+
void Fill(IEnumerable<TEntity> entities);
40+
41+
/// <summary>
42+
/// Remove an entity from the cache.
43+
/// If the resource is not present, this turns to a no-op.
44+
/// </summary>
45+
/// <param name="resource">The resource in question.</param>
1346
void Remove(TEntity resource);
1447

48+
/// <summary>
49+
/// Clear the whole cache.
50+
/// </summary>
1551
public void Clear();
1652
}
1753
}

src/KubeOps/Operator/Caching/ResourceCache.cs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,20 @@ internal class ResourceCache<TEntity> : IResourceCache<TEntity>
1818
private const string Status = "Status";
1919

2020
private readonly CompareLogic _compare = new(
21-
new ComparisonConfig
21+
new()
2222
{
2323
Caching = true,
2424
AutoClearCache = false,
25-
MembersToIgnore = new List<string> { ResourceVersion, ManagedFields },
25+
MembersToIgnore = new() { ResourceVersion, ManagedFields },
2626
});
2727

28-
private readonly IDictionary<string, TEntity> _cache = new ConcurrentDictionary<string, TEntity>();
28+
private readonly ConcurrentDictionary<string, TEntity> _cache = new();
2929

3030
private readonly ResourceCacheMetrics<TEntity> _metrics;
3131

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

3737
public TEntity Get(string id) => _cache[id];
@@ -40,18 +40,24 @@ public TEntity Upsert(TEntity resource, out CacheComparisonResult result)
4040
{
4141
result = CompareCache(resource);
4242

43-
if (result == CacheComparisonResult.New)
44-
{
45-
_cache.Add(resource.Metadata.Uid, resource.DeepClone());
46-
}
47-
else
43+
var clone = resource.DeepClone();
44+
_cache.AddOrUpdate(resource.Metadata.Uid, clone, (_, _) => clone);
45+
46+
_metrics.CachedItemsSize.Set(_cache.Count);
47+
_metrics.CachedItemsSummary.Observe(_cache.Count);
48+
return resource;
49+
}
50+
51+
public void Fill(IEnumerable<TEntity> entities)
52+
{
53+
foreach (var entity in entities)
4854
{
49-
_cache[resource.Metadata.Uid] = resource.DeepClone();
55+
var clone = entity.DeepClone();
56+
_cache.AddOrUpdate(entity.Metadata.Uid, clone, (_, _) => clone);
5057
}
5158

5259
_metrics.CachedItemsSize.Set(_cache.Count);
5360
_metrics.CachedItemsSummary.Observe(_cache.Count);
54-
return resource;
5561
}
5662

5763
public void Remove(TEntity resource) => Remove(resource.Metadata.Uid);
@@ -94,7 +100,7 @@ private CacheComparisonResult CompareCache(TEntity resource)
94100

95101
private void Remove(string resourceUid)
96102
{
97-
_cache.Remove(resourceUid);
103+
_cache.TryRemove(resourceUid, out _);
98104
_metrics.CachedItemsSize.Set(_cache.Count);
99105
_metrics.CachedItemsSummary.Observe(_cache.Count);
100106
}

src/KubeOps/Operator/Controller/ResourceControllerBase.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,14 @@ private async void LeadershipChanged(object? sender, LeaderState state)
130130
if (state == LeaderState.Leader)
131131
{
132132
_logger.LogInformation("This instance was elected as leader, starting event queue.");
133+
134+
if (_services.Settings.PreloadCache)
135+
{
136+
_logger.LogInformation("The 'preload cache' setting is set to 'true'.");
137+
var items = await _services.Client.List<TEntity>(_services.Settings.Namespace);
138+
_services.ResourceCache.Fill(items);
139+
}
140+
133141
_services.EventQueue.ResourceEvent += OnResourceEvent;
134142
await _services.EventQueue.Start();
135143

src/KubeOps/Operator/OperatorSettings.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,16 @@ public sealed class OperatorSettings
6262
/// The timeout in seconds which the watcher has (after this timeout, the server will close the connection).
6363
/// </summary>
6464
public ushort WatcherHttpTimeout { get; set; } = 60;
65+
66+
/// <summary>
67+
/// If set to true, controllers perform a search for already
68+
/// existing objects in the cluster and load them into the objects cache.
69+
///
70+
/// This bears the risk of not catching elements when they are created
71+
/// during downtime of the operator.
72+
///
73+
/// The search will be performed on each "Start" of the controller.
74+
/// </summary>
75+
public bool PreloadCache { get; set; }
6576
}
6677
}

0 commit comments

Comments
 (0)