public bool TryLookup(NamespacedName key, out ReconcileData data) { var endspointsList = new List <Endpoints>(); lock (_sync) { if (!_ingressData.TryGetValue(key.Name, out var ingress)) { ingress = default; } if (_ingressToServiceNames.TryGetValue(key.Name, out var serviceNames)) { foreach (var serviceName in serviceNames) { if (_endpointsData.TryGetValue(serviceName, out var serviceData)) { endspointsList.Add(serviceData); } } } data = new ReconcileData(ingress, endspointsList); return(true); } }
/// <inheritdoc/> public bool TryGetWorkItem(NamespacedName namespacedName, out OperatorCacheWorkItem <TResource> workItem) { lock (_workItemsSync) { return(_workItems.TryGetValue(namespacedName, out workItem)); } }
/// <inheritdoc/> public void UpdateWorkItem(NamespacedName namespacedName, UpdateWorkItem <TResource> update) { if (update is null) { throw new ArgumentNullException(nameof(update)); } lock (_workItemsSync) { if (_workItems.TryGetValue(namespacedName, out var workItem)) { // alter an existing entry workItem = update(workItem); if (workItem.IsEmpty) { // remove if result has no information _workItems.Remove(namespacedName); } else { // otherwise update struct in dictionary _workItems[namespacedName] = workItem; } } else { workItem = update(OperatorCacheWorkItem <TResource> .Empty); if (workItem.IsEmpty == false) { // add if result has information _workItems.Add(namespacedName, workItem); } } } }
public void EqualityAndInequality( string namespace1, string name1, string namespace2, string name2, bool shouldBeEqual) { var namespacedName1 = new NamespacedName(namespace1, name1); var namespacedName2 = new NamespacedName(namespace2, name2); var areEqual = namespacedName1 == namespacedName2; var areNotEqual = namespacedName1 != namespacedName2; #pragma warning disable CS1718 // Comparison made to same variable var sameEqual1 = namespacedName1 == namespacedName1; var sameNotEqual1 = namespacedName1 != namespacedName1; var sameEqual2 = namespacedName2 == namespacedName2; var sameNotEqual2 = namespacedName2 != namespacedName2; #pragma warning restore CS1718 // Comparison made to same variable Assert.NotEqual(areNotEqual, areEqual); Assert.Equal(shouldBeEqual, areEqual); Assert.True(sameEqual1); Assert.False(sameNotEqual1); Assert.True(sameEqual2); Assert.False(sameNotEqual2); }
public async Task ProcessAsync(IDispatchTarget target, NamespacedName key, ReconcileData data, CancellationToken cancellationToken) { try { var message = new Message { MessageType = MessageType.Update, Key = $"{key.Namespace}:{key.Name}" }; var context = new YarpIngressContext(data.Ingress, data.ServiceList, data.EndpointsList); YarpParser.CovertFromKubernetesIngress(context); message.Cluster = context.Clusters; message.Routes = context.Routes; var bytes = JsonSerializer.SerializeToUtf8Bytes(message); _logger.LogInformation(JsonSerializer.Serialize(message)); await _dispatcher.SendAsync(target, bytes, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { _logger.LogWarning(ex.Message); throw; } }
public void JustNameFromClusterResource() { var resource = new V1ClusterRole( apiVersion: V1ClusterRole.KubeApiVersion, kind: V1ClusterRole.KubeKind, metadata: new V1ObjectMeta( name: "the-name")); var nn = NamespacedName.From(resource); Assert.Equal("the-name", nn.Name); Assert.Null(nn.Namespace); }
private void OnPrimaryResourceWatchEvent(WatchEventType watchEventType, TResource resource) { var key = NamespacedName.From(resource); _cache.UpdateWorkItem(key, workItem => { if (watchEventType == WatchEventType.Added || watchEventType == WatchEventType.Modified) { workItem = workItem.SetResource(resource); } else if (watchEventType == WatchEventType.Deleted) { workItem = workItem.SetResource(default); }
public void NamespaceAndNameFromResource() { var resource = new V1ConfigMap( apiVersion: V1ConfigMap.KubeApiVersion, kind: V1ConfigMap.KubeKind, metadata: new V1ObjectMeta( name: "the-name", namespaceProperty: "the-namespace")); var nn = NamespacedName.From(resource); Assert.Equal("the-name", nn.Name); Assert.Equal("the-namespace", nn.Namespace); }
public void WorksAsDictionaryKey() { var dictionary = new Dictionary <NamespacedName, string>(); var name1 = new NamespacedName("ns", "n1"); var name2 = new NamespacedName("ns", "n2"); var name3 = new NamespacedName("ns", "n3"); dictionary[name1] = "one"; dictionary[name1] = "one again"; dictionary[name2] = "two"; Assert.Contains(new KeyValuePair <NamespacedName, string>(name1, "one again"), dictionary); Assert.Contains(new KeyValuePair <NamespacedName, string>(name2, "two"), dictionary); Assert.DoesNotContain(name3, dictionary.Keys); }
private void OnEvent(WatchEventType watchEventType, TResource item) { if (watchEventType != WatchEventType.Modified || item.Kind != "ConfigMap") { Logger.LogDebug( EventId(EventType.InformerWatchEvent), "Informer {ResourceType} received {WatchEventType} notification for {ItemKind}/{ItemName}.{ItemNamespace} at resource version {ResourceVersion}", typeof(TResource).Name, watchEventType, item.Kind, item.Name(), item.Namespace(), item.ResourceVersion()); } if (watchEventType == WatchEventType.Added || watchEventType == WatchEventType.Modified) { // BUGBUG: log warning if cache was not in expected state _cache[NamespacedName.From(item)] = item.Metadata?.OwnerReferences; } if (watchEventType == WatchEventType.Deleted) { _cache.Remove(NamespacedName.From(item)); } if (watchEventType == WatchEventType.Added || watchEventType == WatchEventType.Modified || watchEventType == WatchEventType.Deleted || watchEventType == WatchEventType.Bookmark) { _lastResourceVersion = item.ResourceVersion(); } if (watchEventType == WatchEventType.Added || watchEventType == WatchEventType.Modified || watchEventType == WatchEventType.Deleted) { InvokeRegistrationCallbacks(watchEventType, item); } }
public void NotifyWithPrimaryResourceCausesCacheEntryAndQueueItem() { var generator = Mock.Of <IOperatorGenerator <TypicalResource> >(); var typicalInformer = new FakeResourceInformer <TypicalResource>(); var podInformer = new FakeResourceInformer <V1Pod>(); var addCalls = new List <NamespacedName>(); var queue = new FakeQueue <NamespacedName> { OnAdd = addCalls.Add, }; var cache = new OperatorCache <TypicalResource>(); using var host = new HostBuilder() .ConfigureServices(services => { services .AddLogging() .AddKubernetesOperatorRuntime() .AddOperator <TypicalResource>(op => { op.WithRelatedResource <V1Pod>(); op.Configure(options => options.NewRateLimitingQueue = _ => queue); }) .AddSingleton(generator) .AddSingleton <IResourceInformer <TypicalResource> >(typicalInformer) .AddSingleton <IResourceInformer <V1Pod> >(podInformer) .AddSingleton <IOperatorCache <TypicalResource> >(cache); }) .Build(); var handler = host.Services.GetRequiredService <IOperatorHandler <TypicalResource> >(); var typical = new TypicalResource { ApiVersion = $"{TypicalResource.KubeGroup}/{TypicalResource.KubeApiVersion}", Kind = TypicalResource.KubeKind, Metadata = new V1ObjectMeta( name: "test-name", namespaceProperty: "test-namespace") }; var unrelatedPod = new V1Pod { ApiVersion = TypicalResource.KubeApiVersion, Kind = TypicalResource.KubeKind, Metadata = new V1ObjectMeta( name: "test-unrelated", namespaceProperty: "test-namespace") }; var relatedPod = new V1Pod( apiVersion: TypicalResource.KubeApiVersion, kind: TypicalResource.KubeKind, metadata: new V1ObjectMeta( name: "test-related", namespaceProperty: "test-namespace", ownerReferences: new[] { new V1OwnerReference( uid: typical.Uid(), apiVersion: typical.ApiVersion, kind: typical.Kind, name: typical.Name()) })); typicalInformer.Callback(WatchEventType.Added, typical); podInformer.Callback(WatchEventType.Added, unrelatedPod); podInformer.Callback(WatchEventType.Added, relatedPod); var expectedName = new NamespacedName("test-namespace", "test-name"); Assert.Equal(new[] { expectedName, expectedName }, addCalls); Assert.True(cache.TryGetWorkItem(expectedName, out var cacheItem)); Assert.Equal(typical, cacheItem.Resource); var related = Assert.Single(cacheItem.Related); Assert.Equal(GroupKindNamespacedName.From(relatedPod), related.Key); Assert.Equal(relatedPod, related.Value); }
/// <summary> /// Initializes a new instance of the <see cref="GroupKindNamespacedName"/> struct. /// </summary> /// <param name="group">The group.</param> /// <param name="kind">The kind.</param> /// <param name="namespacedName">Name of the namespaced.</param> public GroupKindNamespacedName(string group, string kind, NamespacedName namespacedName) { Group = group; Kind = kind; NamespacedName = namespacedName; }
public bool Equals(QueueItem other) { return(NamespacedName.Equals(other.NamespacedName) && EqualityComparer <IDispatchTarget> .Default.Equals(DispatchTarget, other.DispatchTarget)); }
public QueueItem(NamespacedName namespacedName, IDispatchTarget dispatchTarget) { NamespacedName = namespacedName; DispatchTarget = dispatchTarget; }
private async Task ListAsync(CancellationToken cancellationToken) { var previousCache = _cache; _cache = new Dictionary <NamespacedName, IList <V1OwnerReference> >(); Logger.LogInformation( EventId(EventType.SynchronizeStarted), "Started synchronizing {ResourceType} resources from API server.", typeof(TResource).Name); string continueParameter = null; do { cancellationToken.ThrowIfCancellationRequested(); // request next page of items using var listWithHttpMessage = await _client.ListClusterAnyResourceKindWithHttpMessagesAsync <TResource>( _names.Group, _names.ApiVersion, _names.PluralName, continueParameter : continueParameter, cancellationToken : cancellationToken); var list = listWithHttpMessage.Body; foreach (var item in list.Items) { // These properties are not already set on items while listing // assigned here for consistency item.ApiVersion = _names.GroupApiVersion; item.Kind = _names.Kind; var key = NamespacedName.From(item); _cache[key] = item?.Metadata?.OwnerReferences; var watchEventType = WatchEventType.Added; if (previousCache.Remove(key)) { // an already-known key is provided as a modification for re-sync purposes watchEventType = WatchEventType.Modified; } InvokeRegistrationCallbacks(watchEventType, item); } foreach (var(key, value) in previousCache) { // for anything which was previously known but not part of list // send a deleted notification to clear any observer caches var item = new TResource() { ApiVersion = _names.GroupApiVersion, Kind = _names.Kind, Metadata = new V1ObjectMeta( name: key.Name, namespaceProperty: key.Namespace, ownerReferences: value), }; InvokeRegistrationCallbacks(WatchEventType.Deleted, item); } // keep track of values needed for next page and to start watching _lastResourceVersion = list.ResourceVersion(); continueParameter = list.Continue(); }while (!string.IsNullOrEmpty(continueParameter)); Logger.LogInformation( EventId(EventType.SynchronizeComplete), "Completed synchronizing {ResourceType} resources from API server.", typeof(TResource).Name); }
public bool TryGetReconcileData(NamespacedName key, out ReconcileData data) { return(Namespace(key.Namespace).TryLookup(key, out data)); }
/// <summary> /// Called by the informer with real-time resource updates. /// </summary> /// <param name="eventType">Indicates if the resource new, updated, or deleted.</param> /// <param name="resource">The information as provided by the Kubernets API server.</param> private void Notification(WatchEventType eventType, V1Ingress resource) { _cache.Update(eventType, resource); _queue.Add(new QueueItem(NamespacedName.From(resource), null)); }
public bool TryGetReconcileData(NamespacedName key, out ReconcileData data) { throw new NotImplementedException(); }