/// <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); }
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; } } } } } }