Browse Source

Add concept of services have direct dependencies (#414)

davidfowl/ingress-changes
Justin Kotalik 7 years ago
committed by GitHub
parent
commit
ba1a5c2f36
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 30
      src/Microsoft.Tye.Core/ApplicationFactory.cs
  2. 8
      src/Microsoft.Tye.Core/CombineStep.cs
  3. 2
      src/Microsoft.Tye.Core/ServiceBuilder.cs
  4. 4
      src/Microsoft.Tye.Hosting/Model/Application.cs
  5. 1
      src/Microsoft.Tye.Hosting/Model/ServiceDescription.cs
  6. 7
      src/Microsoft.Tye.Hosting/Properties/AssemblyInfo.cs
  7. 3
      src/tye/ApplicationBuilderExtensions.cs
  8. 2
      test/E2ETest/ApplicationFactoryTests.cs
  9. 43
      test/E2ETest/ApplicationTests.cs
  10. 2
      test/E2ETest/Microsoft.Tye.E2ETests.csproj
  11. 38
      test/E2ETest/TyeGenerateTests.cs
  12. 1
      test/E2ETest/TyeRunTests.cs
  13. 113
      test/E2ETest/testassets/generate/multirepo.yaml
  14. 2
      test/E2ETest/testassets/projects/multirepo/results/tye.yaml
  15. 5
      test/E2ETest/testassets/projects/multirepo/vote/tye.yaml
  16. 5
      test/UnitTests/TyeDeserializationValidationTests.cs

30
src/Microsoft.Tye.Core/ApplicationFactory.cs

@ -21,7 +21,7 @@ namespace Microsoft.Tye
throw new ArgumentNullException(nameof(source));
}
var queue = new Queue<ConfigApplication>();
var queue = new Queue<(ConfigApplication, HashSet<string>)>();
var visited = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var rootConfig = ConfigFactory.FromFile(source);
@ -29,11 +29,14 @@ namespace Microsoft.Tye
var root = new ApplicationBuilder(source, rootConfig.Name ?? source.Directory.Name.ToLowerInvariant());
root.Namespace = rootConfig.Namespace;
queue.Enqueue(rootConfig);
queue.Enqueue((rootConfig, new HashSet<string>()));
while (queue.Count > 0)
{
var config = queue.Dequeue();
var item = queue.Dequeue();
var config = item.Item1;
var parentDependencies = item.Item2;
if (!visited.Add(config.Source.FullName))
{
continue;
@ -70,6 +73,7 @@ namespace Microsoft.Tye
ServiceBuilder service;
if (root.Services.Any(s => s.Name == configService.Name))
{
AddToRootServices(root, parentDependencies, configService, configService.Name);
// Don't add a service which has already been added by name
continue;
}
@ -140,8 +144,9 @@ namespace Microsoft.Tye
throw new CommandException($"Nested configuration must have the same \"name\" in the tye.yaml. Root config: {rootConfig.Source}, nested config: {nestedConfig.Source}");
}
queue.Enqueue(nestedConfig);
queue.Enqueue((nestedConfig, new HashSet<string>()));
AddToRootServices(root, parentDependencies, configService, configService.Name);
continue;
}
@ -155,6 +160,11 @@ namespace Microsoft.Tye
throw new CommandException("Unable to determine service type.");
}
service.Dependencies.AddRange(parentDependencies);
parentDependencies.Add(service.Name);
AddToRootServices(root, parentDependencies, configService, service.Name);
root.Services.Add(service);
// If there are no bindings and we're in ASP.NET Core project then add an HTTP and HTTPS binding
@ -274,5 +284,17 @@ namespace Microsoft.Tye
return root;
}
private static void AddToRootServices(ApplicationBuilder root, HashSet<string> parentDependencies, ConfigService configService, string serviceName)
{
parentDependencies.Add(serviceName);
foreach (var s in root.Services)
{
if (parentDependencies.Contains(s.Name, StringComparer.OrdinalIgnoreCase) && !s.Name.Equals(configService.Name, StringComparison.OrdinalIgnoreCase))
{
s.Dependencies.Add(serviceName);
}
}
}
}
}

8
src/Microsoft.Tye.Core/CombineStep.cs

@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.Tye
@ -51,12 +52,9 @@ namespace Microsoft.Tye
var bindings = new ComputedBindings();
service.Outputs.Add(bindings);
foreach (var other in application.Services)
foreach (var o in service.Dependencies)
{
if (object.ReferenceEquals(service, other))
{
continue;
}
var other = application.Services.Single(a => a.Name == o);
foreach (var binding in other.Bindings)
{

2
src/Microsoft.Tye.Core/ServiceBuilder.cs

@ -15,5 +15,7 @@ namespace Microsoft.Tye
// TODO: this is temporary while refactoring
public List<ServiceOutput> Outputs { get; } = new List<ServiceOutput>();
public List<string> Dependencies { get; } = new List<string>();
}
}

4
src/Microsoft.Tye.Hosting/Model/Application.cs

@ -132,8 +132,10 @@ namespace Microsoft.Tye.Hosting.Model
{
var bindings = new List<EffectiveBinding>();
foreach (var s in Services.Values)
foreach (var serv in service.Description.Dependencies)
{
var s = Services[serv];
foreach (var b in s.Description.Bindings)
{
var protocol = b.Protocol;

1
src/Microsoft.Tye.Hosting/Model/ServiceDescription.cs

@ -19,5 +19,6 @@ namespace Microsoft.Tye.Hosting.Model
public int Replicas { get; set; } = 1;
public List<ServiceBinding> Bindings { get; } = new List<ServiceBinding>();
public List<EnvironmentVariable> Configuration { get; } = new List<EnvironmentVariable>();
public List<string> Dependencies { get; } = new List<string>();
}
}

7
src/Microsoft.Tye.Hosting/Properties/AssemblyInfo.cs

@ -0,0 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.Tye.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]

3
src/tye/ApplicationBuilderExtensions.cs

@ -31,6 +31,7 @@ namespace Microsoft.Tye
public static Application ToHostingApplication(this ApplicationBuilder application)
{
var services = new Dictionary<string, Service>();
foreach (var service in application.Services)
{
RunInfo? runInfo;
@ -104,7 +105,9 @@ namespace Microsoft.Tye
{
Replicas = replicas,
};
description.Configuration.AddRange(env);
description.Dependencies.AddRange(service.Dependencies);
foreach (var binding in service.Bindings)
{

2
test/UnitTests/ApplicationFactoryTests.cs → test/E2ETest/ApplicationFactoryTests.cs

@ -6,7 +6,7 @@ using Test.Infrastructure;
using Xunit;
using Xunit.Abstractions;
namespace UnitTests
namespace E2ETest
{
public class ApplicationFactoryTests
{

43
test/E2ETest/ApplicationTests.cs

@ -0,0 +1,43 @@
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Tye;
using Test.Infrastructure;
using Xunit;
using Xunit.Abstractions;
namespace E2ETest
{
public class ApplicationTests
{
private readonly TestOutputLogEventSink _sink;
public ApplicationTests(ITestOutputHelper output)
{
_sink = new TestOutputLogEventSink(output);
}
[Fact]
public async Task EnvironmentVariablesOnlySetForDirectDependencies()
{
using var projectDirectory = TestHelpers.CopyTestProjectDirectory(Path.Combine("multirepo"));
var yamlFile = Path.Combine(projectDirectory.DirectoryPath, "results", "tye.yaml");
// Debug targets can be null if not specified, so make sure calling host.Start does not throw.
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, new FileInfo(yamlFile));
var app = application.ToHostingApplication();
var dictionary = new Dictionary<string, string>();
app.PopulateEnvironment(app.Services["results"], (s1, s2) => dictionary[s1] = s2);
// Just the WORKER is defined.
Assert.Equal(8, dictionary.Count);
Assert.Equal("http", dictionary["SERVICE__WORKER__PROTOCOL"]);
// No POSTGRES or REDIS
Assert.False(dictionary.ContainsKey("SERVICE__POSTGRES__PROTOCOL"));
Assert.False(dictionary.ContainsKey("SERVICE__REDIS__PROTOCOL"));
}
}
}

2
test/E2ETest/Microsoft.Tye.E2ETests.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>

38
test/E2ETest/TyeGenerateTests.cs

@ -318,6 +318,44 @@ namespace E2ETest
}
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task Generate_DirectDependencyForEnvVars()
{
var applicationName = "multirepo";
var projectName = "results";
var otherProject = "worker";
var environment = "production";
await DockerAssert.DeleteDockerImagesAsync(output, projectName);
await DockerAssert.DeleteDockerImagesAsync(output, otherProject);
using var projectDirectory = TestHelpers.CopyTestProjectDirectory(applicationName);
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "results", "tye.yaml"));
var outputContext = new OutputContext(sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
try
{
await GenerateHost.ExecuteGenerateAsync(outputContext, application, environment, interactive: false);
// name of application is the folder
var content = await File.ReadAllTextAsync(Path.Combine(projectDirectory.DirectoryPath, "results", $"VotingSample-generate-{environment}.yaml"));
var expectedContent = await File.ReadAllTextAsync($"testassets/generate/{applicationName}.yaml");
YamlAssert.Equals(expectedContent, content, output);
await DockerAssert.AssertImageExistsAsync(output, projectName);
await DockerAssert.AssertImageExistsAsync(output, otherProject);
}
finally
{
await DockerAssert.DeleteDockerImagesAsync(output, projectName);
await DockerAssert.DeleteDockerImagesAsync(output, otherProject);
}
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task Generate_Ingress()

1
test/E2ETest/TyeRunTests.cs

@ -210,6 +210,7 @@ namespace E2ETest
var outputFileName = project.AssemblyName + ".dll";
var container = new ContainerServiceBuilder(project.Name, $"mcr.microsoft.com/dotnet/core/sdk:{project.TargetFrameworkVersion}");
container.Dependencies.AddRange(project.Dependencies);
container.Volumes.Add(new VolumeBuilder(project.PublishDir, name: null, target: "/app"));
container.Args = $"dotnet /app/{outputFileName} {project.Args}";
container.Bindings.AddRange(project.Bindings);

113
test/E2ETest/testassets/generate/multirepo.yaml

@ -0,0 +1,113 @@
kind: Deployment
apiVersion: apps/v1
metadata:
name: results
labels:
app.kubernetes.io/name: 'results'
app.kubernetes.io/part-of: 'VotingSample'
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: results
template:
metadata:
labels:
app.kubernetes.io/name: 'results'
app.kubernetes.io/part-of: 'VotingSample'
spec:
containers:
- name: results
image: results:1.0.0
imagePullPolicy: Always
env:
- name: ASPNETCORE_URLS
value: 'http://*'
- name: PORT
value: '80'
- name: SERVICE__WORKER__PROTOCOL
value: 'http'
- name: SERVICE__WORKER__PORT
value: '80'
- name: SERVICE__WORKER__HOST
value: 'worker'
ports:
- containerPort: 80
...
---
kind: Service
apiVersion: v1
metadata:
name: results
labels:
app.kubernetes.io/name: 'results'
app.kubernetes.io/part-of: 'VotingSample'
spec:
selector:
app.kubernetes.io/name: results
type: ClusterIP
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
...
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: worker
labels:
app.kubernetes.io/name: 'worker'
app.kubernetes.io/part-of: 'VotingSample'
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: worker
template:
metadata:
labels:
app.kubernetes.io/name: 'worker'
app.kubernetes.io/part-of: 'VotingSample'
spec:
containers:
- name: worker
image: worker:1.0.0
imagePullPolicy: Always
env:
- name: ASPNETCORE_URLS
value: 'http://*'
- name: PORT
value: '80'
- name: CONNECTIONSTRINGS__REDIS
valueFrom:
secretKeyRef:
name: 'binding-production-redis-secret'
key: 'connectionstring'
- name: CONNECTIONSTRINGS__POSTGRES
valueFrom:
secretKeyRef:
name: 'binding-production-postgres-secret'
key: 'connectionstring'
ports:
- containerPort: 80
...
---
kind: Service
apiVersion: v1
metadata:
name: worker
labels:
app.kubernetes.io/name: 'worker'
app.kubernetes.io/part-of: 'VotingSample'
spec:
selector:
app.kubernetes.io/name: worker
type: ClusterIP
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
...

2
test/E2ETest/testassets/projects/multirepo/results/tye.yaml

@ -1,6 +1,6 @@
name: VotingSample
services:
- name: include
- name: worker
include: ../worker/tye.yaml
- name: results
project: results.csproj

5
test/E2ETest/testassets/projects/multirepo/vote/tye.yaml

@ -4,3 +4,8 @@ services:
project: vote.csproj
- name: worker
include: ../worker/tye.yaml
- name: redis
image: redis
bindings:
- port: 6379
connectionString: ${host}:${port}

5
test/UnitTests/TyeDeserializationValidationTests.cs

@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
using Tye;
using Tye;
using Tye.Serialization;
using Xunit;

Loading…
Cancel
Save