Beispiel #1
0
        private async Task <List <Cluster> > ExtractClusters(QueryResult <Dictionary <string, AgentService> > serviceResult)
        {
            var clusters       = new Dictionary <string, Cluster>();
            var serviceMapping = serviceResult.Response;

            foreach (var(key, svc) in serviceMapping)
            {
                var cluster = clusters.ContainsKey(svc.Service)
                    ? clusters[svc.Service]
                    : new Cluster {
                    Id = svc.Service
                };

                cluster.Destinations.Add(svc.ID, new Destination {
                    Address = $"{svc.Address}:{svc.Port}"
                });

                var clusterErrs = await _proxyConfigValidator.ValidateClusterAsync(cluster);

                if (clusterErrs.Any())
                {
                    _logger.LogError("Errors found when creating clusters for {Service}", svc.Service);
                    clusterErrs.ForEach(err => _logger.LogError(err, $"{svc.Service} cluster validation error"));
                    continue;
                }

                clusters[svc.Service] = cluster;
            }

            return(clusters.Values.ToList());
        }
        private async Task <(IList <Cluster>, IList <Exception>)> VerifyClustersAsync(IReadOnlyList <Cluster> clusters, CancellationToken cancellation)
        {
            if (clusters == null)
            {
                return(Array.Empty <Cluster>(), Array.Empty <Exception>());
            }

            var seenClusterIds     = new HashSet <string>(clusters.Count, StringComparer.OrdinalIgnoreCase);
            var configuredClusters = new List <Cluster>(clusters.Count);
            var errors             = new List <Exception>();

            // The IProxyConfigProvider provides a fresh snapshot that we need to reconfigure each time.
            foreach (var c in clusters)
            {
                try
                {
                    if (seenClusterIds.Contains(c.Id))
                    {
                        errors.Add(new ArgumentException($"Duplicate cluster '{c.Id}'."));
                        continue;
                    }

                    seenClusterIds.Add(c.Id);

                    // Don't modify the original
                    var cluster = c.DeepClone();

                    foreach (var filter in _filters)
                    {
                        await filter.ConfigureClusterAsync(cluster, cancellation);
                    }

                    var clusterErrors = await _configValidator.ValidateClusterAsync(cluster);

                    if (clusterErrors.Count > 0)
                    {
                        errors.AddRange(clusterErrors);
                        continue;
                    }

                    configuredClusters.Add(cluster);
                }
                catch (Exception ex)
                {
                    errors.Add(new ArgumentException($"An exception was thrown from the configuration callbacks for cluster '{c.Id}'.", ex));
                }
            }

            if (errors.Count > 0)
            {
                return(null, errors);
            }

            return(configuredClusters, errors);
        }
Beispiel #3
0
        /// <inheritdoc/>
        public async Task <(IReadOnlyList <RouteConfig> Routes, IReadOnlyList <ClusterConfig> Clusters)> DiscoverAsync(CancellationToken cancellation)
        {
            // Take a snapshot of current options and use that consistently for this execution.
            var options = _optionsMonitor.CurrentValue;

            _serviceFabricCaller.CleanUpExpired();

            var discoveredBackends = new Dictionary <string, ClusterConfig>(StringComparer.Ordinal);
            var discoveredRoutes   = new List <RouteConfig>();
            IEnumerable <ApplicationWrapper> applications;

            try
            {
                applications = await _serviceFabricCaller.GetApplicationListAsync(cancellation);
            }
            catch (OperationCanceledException) when(cancellation.IsCancellationRequested)
            {
                throw;
            }
            catch (Exception ex) // TODO: davidni: not fatal?
            {
                // The serviceFabricCaller does their best effort to use LKG information, nothing we can do at this point
                Log.GettingApplicationFailed(_logger, ex);
                applications = Enumerable.Empty <ApplicationWrapper>();
            }

            foreach (var application in applications)
            {
                IEnumerable <ServiceWrapper> services;

                try
                {
                    services = await _serviceFabricCaller.GetServiceListAsync(application.ApplicationName, cancellation);
                }
                catch (OperationCanceledException) when(cancellation.IsCancellationRequested)
                {
                    throw;
                }
                catch (Exception ex) // TODO: davidni: not fatal?
                {
                    Log.GettingServiceFailed(_logger, application.ApplicationName, ex);
                    continue;
                }

                foreach (var service in services)
                {
                    try
                    {
                        var serviceExtensionLabels = await _serviceFabricExtensionConfigProvider.GetExtensionLabelsAsync(application, service, cancellation);

                        // If this service wants to use us as the proxy
                        if (serviceExtensionLabels.GetValueOrDefault("YARP.Enable", null) != "true")
                        {
                            // Skip this service
                            continue;
                        }

                        var destinations = await DiscoverDestinationsAsync(options, service, serviceExtensionLabels, cancellation);

                        var cluster = LabelsParser.BuildCluster(service.ServiceName, serviceExtensionLabels, destinations);
                        var clusterValidationErrors = await _configValidator.ValidateClusterAsync(cluster);

                        if (clusterValidationErrors.Count > 0)
                        {
                            throw new ConfigException($"Skipping cluster id '{cluster.ClusterId} due to validation errors.", new AggregateException(clusterValidationErrors));
                        }

                        if (!discoveredBackends.TryAdd(cluster.ClusterId, cluster))
                        {
                            throw new ConfigException($"Duplicated cluster id '{cluster.ClusterId}'. Skipping repeated definition, service '{service.ServiceName}'");
                        }

                        var routes = LabelsParser.BuildRoutes(service.ServiceName, serviceExtensionLabels);
                        var routeValidationErrors = new List <Exception>();
                        foreach (var route in routes)
                        {
                            routeValidationErrors.AddRange(await _configValidator.ValidateRouteAsync(route));
                        }

                        if (routeValidationErrors.Count > 0)
                        {
                            // Don't add ANY routes if even a single one is bad. Trying to add partial routes
                            // could lead to unexpected results (e.g. a typo in the configuration of higher-priority route
                            // could lead to a lower-priority route being selected for requests it should not be handling).
                            throw new ConfigException($"Skipping ALL routes for cluster id '{cluster.ClusterId} due to validation errors.", new AggregateException(routeValidationErrors));
                        }

                        discoveredRoutes.AddRange(routes);

                        ReportServiceHealth(options, service.ServiceName, HealthState.Ok, $"Successfully built cluster '{cluster.ClusterId}' with {routes.Count} routes.");
                    }
                    catch (ConfigException ex)
                    {
                        // User error
                        Log.InvalidServiceConfig(_logger, service.ServiceName, ex);

                        // TODO: emit Error health report once we are able to detect config issues *during* (as opposed to *after*) a target service upgrade.
                        // Proactive Error health report would trigger a rollback of the target service as desired. However, an Error report after rhe fact
                        // will NOT cause a rollback and will prevent the target service from performing subsequent monitored upgrades to mitigate, making things worse.
                        ReportServiceHealth(options, service.ServiceName, HealthState.Warning, $"Could not load service configuration: {ex.Message}.");
                    }
                    catch (Exception ex) // TODO: davidni: not fatal?
                    {
                        // Not user's problem
                        Log.ErrorLoadingServiceConfig(_logger, service.ServiceName, ex);
                    }
                }
            }

            Log.ServiceDiscovered(_logger, discoveredBackends.Count, discoveredRoutes.Count);
            return(discoveredRoutes, discoveredBackends.Values.ToList());
        }