/// <summary>
        /// The Service Manifest can specify a label with value <c>[AppParamName]</c>, in which case we replace it
        /// with the value of an application parameter with the given name <c>AppParamName</c>.
        /// Application parameter names are case insensitive in Service Fabric.
        /// If no such app param exists, we replace with empty string.
        /// </summary>
        private void ApplyAppParamReplacements(Dictionary <string, string> labels, ApplicationWrapper app, ServiceWrapper service)
        {
            var replacements = new List <KeyValuePair <string, string> >();

            foreach (var label in labels)
            {
                var value = label.Value;
                if (value.Length > 2 && value[0] == '[' && value[value.Length - 1] == ']')
                {
                    var appParamName = value.Substring(1, value.Length - 2);
                    if (app.ApplicationParameters == null ||
                        !app.ApplicationParameters.TryGetValue(appParamName, out var appParamValue))
                    {
                        // TODO: This should trigger a Warning or Error health report on the faulty service.
                        // This is not critical because if the absence of the setting leads to invalid configs, we *do* already report error
                        // (for example, if a route's rule were missing).
                        _logger.LogInformation($"Application does not specify parameter referenced in a Service Manifest extension label. ApplicationName='{app.ApplicationName}', ApplicationtypeName='{app.ApplicationTypeName}', ApplicationTypeVersion='{app.ApplicationTypeVersion}', ServiceName='{service.ServiceName}', Label='{label.Key}', AppParamName='{appParamName}'.");
                        appParamValue = string.Empty;
                    }

                    replacements.Add(KeyValuePair.Create(label.Key, appParamValue));
                }
            }

            foreach (var replacement in replacements)
            {
                labels[replacement.Key] = replacement.Value;
            }
        }
        /// <inheritdoc/>
        public async Task <Dictionary <string, string> > GetExtensionLabelsAsync(ApplicationWrapper application, ServiceWrapper service, CancellationToken cancellationToken)
        {
            _ = application ?? throw new ArgumentNullException(nameof(application));
            _ = service ?? throw new ArgumentNullException(nameof(service));
            _ = application.ApplicationTypeName ?? throw new ArgumentNullException($"{nameof(application)}.{nameof(application.ApplicationTypeName)}");
            _ = application.ApplicationTypeVersion ?? throw new ArgumentNullException($"{nameof(application)}.{nameof(application.ApplicationTypeVersion)}");
            _ = service.ServiceTypeName ?? throw new ArgumentNullException($"{nameof(service)}.{nameof(service.ServiceTypeName)}");
            _ = service.ServiceName ?? throw new ArgumentNullException($"{nameof(service)}.{nameof(service.ServiceName)}");

            string serviceManifestName;

            try
            {
                serviceManifestName = await _serviceFabricCaller.GetServiceManifestName(application.ApplicationTypeName, application.ApplicationTypeVersion, service.ServiceTypeName, cancellationToken);
            }
            catch (OperationCanceledException) when(cancellationToken.IsCancellationRequested)
            {
                throw;
            }
            catch (Exception ex) // TODO: davidni: not fatal?
            {
                throw new ServiceFabricIntegrationException($"Failed to get service manifest name for service type {service.ServiceTypeName} of application type {application.ApplicationTypeName} {application.ApplicationTypeVersion} from Service Fabric: {ex}.");
            }

            if (serviceManifestName == null)
            {
                throw new ServiceFabricIntegrationException($"No service manifest name was found for service type {service.ServiceTypeName} of application type {application.ApplicationTypeName} {application.ApplicationTypeVersion}.");
            }

            string rawServiceManifest;

            try
            {
                rawServiceManifest = await _serviceFabricCaller.GetServiceManifestAsync(application.ApplicationTypeName, application.ApplicationTypeVersion, serviceManifestName, cancellationToken);
            }
            catch (OperationCanceledException) when(cancellationToken.IsCancellationRequested)
            {
                throw;
            }
            catch (Exception ex) // TODO: davidni: not fatal?
            {
                throw new ServiceFabricIntegrationException($"Failed to get service manifest {serviceManifestName} of service type {service.ServiceTypeName} of application type {application.ApplicationTypeName} {application.ApplicationTypeVersion} from Service Fabric: {ex}.");
            }

            if (rawServiceManifest == null)
            {
                throw new ServiceFabricIntegrationException($"No service manifest named '{serviceManifestName}' was found for service type {service.ServiceTypeName} of application type {application.ApplicationTypeName} {application.ApplicationTypeVersion}.");
            }

            // TODO: gathering labels from multiple servicetypes within the same service would result in multiple
            // calls to the SF client and multiple XML parses. We should consider creating an instance of this class
            // per application type to reuse that data. Since this is uncommon, for now we follow the na�ve implementation.
            var result = ExtractLabels(rawServiceManifest, service.ServiceTypeName);

            ApplyAppParamReplacements(result, application, service);

            if (result.GetValueOrDefault("YARP.EnableDynamicOverrides", null)?.ToLower() == "true")
            {
                // Override with properties
                IDictionary <string, string> properties;
                try
                {
                    properties = await _serviceFabricCaller.EnumeratePropertiesAsync(service.ServiceName, cancellationToken);
                }
                catch (OperationCanceledException) when(cancellationToken.IsCancellationRequested)
                {
                    throw;
                }
                catch (Exception ex)
                {
                    throw new ServiceFabricIntegrationException($"Failed to get properties for {service.ServiceName}.", ex);
                }

                OverrideLabels(ref result, properties);
            }

            return(result);
        }
Beispiel #3
0
        private async Task LoadServiceDataAsync(Dictionary <string, string> data, ApplicationWrapper application, string appPrefix,
                                                ServiceWrapper service, CancellationToken cancellationToken)
        {
            string serviceManifestName;

            try
            {
                serviceManifestName = await _serviceFabricCaller.GetServiceManifestName(application.ApplicationTypeName,
                                                                                        application.ApplicationTypeVersion, service.ServiceTypeName, cancellationToken);
            }
            catch (OperationCanceledException) when(cancellationToken.IsCancellationRequested)
            {
                throw;
            }
            catch (Exception ex) // TODO: davidni: not fatal?
            {
                throw new ServiceFabricIntegrationException(
                          $"Failed to get service manifest name for service type {service.ServiceTypeName} of application type {application.ApplicationTypeName} {application.ApplicationTypeVersion} from Service Fabric: {ex}.");
            }

            if (serviceManifestName == null)
            {
                throw new ServiceFabricIntegrationException(
                          $"No service manifest name was found for service type {service.ServiceTypeName} of application type {application.ApplicationTypeName} {application.ApplicationTypeVersion}.");
            }

            string rawServiceManifest;

            try
            {
                rawServiceManifest = await _serviceFabricCaller.GetServiceManifestAsync(application.ApplicationTypeName,
                                                                                        application.ApplicationTypeVersion, serviceManifestName, cancellationToken);
            }
            catch (OperationCanceledException) when(cancellationToken.IsCancellationRequested)
            {
                throw;
            }
            catch (Exception ex) // TODO: davidni: not fatal?
            {
                throw new ServiceFabricIntegrationException(
                          $"Failed to get service manifest {serviceManifestName} of service type {service.ServiceTypeName} of application type {application.ApplicationTypeName} {application.ApplicationTypeVersion} from Service Fabric: {ex}.");
            }

            if (rawServiceManifest == null)
            {
                throw new ServiceFabricIntegrationException(
                          $"No service manifest named '{serviceManifestName}' was found for service type {service.ServiceTypeName} of application type {application.ApplicationTypeName} {application.ApplicationTypeVersion}.");
            }

            using (var reader = XmlReader.Create(new StringReader(rawServiceManifest), XmlReaderHelper.CreateSafeXmlSetting()))
            {
                XDocument parsedManifest;
                try
                {
                    parsedManifest = await XDocument.LoadAsync(reader, LoadOptions.None, cancellationToken);
                }
                catch (System.Xml.XmlException ex)
                {
                    // TODO: we don't know if the service wants to use the gateway yet, so not sure if this classifies as config error (considering it will escalate into a bad health report)
                    throw new ConfigException("Failed to parse service manifest XML.", ex);
                }

                var elements = parsedManifest
                               .Elements(XmlReaderHelper.XNSServiceManifest + "ServiceManifest")
                               .Elements(XmlReaderHelper.XNSServiceManifest + "ServiceTypes")
                               .Elements().Where(s => (string)s.Attribute("ServiceTypeName") == service.ServiceTypeName)
                               .Elements(XmlReaderHelper.XNSServiceManifest + "Extensions")
                               .Elements(XmlReaderHelper.XNSServiceManifest + "Extension").Where(s =>
                                                                                                 (string)s.Attribute("Name") == ConfigurationValues.ExtensionName)
                               .Elements(XmlReaderHelper.XNSFabricNoSchema + "Service");

                if (!elements.Any())
                {
                    return;
                }

                var serviceId     = service.ServiceName.ToString().Replace($"{application.ApplicationName}/", string.Empty);
                var servicePrefix =
                    $"{appPrefix}Services{ConfigurationPath.KeyDelimiter}{serviceId}{ConfigurationPath.KeyDelimiter}";
                data[$"{servicePrefix}Id"]              = serviceId;
                data[$"{servicePrefix}Name"]            = service.ServiceName.ToString();
                data[$"{servicePrefix}TypeName"]        = service.ServiceTypeName;
                data[$"{servicePrefix}Kind"]            = service.ServiceKind.ToString();
                data[$"{servicePrefix}ManifestVersion"] = service.ServiceManifestVersion;

                await using (var stream = new MemoryStream())
                {
                    await using (var sw = new StreamWriter(stream))
                    {
                        using (var writer = new XmlNoNamespaceWriter(sw, new XmlWriterSettings {
                            CloseOutput = false
                        }))
                        {
                            foreach (var element in elements)
                            {
                                element.Save(writer);
                            }

                            writer.Flush();
                            await sw.FlushAsync();

                            var sections = XmlStreamToDictionaryParser.Parse(stream, (options) =>
                            {
                                options.KeyDelimiter = ConfigurationPath.KeyDelimiter;
                                options.Parents      = new List <string>(servicePrefix.Split(ConfigurationPath.KeyDelimiter,
                                                                                             StringSplitOptions.RemoveEmptyEntries));
                                options.IsIndexAttribute = (attribute, stack) =>
                                {
                                    switch (stack.FirstOrDefault())
                                    {
                                    case "Endpoint":
                                        return(string.Equals(attribute, "Id", StringComparison.OrdinalIgnoreCase));

                                    case "Route":
                                        return(string.Equals(attribute, "Id", StringComparison.OrdinalIgnoreCase));
                                    }

                                    return(false);
                                };
                            });
                            foreach (var section in sections)
                            {
                                data[section.Key] = section.Value;
                            }
                        }
                    }
                }
            }
        }
Beispiel #4
0
        private async Task LoadApplicationDataAsync(Dictionary <string, string> data, ApplicationWrapper application, CancellationToken cancellationToken)
        {
            IEnumerable <ServiceWrapper> services;

            try
            {
                services = await _serviceFabricCaller.GetServiceListAsync(application.ApplicationName, cancellationToken);
            }
            catch (OperationCanceledException) when(cancellationToken.IsCancellationRequested)
            {
                throw;
            }
            catch (Exception ex) // TODO: davidni: not fatal?
            {
                _logger.LogError(ex,
                                 $"Could not get service list for application {application.ApplicationName}, skipping application.");
                return;
            }

            var appId     = application.ApplicationName.ToString().Replace("fabric:/", string.Empty);
            var appPrefix =
                $"Fabric{ConfigurationPath.KeyDelimiter}Applications{ConfigurationPath.KeyDelimiter}{appId}{ConfigurationPath.KeyDelimiter}";

            data[$"{appPrefix}Id"]          = appId;
            data[$"{appPrefix}Name"]        = application.ApplicationName.ToString();
            data[$"{appPrefix}TypeName"]    = application.ApplicationTypeName;
            data[$"{appPrefix}TypeVersion"] = application.ApplicationTypeVersion;
            foreach (var parameter in application.ApplicationParameters)
            {
                var paramPrefix =
                    $"{appPrefix}Parameters{ConfigurationPath.KeyDelimiter}{parameter.Key}{ConfigurationPath.KeyDelimiter}";
                data[$"{paramPrefix}Name"]  = parameter.Key;
                data[$"{paramPrefix}Value"] = parameter.Value;
            }

            foreach (var service in services)
            {
                await LoadServiceDataAsync(data, application, appPrefix, service, cancellationToken);
            }
        }