internal static void MapCustomUIPaths(this V1Container container, HealthCheckResource resource, OperatorDiagnostics diagnostics)
        {
            var uiPath = resource.Spec.UiPath ?? Constants.DEFAULT_UI_PATH;

            container.Env.Add(new V1EnvVar("ui_path", uiPath));
            diagnostics.UiPathConfigured(nameof(resource.Spec.UiPath), uiPath);

            if (!string.IsNullOrEmpty(resource.Spec.UiApiPath))
            {
                container.Env.Add(new V1EnvVar("ui_api_path", resource.Spec.UiApiPath));
                diagnostics.UiPathConfigured(nameof(resource.Spec.UiApiPath), resource.Spec.UiApiPath);
            }

            if (!string.IsNullOrEmpty(resource.Spec.UiResourcesPath))
            {
                container.Env.Add(new V1EnvVar("ui_resources_path", resource.Spec.UiResourcesPath));
                diagnostics.UiPathConfigured(nameof(resource.Spec.UiResourcesPath), resource.Spec.UiResourcesPath);
            }

            if (!string.IsNullOrEmpty(resource.Spec.UiWebhooksPath))
            {
                container.Env.Add(new V1EnvVar("ui_webhooks_path", resource.Spec.UiWebhooksPath));
                diagnostics.UiPathConfigured(nameof(resource.Spec.UiWebhooksPath), resource.Spec.UiWebhooksPath);
            }

            if (resource.Spec.UiNoRelativePaths.HasValue)
            {
                var noRelativePaths = resource.Spec.UiNoRelativePaths.Value.ToString();
                container.Env.Add(new V1EnvVar("ui_no_relative_paths", noRelativePaths));
                diagnostics.UiPathConfigured(nameof(resource.Spec.UiNoRelativePaths), noRelativePaths);
            }
        }
Пример #2
0
        public async Task <V1Deployment> GetOrCreateAsync(HealthCheckResource resource)
        {
            var deployment = await Get(resource);

            if (deployment != null)
            {
                return(deployment);
            }

            try
            {
                var deploymentResource = Build(resource);
                var response           =
                    await _client.CreateNamespacedDeploymentWithHttpMessagesAsync(deploymentResource,
                                                                                  resource.Metadata.NamespaceProperty);

                deployment = response.Body;

                _logger.LogInformation("Deployment {deployment} has been created", deployment.Metadata.Name);
            }
            catch (Exception ex)
            {
                _logger.LogError("Error creating deployment: {error}", ex.Message);
            }

            return(deployment);
        }
        public static string CreateAddress(V1Service service, HealthCheckResource resource)
        {
            var defaultPort = int.Parse(resource.Spec.PortNumber ?? Constants.DEFAULT_PORT);
            var port        = GetServicePort(service)?.Port ?? defaultPort;
            var address     = service.Spec.ClusterIP;

            string healthScheme = resource.Spec.HealthChecksScheme;

            if (service.Metadata.Annotations?.ContainsKey(Constants.HEALTH_CHECK_SCHEME_ANNOTATION) ?? false)
            {
                healthScheme = service.Metadata.Annotations[Constants.HEALTH_CHECK_SCHEME_ANNOTATION];
            }

            if (healthScheme.IsEmpty())
            {
                healthScheme = Constants.DEFAULT_SCHEME;
            }

            if (address.Contains(":"))
            {
                return($"{healthScheme}://[{address}]:{port}");
            }
            else
            {
                return($"{healthScheme}://{address}:{port}");
            }
        }
        public V1Service Build(HealthCheckResource resource)
        {
            var meta = new V1ObjectMeta
            {
                Name            = $"{resource.Spec.Name}-svc",
                OwnerReferences = new List <V1OwnerReference> {
                    resource.CreateOwnerReference()
                },
                Labels = new Dictionary <string, string>
                {
                    ["app"] = resource.Spec.Name
                },
            };

            var spec = new V1ServiceSpec
            {
                Selector = new Dictionary <string, string>
                {
                    ["app"] = resource.Spec.Name
                },
                Type  = resource.Spec.ServiceType ?? Constants.DefaultServiceType,
                Ports = new List <V1ServicePort> {
                    new V1ServicePort {
                        Name       = "httport",
                        Port       = int.Parse(resource.Spec.PortNumber ?? Constants.DefaultPort),
                        TargetPort = 80
                    }
                }
            };

            return(new V1Service(metadata: meta, spec: spec));
        }
        public async Task <V1Deployment> GetOrCreateAsync(HealthCheckResource resource)
        {
            var deployment = await Get(resource);

            if (deployment != null)
            {
                return(deployment);
            }

            try
            {
                var deploymentResource = Build(resource);
                var response           =
                    await _client.CreateNamespacedDeploymentWithHttpMessagesAsync(deploymentResource,
                                                                                  resource.Metadata.NamespaceProperty);

                deployment = response.Body;

                _operatorDiagnostics.DeploymentCreated(deployment.Metadata.Name);
            }
            catch (Exception ex)
            {
                _operatorDiagnostics.DeploymentOperationError(deployment.Metadata.Name, Deployment.Operation.ADD, ex.Message);
            }

            return(deployment);
        }
Пример #6
0
        public static string CreateAddress(V1Service service, HealthCheckResource resource)
        {
            var defaultPort = int.Parse(resource.Spec.PortNumber ?? Constants.DefaultPort);
            var port        = GetServicePort(service)?.Port ?? defaultPort;
            var address     = service.Spec.ClusterIP;

            string healthScheme = resource.Spec.HealthChecksScheme;

            if (service.Metadata.Annotations?.ContainsKey(Constants.HealthCheckSchemeAnnotation) ?? false)
            {
                healthScheme = service.Metadata.Annotations[Constants.HealthCheckSchemeAnnotation];
            }

            if (string.IsNullOrEmpty(healthScheme))
            {
                healthScheme = Constants.DefaultScheme;
            }

            if (address.Contains(":"))
            {
                return($"{healthScheme}://[{address}]:{port}");
            }
            else
            {
                return($"{healthScheme}://{address}:{port}");
            }
        }
        public async Task <V1Secret> GetOrCreateAsync(HealthCheckResource resource)
        {
            var secret = await Get(resource);

            if (secret != null)
            {
                return(secret);
            }

            try
            {
                var secretResource = Build(resource);
                secret = await _client.CreateNamespacedSecretAsync(secretResource,
                                                                   resource.Metadata.NamespaceProperty);

                _logger.LogInformation("Secret {name} has been created", secret.Metadata.Name);
            }
            catch (Exception ex)
            {
                _logger.LogError("Error creating Secret: {message}", ex.Message);
                throw;
            }

            return(secret);
        }
        internal Task Watch(HealthCheckResource resource, CancellationToken token)
        {
            Func <HealthCheckResource, bool> filter = (k) => k.Metadata.NamespaceProperty == resource.Metadata.NamespaceProperty;

            if (!_watchers.Keys.Any(filter))
            {
                var response = _client.ListNamespacedServiceWithHttpMessagesAsync(
                    namespaceParameter: resource.Metadata.NamespaceProperty,
                    labelSelector: $"{resource.Spec.ServicesLabel}",
                    watch: true,
                    cancellationToken: token);

                var watcher = response.Watch <V1Service, V1ServiceList>(
                    onEvent: async(type, item) => await _notificationHandler.NotifyDiscoveredServiceAsync(type, item, resource),
                    onError: e =>
                {
                    _diagnostics.ServiceWatcherThrow(e);
                    Watch(resource, token);
                }
                    );

                _diagnostics.ServiceWatcherStarting(resource.Metadata.NamespaceProperty);

                _watchers.Add(resource, watcher);
            }

            return(Task.CompletedTask);
        }
Пример #9
0
        public async Task DeleteDeploymentAsync(HealthCheckResource resource)
        {
            _logger.LogInformation("Deleting healthchecks deployment {name}", resource.Spec.Name);

            await _secretHandler.Delete(resource);

            await _deploymentHandler.Delete(resource);

            await _serviceHandler.Delete(resource);
        }
 public static V1OwnerReference CreateOwnerReference(this HealthCheckResource resource)
 {
     return(new V1OwnerReference
     {
         Name = resource.Spec.Name,
         ApiVersion = resource.ApiVersion,
         Uid = resource.Metadata.Uid,
         Kind = resource.Kind,
         Controller = true
     });
 }
 public async Task DeleteAsync(HealthCheckResource resource)
 {
     try
     {
         await _client.DeleteNamespacedSecretAsync($"{resource.Spec.Name}-secret", resource.Metadata.NamespaceProperty);
     }
     catch (Exception ex)
     {
         _logger.LogError("Error deleting secret for hc resource {name} : {message}", resource.Spec.Name, ex.Message);
     }
 }
 public async Task DeleteAsync(HealthCheckResource resource)
 {
     try
     {
         await _client.DeleteNamespacedDeploymentAsync($"{resource.Spec.Name}-deploy",
                                                       resource.Metadata.NamespaceProperty);
     }
     catch (Exception ex)
     {
         _operatorDiagnostics.DeploymentOperationError(resource.Spec.Name, Deployment.Operation.DELETE, ex.Message);
     }
 }
Пример #13
0
 public async Task Delete(HealthCheckResource resource)
 {
     try
     {
         await _client.DeleteNamespacedDeploymentAsync($"{resource.Spec.Name}-deploy",
                                                       resource.Metadata.NamespaceProperty);
     }
     catch (Exception ex)
     {
         _logger.LogError("Error deleting deployment for hc resource: {name} - err: {error}", resource.Spec.Name, ex.Message);
     }
 }
Пример #14
0
        private void StopServiceWatcher(HealthCheckResource resource)
        {
            Action stopWatcher        = () => _serviceWatcher.Stopwatch(resource);
            Action stopClusterWatcher = () => _clusterServiceWatcher.Stopwatch(resource);

            var stop = resource.Spec.Scope switch
            {
                Deployment.Scope.Namespaced => stopWatcher,
                Deployment.Scope.Cluster => stopClusterWatcher,
                _ => throw new ArgumentOutOfRangeException(nameof(resource.Spec.Scope))
            };

            stop();
        }
        internal void Stopwatch(HealthCheckResource resource)
        {
            Func <HealthCheckResource, bool> filter = (k) => k.Metadata.NamespaceProperty == resource.Metadata.NamespaceProperty;

            if (_watchers.Keys.Any(filter))
            {
                var svcResource = _watchers.Keys.FirstOrDefault(filter);
                if (svcResource != null)
                {
                    _diagnostics.ServiceWatcherStopped(resource.Metadata.NamespaceProperty);
                    _watchers[svcResource]?.Dispose();
                    _watchers.Remove(svcResource);
                }
            }
        }
        /// <inheritdoc />
        public async Task <HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = new CancellationToken())
        {
            var result = new HealthCheckResource();
            var timer  = Stopwatch.StartNew();

            foreach (var healthTest in _healthTests)
            {
                result.Tests.Add(await healthTest.RunTestAsync());
            }
            timer.Stop();
            result.ReportAsOf     = DateTime.UtcNow;
            result.ReportDuration = $"{timer.Elapsed.TotalSeconds} seconds";
            _healthStatusFlag.SetHealthStatus(result.Tests.Any(t => t.TestResult == TestResult.Failed) ? HealthStatus.Unhealthy : HealthStatus.Healthy);
            return(new HealthCheckResult(_healthStatusFlag.CurrentHealth, data: JObject.FromObject(result).ToObject <Dictionary <string, object> >()));
        }
Пример #17
0
        internal void Stopwatch(HealthCheckResource resource)
        {
            Func <HealthCheckResource, bool> filter = (k) => k.Metadata.NamespaceProperty == resource.Metadata.NamespaceProperty;

            if (_watchers.Keys.Any(filter))
            {
                var svcResource = _watchers.Keys.FirstOrDefault(filter);
                if (svcResource != null)
                {
                    _logger.LogInformation("Stopping services watcher for namespace {namespace}", resource.Metadata.NamespaceProperty);
                    _watchers[svcResource].Dispose();
                    _watchers.Remove(svcResource);
                }
            }
        }
Пример #18
0
        private async Task StartServiceWatcher(HealthCheckResource resource)
        {
            Func <Task> startWatcher = async() => await _serviceWatcher.Watch(resource, _operatorCts.Token);

            Func <Task> startClusterWatcher = async() => await _clusterServiceWatcher.Watch(resource, _operatorCts.Token);

            var start = resource.Spec.Scope switch
            {
                Deployment.Scope.Namespaced => startWatcher,
                Deployment.Scope.Cluster => startClusterWatcher,
                _ => throw new ArgumentOutOfRangeException(nameof(resource.Spec.Scope))
            };

            await start();
        }
        private async Task OnEventHandlerAsync(WatchEventType type, HealthCheckResource item, CancellationToken token)
        {
            if (type == WatchEventType.Added)
            {
                await _controller.DeployAsync(item);

                _serviceWatcher.Watch(item, token);
            }

            if (type == WatchEventType.Deleted)
            {
                await _controller.DeleteDeploymentAsync(item);

                _serviceWatcher.Stopwatch(item);
            }
        }
        internal Task Watch(HealthCheckResource resource, CancellationToken token)
        {
            var response = _client.ListServiceForAllNamespacesWithHttpMessagesAsync(
                labelSelector: $"{resource.Spec.ServicesLabel}",
                watch: true,
                cancellationToken: token);

            _watcher = response.Watch <V1Service, V1ServiceList>(
                onEvent: async(type, item) => await _notificationHandler.NotifyDiscoveredServiceAsync(type, item, resource),
                onError: e => _diagnostics.ServiceWatcherThrow(e)
                );

            _diagnostics.ServiceWatcherStarting("All");

            return(Task.CompletedTask);
        }
Пример #21
0
        public async Task <DeploymentResult> DeployAsync(HealthCheckResource resource)
        {
            _logger.LogInformation("Creating secret for hc resource - namespace {namespace}", resource.Metadata.NamespaceProperty);

            var secret = await _secretHandler.GetOrCreate(resource);

            _logger.LogInformation("Creating deployment for hc resource - namespace {namespace}", resource.Metadata.NamespaceProperty);

            var deployment = await _deploymentHandler.GetOrCreateAsync(resource);

            _logger.LogInformation("Creating service for hc resource - namespace {namespace}", resource.Metadata.NamespaceProperty);

            var service = await _serviceHandler.GetOrCreateAsync(resource);

            return(DeploymentResult.Create(deployment, service, secret));
        }
Пример #22
0
        public static async Task PushNotification(
            WatchEventType eventType,
            HealthCheckResource resource,
            V1Service uiService,
            V1Service notificationService,
            V1Secret endpointSecret,
            ILogger <K8sOperator> logger,
            IHttpClientFactory httpClientFactory)
        {
            var address   = KubernetesAddressFactory.CreateHealthAddress(notificationService, resource);
            var uiAddress = KubernetesAddressFactory.CreateAddress(uiService, resource);

            dynamic healthCheck = new
            {
                Type = eventType,
                notificationService.Metadata.Name,
                Uri = address
            };

            var client = httpClientFactory.CreateClient();

            try
            {
                string type = healthCheck.Type.ToString();
                string name = healthCheck.Name;
                string uri  = healthCheck.Uri;

                logger.LogInformation("[PushService] Namespace {Namespace} - Sending Type: {type} - Service {name} with uri : {uri} to ui endpoint: {address}", resource.Metadata.NamespaceProperty, type, name, uri, uiAddress);

                var key = Encoding.UTF8.GetString(endpointSecret.Data["key"]);

                var response = await client.PostAsync($"{uiAddress}{Constants.PushServicePath}?{Constants.PushServiceAuthKey}={key}",

                                                      new StringContent(JsonSerializer.Serialize(healthCheck, new JsonSerializerOptions
                {
                    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
                }), Encoding.UTF8, "application/json"));


                logger.LogInformation("[PushService] Notification result for {name} - status code: {statuscode}", notificationService.Metadata.Name, response.StatusCode);
            }
            catch (Exception ex)
            {
                logger.LogError("Error notifying healthcheck service: {message}", ex.Message);
            }
        }
        public static string CreateHealthAddress(V1Service service, HealthCheckResource resource)
        {
            var address = CreateAddress(service, resource);

            string healthPath = resource.Spec.HealthChecksPath;

            if (service.Metadata.Annotations?.ContainsKey(Constants.HEALTH_CHECK_PATH_ANNOTATION) ?? false)
            {
                healthPath = service.Metadata.Annotations[Constants.HEALTH_CHECK_PATH_ANNOTATION];
            }

            if (healthPath.IsEmpty())
            {
                healthPath = Constants.DEFAULT_HEALTH_PATH;
            }

            return($"{address}/{ healthPath.TrimStart('/')}");
        }
        public static string CreateHealthAddress(V1Service service, HealthCheckResource resource)
        {
            var address = CreateAddress(service, resource);

            string healthPath = resource.Spec.HealthChecksPath;

            if (service.Metadata.Annotations?.ContainsKey(Constants.HealthCheckPathAnnotation) ?? false)
            {
                healthPath = service.Metadata.Annotations[Constants.HealthCheckPathAnnotation];
            }

            if (string.IsNullOrEmpty(healthPath))
            {
                healthPath = Constants.DefaultHealthPath;
            }

            return($"{address}/{ healthPath.TrimStart('/')}");
        }
 public V1ConfigMap Build(HealthCheckResource resource)
 {
     return(new V1ConfigMap
     {
         BinaryData = new Dictionary <string, byte[]>
         {
             [Constants.STYLE_SHEET_NAME] = Encoding.UTF8.GetBytes(resource.Spec.StylesheetContent)
         },
         Metadata = new V1ObjectMeta
         {
             OwnerReferences = new List <V1OwnerReference>
             {
                 resource.CreateOwnerReference(),
             },
             NamespaceProperty = resource.Metadata.NamespaceProperty,
             Name = $"{resource.Spec.Name}-config"
         }
     });
 }
Пример #26
0
        private async Task WaitForAvailableReplicas(HealthCheckResource resource)
        {
            int retries           = 1;
            int availableReplicas = 0;

            while (retries <= WaitForReplicaRetries && availableReplicas == 0)
            {
                var deployment = await _client.ListNamespacedOwnedDeploymentAsync(resource.Metadata.NamespaceProperty, resource.Metadata.Uid);

                availableReplicas = deployment.Status.AvailableReplicas ?? 0;

                if (availableReplicas == 0)
                {
                    _logger.LogInformation("The UI replica {Name} in {Namespace} is not available yet, retrying...{Retries}/{MaxRetries}", deployment.Metadata.Name, resource.Metadata.NamespaceProperty, retries, WaitForReplicaRetries);
                    await Task.Delay(WaitForReplicaDelay);

                    retries++;
                }
            }
        }
        public static string CreateAddress(V1Service service, HealthCheckResource resource)
        {
            var defaultPort = int.Parse(resource.Spec.PortNumber ?? Constants.DefaultPort);

            var port = service.Spec.Type switch
            {
                ServiceType.LoadBalancer => GetServicePort(service)?.Port ?? defaultPort,
                ServiceType.ClusterIP => GetServicePort(service)?.Port ?? defaultPort,
                ServiceType.NodePort => GetServicePort(service)?.NodePort ?? defaultPort,
                _ => throw new NotSupportedException($"{service.Spec.Type} port type not supported")
            };

            var address = service.Spec.Type switch
            {
                ServiceType.LoadBalancer => GetLoadBalancerAddress(service),
                ServiceType.NodePort => GetLoadBalancerAddress(service),
                ServiceType.ClusterIP => service.Spec.ClusterIP,
                _ => throw new NotSupportedException($"{service.Spec.Type} port type not supported")
            };

            string healthScheme = resource.Spec.HealthChecksScheme;

            if (service.Metadata.Annotations?.ContainsKey(Constants.HealthCheckSchemeAnnotation) ?? false)
            {
                healthScheme = service.Metadata.Annotations[Constants.HealthCheckSchemeAnnotation];
            }

            if (string.IsNullOrEmpty(healthScheme))
            {
                healthScheme = Constants.DefaultScheme;
            }

            if (address.Contains(":"))
            {
                return($"{healthScheme}://[{address}]:{port}");
            }
            else
            {
                return($"{healthScheme}://{address}:{port}");
            }
        }
 public V1Secret Build(HealthCheckResource resource)
 {
     return(new V1Secret
     {
         Metadata = new V1ObjectMeta
         {
             Name = $"{resource.Spec.Name}-secret",
             NamespaceProperty = resource.Metadata.NamespaceProperty,
             OwnerReferences = new List <V1OwnerReference> {
                 resource.CreateOwnerReference()
             },
             Labels = new Dictionary <string, string>
             {
                 ["app"] = resource.Spec.Name
             }
         },
         Data = new Dictionary <string, byte[]>
         {
             ["key"] = Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())
         }
     });
 }
Пример #29
0
        public V1Service Build(HealthCheckResource resource)
        {
            var meta = new V1ObjectMeta
            {
                Name            = $"{resource.Spec.Name}-svc",
                OwnerReferences = new List <V1OwnerReference> {
                    resource.CreateOwnerReference()
                },
                Annotations = new Dictionary <string, string>(),
                Labels      = new Dictionary <string, string>
                {
                    ["app"] = resource.Spec.Name
                },
            };

            var spec = new V1ServiceSpec
            {
                Selector = new Dictionary <string, string>
                {
                    ["app"] = resource.Spec.Name
                },
                Type  = resource.Spec.ServiceType ?? Constants.DefaultServiceType,
                Ports = new List <V1ServicePort> {
                    new V1ServicePort {
                        Name       = "httport",
                        Port       = int.Parse(resource.Spec.PortNumber ?? Constants.DefaultPort),
                        TargetPort = 80
                    }
                }
            };

            foreach (var annotation in resource.Spec.ServiceAnnotations)
            {
                _logger.LogInformation("Adding annotation {Annotation} to ui service with value {AnnotationValue}", annotation.Name, annotation.Value);
                meta.Annotations.Add(annotation.Name, annotation.Value);
            }

            return(new V1Service(metadata: meta, spec: spec));
        }
        public async Task <V1ConfigMap> GetOrCreateAsync(HealthCheckResource resource)
        {
            var configMap = await Get(resource);

            if (configMap != null)
            {
                return(configMap);
            }

            try
            {
                var configMapResource = Build(resource);
                configMap = await _client.CreateNamespacedConfigMapAsync(configMapResource, resource.Metadata.NamespaceProperty);

                _logger.LogInformation("Config Map {name} has been created", configMap.Metadata.Name);
            }
            catch (Exception ex)
            {
                _logger.LogError("Error creating config map for hc resource {name} : {message}", resource.Spec.Name, ex.Message);
            }

            return(configMap);
        }