Browse Source

Support additional Dapr configuration properties (#1209)

* Support more Dapr properties.

* Update schema with Dapr extension properties.

* Add more Dapr properties.

* Fix formatting.
sebros/macos
Phillip Hoff 5 years ago
committed by GitHub
parent
commit
1f51824cbc
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 95
      docs/reference/schema.md
  2. 109
      src/Microsoft.Tye.Extensions/Dapr/DaprExtension.cs
  3. 13
      src/Microsoft.Tye.Extensions/Dapr/DaprExtensionConfiguration.cs
  4. 44
      src/Microsoft.Tye.Extensions/Dapr/DaprExtensionConfigurationReader.cs

95
docs/reference/schema.md

@ -707,3 +707,98 @@ If both `port` and `protocol` are provided, Tye selects the first biding with th
#### `headers` (`(name, value)[]`) #### `headers` (`(name, value)[]`)
Array of headers that are sent as part of the HTTP request that probes the replicas of the service. Array of headers that are sent as part of the HTTP request that probes the replicas of the service.
## Extensions
Each of the Tye extensions have their own custom configuration schema.
### Dapr
For the Dapr extension, the available properties closely follow those found on the [`dapr run` command line](https://docs.dapr.io/reference/cli/dapr-run/). The Dapr extension allows many properties to be set and/or overridden on a service-by-service basis using the `services` dictionary.
```yaml
extensions:
- name: dapr
config: common
enable-profiling: true
services:
frontend:
components-path: "./frontend/components"
backend:
components-path: "./backend/components"
services:
- name: frontend
- name: backend
```
The following properties are annotated as follows:
- *extension-level*: Can only be set at the root of the extension configuration.
- *service-level*: Can only be set for a specific service within the `services` dictionary.
- *overridable*: Can be set at both the root of the extension configuration or be overridden for a specific service within the `services` dictionary.
#### `app-id` (`string`) *service-level*
The ID for your application, used for service discovery.
#### `app-max-concurrency` (`integer`) *overridable*
The concurrency level of the
application (otherwise unlimited).
#### `app-protocol` (`string`) *overridable*
The protocol (gRPC or HTTP) Dapr uses to talk to the application (with HTTP being the default).
#### `app-ssl` (`boolean`) *overridable*
Enable HTTPS when Dapr invokes the application.
#### `components-path` (`string`) *overridable*
The path of the components directory. If relative, is relative to the root directory of the application (i.e. of the `tye.yaml`).
#### `config` (`string`) *overridable*
The name of the Dapr configuration file (without extension). Assumed to be relative to the `components-path` directory, if specified, else the `components` folder in the root directory of the application (i.e. of the `tye.yaml`).
#### `enabled` (`boolean`) *service-level*
Whether a Dapr sidecar is created for the service. If `true`, a sidecar is created even if the default for the service type would be not to create one. If `false`, a sidecar is *not* created even if the default for the service type would be to create one.
#### `enable-profiling` (`boolean`) *overridable*
Enable `pprof` profiling via an HTTP endpoint.
#### `grpc-port` (`integer`) *service-level*
The gRPC port for Dapr to listen on.
#### `http-max-request-size` (`integer`) *overridable*
The maximum size of an HTTP request body in MB.
#### `http-port` (`integer`) *service-level*
The HTTP port for Dapr to listen on.
#### `log-level` (`string`) *overridable*
The log verbosity. Valid values are: `debug`, `info`, `warn`, `error`, `fatal`, and `panic`.
#### `metrics-port` (`integer`) *service-level*
The port used to collect Dapr metrics.
#### `placement-port` (`integer`) *service-level*
The port of the Dapr placement service.
#### `profile-port` (`integer`) *service-level*
The port for the Dapr profile servicer to listen on.
#### `services` (`(string, object)[]`) *extension-level*
The dictionary in which service-level configuration can be set.

109
src/Microsoft.Tye.Extensions/Dapr/DaprExtension.cs

@ -19,12 +19,12 @@ namespace Microsoft.Tye.Extensions.Dapr
// If we're getting called then the user configured dapr in their tye.yaml. // If we're getting called then the user configured dapr in their tye.yaml.
// We don't have any of our own config. // We don't have any of our own config.
var extensionConfiguration = DaprExtensionConfigurationReader.ReadConfiguration(config.Data);
if (context.Operation == ExtensionContext.OperationKind.LocalRun) if (context.Operation == ExtensionContext.OperationKind.LocalRun)
{ {
await VerifyDaprInitialized(context); await VerifyDaprInitialized(context);
var extensionConfiguration = DaprExtensionConfigurationReader.ReadConfiguration(config.Data);
// For local run, enumerate all projects, and add services for each dapr proxy. // For local run, enumerate all projects, and add services for each dapr proxy.
var projects = context.Application.Services.OfType<ProjectServiceBuilder>().Cast<LaunchedServiceBuilder>(); var projects = context.Application.Services.OfType<ProjectServiceBuilder>().Cast<LaunchedServiceBuilder>();
var executables = context.Application.Services.OfType<ExecutableServiceBuilder>().Cast<LaunchedServiceBuilder>(); var executables = context.Application.Services.OfType<ExecutableServiceBuilder>().Cast<LaunchedServiceBuilder>();
@ -32,7 +32,9 @@ namespace Microsoft.Tye.Extensions.Dapr
foreach (var project in services) foreach (var project in services)
{ {
extensionConfiguration.Services.TryGetValue(project.Name, out DaprExtensionServiceConfiguration? serviceConfiguration); DaprExtensionServiceConfiguration? serviceConfiguration = null;
extensionConfiguration?.Services.TryGetValue(project.Name, out serviceConfiguration);
if (serviceConfiguration?.Enabled != null && serviceConfiguration.Enabled.Value == false) if (serviceConfiguration?.Enabled != null && serviceConfiguration.Enabled.Value == false)
{ {
@ -68,13 +70,15 @@ namespace Microsoft.Tye.Extensions.Dapr
var daprExecutablePath = GetDaprExecutablePath(); var daprExecutablePath = GetDaprExecutablePath();
string appId = serviceConfiguration?.AppId ?? project.Name;
var proxy = new ExecutableServiceBuilder($"{project.Name}-dapr", daprExecutablePath, ServiceSource.Extension) var proxy = new ExecutableServiceBuilder($"{project.Name}-dapr", daprExecutablePath, ServiceSource.Extension)
{ {
WorkingDirectory = context.Application.Source.DirectoryName, WorkingDirectory = context.Application.Source.DirectoryName,
// These environment variables are replaced with environment variables // These environment variables are replaced with environment variables
// defined for this service. // defined for this service.
Args = $"run --app-id {project.Name} --dapr-grpc-port %DAPR_GRPC_PORT% --dapr-http-port %DAPR_HTTP_PORT% --metrics-port %METRICS_PORT%", Args = $"run --app-id {appId} --dapr-grpc-port %DAPR_GRPC_PORT% --dapr-http-port %DAPR_HTTP_PORT% --metrics-port %METRICS_PORT%",
}; };
if (httpBinding != null) if (httpBinding != null)
@ -82,7 +86,28 @@ namespace Microsoft.Tye.Extensions.Dapr
proxy.Args += $" --app-port %APP_PORT%"; proxy.Args += $" --app-port %APP_PORT%";
} }
var daprPlacementPort = serviceConfiguration?.PlacementPort ?? extensionConfiguration.PlacementPort; var appMaxConcurrency = serviceConfiguration?.AppMaxConcurrency ?? extensionConfiguration?.AppMaxConcurrency;
if (appMaxConcurrency != null)
{
proxy.Args += $" --app-max-concurrency {appMaxConcurrency}";
}
var appProtocol = serviceConfiguration?.AppProtocol ?? extensionConfiguration?.AppProtocol;
if (appProtocol != null)
{
proxy.Args += $" --app-protocol {appProtocol}";
}
var appSsl = serviceConfiguration?.AppSsl ?? extensionConfiguration?.AppSsl;
if (appSsl == true)
{
proxy.Args += " --app-ssl";
}
var daprPlacementPort = serviceConfiguration?.PlacementPort ?? extensionConfiguration?.PlacementPort;
if (daprPlacementPort.HasValue) if (daprPlacementPort.HasValue)
{ {
@ -90,11 +115,21 @@ namespace Microsoft.Tye.Extensions.Dapr
proxy.Args += $" --placement-host-address localhost:{daprPlacementPort.Value}"; proxy.Args += $" --placement-host-address localhost:{daprPlacementPort.Value}";
} }
string? componentsPath = serviceConfiguration?.ComponentsPath ?? extensionConfiguration?.ComponentsPath;
if (componentsPath != null)
{
proxy.Args += $" --components-path {componentsPath}";
}
string? daprConfig = serviceConfiguration?.Config ?? extensionConfiguration?.Config;
// When running locally `-config` specifies a filename, not a configuration name. By convention // When running locally `-config` specifies a filename, not a configuration name. By convention
// we'll assume the filename and config name are the same. // we'll assume the filename and config name are the same.
if (config.Data.TryGetValue("config", out var obj) && obj?.ToString() is string daprConfig) if (daprConfig != null)
{ {
var configFile = Path.Combine(context.Application.Source.DirectoryName!, "components", $"{daprConfig}.yaml"); string configDirectory = componentsPath ?? Path.Combine(context.Application.Source.DirectoryName!, "components");
var configFile = Path.Combine(configDirectory, $"{daprConfig}.yaml");
if (File.Exists(configFile)) if (File.Exists(configFile))
{ {
proxy.Args += $" --config \"{configFile}\""; proxy.Args += $" --config \"{configFile}\"";
@ -105,15 +140,25 @@ namespace Microsoft.Tye.Extensions.Dapr
} }
} }
if (config.Data.TryGetValue("log-level", out obj) && obj?.ToString() is string logLevel) int? httpMaxRequestSize = serviceConfiguration?.HttpMaxRequestSize ?? extensionConfiguration?.HttpMaxRequestSize;
if (httpMaxRequestSize != null)
{ {
proxy.Args += $" --log-level {logLevel}"; proxy.Args += $" --dapr-http-max-request-size {httpMaxRequestSize}";
} }
if (config.Data.TryGetValue("components-path", out obj) && obj?.ToString() is string componentsPath) if ((serviceConfiguration?.EnableProfiling ?? extensionConfiguration?.EnableProfiling) == true)
{ {
proxy.Args += $" --components-path {componentsPath}"; proxy.Args += " --enable-profiling";
}
string? logLevel = serviceConfiguration?.LogLevel ?? extensionConfiguration?.LogLevel;
if (logLevel != null)
{
proxy.Args += $" --log-level {logLevel}";
} }
// Add dapr proxy as a service available to everyone. // Add dapr proxy as a service available to everyone.
proxy.Dependencies.UnionWith(context.Application.Services.Select(s => s.Name)); proxy.Dependencies.UnionWith(context.Application.Services.Select(s => s.Name));
@ -129,6 +174,7 @@ namespace Microsoft.Tye.Extensions.Dapr
{ {
Name = "grpc", Name = "grpc",
Protocol = "https", Protocol = "https",
Port = serviceConfiguration?.GrpcPort
}; };
proxy.Bindings.Add(grpc); proxy.Bindings.Add(grpc);
@ -137,6 +183,7 @@ namespace Microsoft.Tye.Extensions.Dapr
{ {
Name = "http", Name = "http",
Protocol = "http", Protocol = "http",
Port = serviceConfiguration?.HttpPort
}; };
proxy.Bindings.Add(http); proxy.Bindings.Add(http);
@ -145,6 +192,7 @@ namespace Microsoft.Tye.Extensions.Dapr
{ {
Name = "metrics", Name = "metrics",
Protocol = "http", Protocol = "http",
Port = serviceConfiguration?.MetricsPort
}; };
proxy.Bindings.Add(metrics); proxy.Bindings.Add(metrics);
@ -210,6 +258,29 @@ namespace Microsoft.Tye.Extensions.Dapr
}, },
}; };
proxy.EnvironmentVariables.Add(metricsPort); proxy.EnvironmentVariables.Add(metricsPort);
// TODO: Do we add a means of dynamically using the profile port?
if (serviceConfiguration?.ProfilePort != null)
{
proxy.Args += $" --profile-port %PROFILE_PORT%";
var profile = new BindingBuilder()
{
Name = "profile",
Protocol = "http",
Port = serviceConfiguration.ProfilePort
};
proxy.Bindings.Add(profile);
var profilePort = new EnvironmentVariableBuilder("PROFILE_PORT")
{
Source = new EnvironmentVariableSourceBuilder(proxy.Name, binding: "profile")
{
Kind = EnvironmentVariableSourceBuilder.SourceKind.Port,
},
};
proxy.EnvironmentVariables.Add(profilePort);
}
} }
} }
else else
@ -218,6 +289,10 @@ namespace Microsoft.Tye.Extensions.Dapr
var projects = context.Application.Services.OfType<ProjectServiceBuilder>(); var projects = context.Application.Services.OfType<ProjectServiceBuilder>();
foreach (var project in projects) foreach (var project in projects)
{ {
DaprExtensionServiceConfiguration? serviceConfiguration = null;
extensionConfiguration?.Services.TryGetValue(project.Name, out serviceConfiguration);
// Dapr requires http. If this project isn't listening to HTTP then it's not daprized. // Dapr requires http. If this project isn't listening to HTTP then it's not daprized.
var httpBinding = project.Bindings.FirstOrDefault(b => b.Protocol == "http"); var httpBinding = project.Bindings.FirstOrDefault(b => b.Protocol == "http");
if (httpBinding == null) if (httpBinding == null)
@ -230,16 +305,22 @@ namespace Microsoft.Tye.Extensions.Dapr
continue; continue;
} }
string appId = serviceConfiguration?.AppId ?? project.Name;
deployment.Annotations.Add("dapr.io/enabled", "true"); deployment.Annotations.Add("dapr.io/enabled", "true");
deployment.Annotations.Add("dapr.io/app-id", project.Name); deployment.Annotations.Add("dapr.io/app-id", appId);
deployment.Annotations.Add("dapr.io/app-port", (httpBinding.Port ?? 80).ToString(CultureInfo.InvariantCulture)); deployment.Annotations.Add("dapr.io/app-port", (httpBinding.Port ?? 80).ToString(CultureInfo.InvariantCulture));
if (config.Data.TryGetValue("config", out var obj) && obj?.ToString() is string daprConfig) string? daprConfig = serviceConfiguration?.Config ?? extensionConfiguration?.Config;
if (daprConfig != null)
{ {
deployment.Annotations.TryAdd("dapr.io/config", daprConfig); deployment.Annotations.TryAdd("dapr.io/config", daprConfig);
} }
if (config.Data.TryGetValue("log-level", out obj) && obj?.ToString() is string logLevel) string? logLevel = serviceConfiguration?.LogLevel ?? extensionConfiguration?.LogLevel;
if (logLevel != null)
{ {
deployment.Annotations.TryAdd("dapr.io/log-level", logLevel); deployment.Annotations.TryAdd("dapr.io/log-level", logLevel);
} }

13
src/Microsoft.Tye.Extensions/Dapr/DaprExtensionConfiguration.cs

@ -8,12 +8,25 @@ namespace Microsoft.Tye.Extensions.Dapr
{ {
internal abstract class DaprExtensionCommonConfiguration internal abstract class DaprExtensionCommonConfiguration
{ {
public int? AppMaxConcurrency { get; set; }
public string? AppProtocol { get; set; }
public bool? AppSsl { get; set; }
public string? ComponentsPath { get; set; }
public string? Config { get; set; }
public bool? EnableProfiling { get; set; }
public int? HttpMaxRequestSize { get; set; }
public string? LogLevel { get; set; }
public int? PlacementPort { get; set; } public int? PlacementPort { get; set; }
} }
internal sealed class DaprExtensionServiceConfiguration : DaprExtensionCommonConfiguration internal sealed class DaprExtensionServiceConfiguration : DaprExtensionCommonConfiguration
{ {
public string? AppId { get; set; }
public bool? Enabled { get; set; } public bool? Enabled { get; set; }
public int? GrpcPort { get; set; }
public int? HttpPort { get; set; }
public int? MetricsPort { get; set; }
public int? ProfilePort { get; set; }
} }
internal sealed class DaprExtensionConfiguration : DaprExtensionCommonConfiguration internal sealed class DaprExtensionConfiguration : DaprExtensionCommonConfiguration

44
src/Microsoft.Tye.Extensions/Dapr/DaprExtensionConfigurationReader.cs

@ -41,18 +41,50 @@ namespace Microsoft.Tye.Extensions.Dapr
{ {
ReadCommonConfiguration(rawConfiguration, serviceConfiguration); ReadCommonConfiguration(rawConfiguration, serviceConfiguration);
if (rawConfiguration.TryGetValue("enabled", out var obj) && obj is string && Boolean.TryParse(obj.ToString(), out var enabled)) serviceConfiguration.AppId = TryGetValue(rawConfiguration, "app-id");
{ serviceConfiguration.Enabled = TryGetValue<bool>(rawConfiguration, "enabled");
serviceConfiguration.Enabled = enabled; serviceConfiguration.GrpcPort = TryGetValue<int>(rawConfiguration, "grpc-port");
} serviceConfiguration.HttpPort = TryGetValue<int>(rawConfiguration, "http-port");
serviceConfiguration.MetricsPort = TryGetValue<int>(rawConfiguration, "metrics-port");
serviceConfiguration.ProfilePort = TryGetValue<int>(rawConfiguration, "profile-port");
} }
private static void ReadCommonConfiguration(IDictionary<string, object> rawConfiguration, DaprExtensionCommonConfiguration commonConfiguration) private static void ReadCommonConfiguration(IDictionary<string, object> rawConfiguration, DaprExtensionCommonConfiguration commonConfiguration)
{ {
if (rawConfiguration.TryGetValue("placement-port", out var obj) && obj?.ToString() is string && int.TryParse(obj.ToString(), out var customPlacementPort)) commonConfiguration.AppMaxConcurrency = TryGetValue<int>(rawConfiguration, "app-max-concurrency");
commonConfiguration.AppProtocol = TryGetValue(rawConfiguration, "app-protocol");
commonConfiguration.AppSsl = TryGetValue<bool>(rawConfiguration, "app-ssl");
commonConfiguration.ComponentsPath = TryGetValue(rawConfiguration, "components-path");
commonConfiguration.Config = TryGetValue(rawConfiguration, "config");
commonConfiguration.EnableProfiling = TryGetValue<bool>(rawConfiguration, "enable-profiling");
commonConfiguration.HttpMaxRequestSize = TryGetValue<int>(rawConfiguration, "http-max-request-size");
commonConfiguration.LogLevel = TryGetValue(rawConfiguration, "log-level");
commonConfiguration.PlacementPort = TryGetValue<int>(rawConfiguration, "placement-port");
}
private static string? TryGetValue(IDictionary<string, object> rawConfiguration, string name)
{
return rawConfiguration.TryGetValue(name, out var obj) && obj?.ToString() is string
? obj.ToString()
: null;
}
private static T? TryGetValue<T>(IDictionary<string, object> rawConfiguration, string name)
where T : struct
{
if (rawConfiguration.TryGetValue(name, out var obj) && obj?.ToString() is string)
{ {
commonConfiguration.PlacementPort = customPlacementPort; try
{
return (T?)Convert.ChangeType(obj.ToString(), typeof(T));
}
catch
{
// No-op.
}
} }
return null;
} }
} }
} }

Loading…
Cancel
Save