Skip to content
This repository has been archived by the owner on Nov 20, 2023. It is now read-only.

Commit

Permalink
Feed update master (#973)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkotalik authored Mar 16, 2021
1 parent 44c9cc1 commit f4a220d
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 208 deletions.
4 changes: 1 addition & 3 deletions NuGet.config
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
<clear />
<add key="dotnet-eng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" />
<add key="dotnet-tools" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" />
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
<add key="featherhttp" value="https://f.feedz.io/davidfowl/featherhttp/nuget/index.json" />
<add key="bedrockframework" value="https://f.feedz.io/davidfowl/bedrockframework/nuget/index.json" />
<add key="dotnet-public" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json" />
</packageSources>
<disabledPackageSources />
</configuration>
2 changes: 1 addition & 1 deletion src/Microsoft.Tye.Core/Microsoft.Tye.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="KubernetesClient" Version="4.0.16" />
<PackageReference Include="KubernetesClient" Version="4.0.10" />
<!--
The Microsoft.Build.Locator package takes care of dynamically loading these assemblies
at runtime. We don't need/want to ship them, just to have them as references.
Expand Down
248 changes: 141 additions & 107 deletions src/Microsoft.Tye.Hosting/HttpProxyService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,21 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Proxy;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Tye.Hosting.Model;

namespace Microsoft.Tye.Hosting
{
public partial class HttpProxyService : IApplicationProcessor
{
private List<WebApplication> _webApplications = new List<WebApplication>();
private List<IHost> _webApplications = new List<IHost>();
private readonly ILogger _logger;

private ConcurrentDictionary<int, bool> _readyPorts;
Expand All @@ -50,126 +52,158 @@ public async Task StartAsync(Application application)

if (service.Description.RunInfo is IngressRunInfo runInfo)
{
var builder = new WebApplicationBuilder();

builder.Services.AddSingleton<MatcherPolicy, IngressHostMatcherPolicy>();

builder.Logging.AddProvider(new ServiceLoggerProvider(service.Logs));

var addresses = new List<string>();

// Bind to the addresses on this resource
for (int i = 0; i < serviceDescription.Replicas; i++)
{
// Fake replicas since it's all running processes
var replica = service.Description.Name + "_" + Guid.NewGuid().ToString().Substring(0, 10).ToLower();
var status = new IngressStatus(service, replica);
service.Replicas[replica] = status;

var ports = new List<int>();

foreach (var binding in serviceDescription.Bindings)
{
if (binding.Port == null)
{
continue;
}

var port = binding.ReplicaPorts[i];
ports.Add(port);
var url = $"{binding.Protocol}://localhost:{port}";
addresses.Add(url);
}

status.Ports = ports;

service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Added, status));
}

builder.Server.UseUrls(addresses.ToArray());
var webApp = builder.Build();

_webApplications.Add(webApp);

// For each ingress rule, bind to the path and host
foreach (var rule in runInfo.Rules)
{
if (!application.Services.TryGetValue(rule.Service, out var target))
{
continue;
}

_logger.LogInformation("Processing ingress rule: Path:{Path}, Host:{Host}, Service:{Service}", rule.Path, rule.Host, rule.Service);

var targetServiceDescription = target.Description;
RegisterListener(target);

var uris = new List<(int Port, Uri Uri)>();

// HTTP before HTTPS (this might change once we figure out certs...)
var targetBinding = targetServiceDescription.Bindings.FirstOrDefault(b => b.Protocol == "http") ??
targetServiceDescription.Bindings.FirstOrDefault(b => b.Protocol == "https");

if (targetBinding == null)
var host = Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder =>
{
_logger.LogInformation("Service {ServiceName} does not have any HTTP or HTTPs bindings", targetServiceDescription.Name);
continue;
}
var urls = new List<string>();
// For each of the target service replicas, get the base URL
// based on the replica port
for (int i = 0; i < targetServiceDescription.Replicas; i++)
{
var port = targetBinding.ReplicaPorts[i];
var url = $"{targetBinding.Protocol}://localhost:{port}";
uris.Add((port, new Uri(url)));
}

_logger.LogInformation("Service {ServiceName} is using {Urls}", targetServiceDescription.Name, string.Join(",", uris.Select(u => u.ToString())));
// Bind to the addresses on this resource
for (int i = 0; i < serviceDescription.Replicas; i++)
{
// Fake replicas since it's all running processes
var replica = service.Description.Name + "_" + Guid.NewGuid().ToString().Substring(0, 10).ToLower();
var status = new IngressStatus(service, replica);
service.Replicas[replica] = status;
// The only load balancing strategy here is round robin
long count = 0;
RequestDelegate del = async context =>
{
var next = (int)(Interlocked.Increment(ref count) % uris.Count);
var ports = new List<int>();
// we find the first `Ready` port
for (int i = 0; i < uris.Count; i++)
{
if (_readyPorts.ContainsKey(uris[next].Port))
foreach (var binding in serviceDescription.Bindings)
{
break;
if (binding.Port == null)
{
continue;
}
var port = binding.ReplicaPorts[i];
ports.Add(port);
var url = $"{binding.Protocol}://localhost:{port}";
urls.Add(url);
}
next = (int)(Interlocked.Increment(ref count) % uris.Count);
}
status.Ports = ports;
// if we've looped through all the port and didn't find a single one that is `Ready`, we return HTTP BadGateway
if (!_readyPorts.ContainsKey(uris[next].Port))
{
context.Response.StatusCode = (int)HttpStatusCode.BadGateway;
await context.Response.WriteAsync("Bad gateway");
return;
service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Added, status));
}
var uri = new UriBuilder(uris[next].Uri)
builder.ConfigureServices(services =>
{
Path = rule.PreservePath ? $"{context.Request.Path}" : (string)context.Request.RouteValues["path"] ?? "/",
Query = context.Request.QueryString.Value
};
services.AddSingleton<MatcherPolicy, IngressHostMatcherPolicy>();
services.AddLogging(loggingBuilder =>
{
loggingBuilder.AddProvider(new ServiceLoggerProvider(service.Logs));
});
await context.ProxyRequest(invoker, uri.Uri);
};
services.Configure<IServerAddressesFeature>(serverAddresses =>
{
var addresses = serverAddresses.Addresses;
if (addresses.IsReadOnly)
{
throw new NotSupportedException("Changing the URL isn't supported.");
}
addresses.Clear();
foreach (var u in urls)
{
addresses.Add(u);
}
});
});
builder.UseUrls(urls.ToArray());
builder.Configure(app =>
{
app.UseRouting();
IEndpointConventionBuilder conventions =
((IEndpointRouteBuilder)webApp).Map((rule.Path?.TrimEnd('/') ?? "") + "/{**path}", del);
app.UseEndpoints(endpointBuilder =>
{
foreach (var rule in runInfo.Rules)
{
if (!application.Services.TryGetValue(rule.Service, out var target))
{
continue;
}
_logger.LogInformation("Processing ingress rule: Path:{Path}, Host:{Host}, Service:{Service}", rule.Path, rule.Host, rule.Service);
var targetServiceDescription = target.Description;
RegisterListener(target);
var uris = new List<(int Port, Uri Uri)>();
// HTTP before HTTPS (this might change once we figure out certs...)
var targetBinding = targetServiceDescription.Bindings.FirstOrDefault(b => b.Protocol == "http") ??
targetServiceDescription.Bindings.FirstOrDefault(b => b.Protocol == "https");
if (targetBinding == null)
{
_logger.LogInformation("Service {ServiceName} does not have any HTTP or HTTPs bindings", targetServiceDescription.Name);
continue;
}
// For each of the target service replicas, get the base URL
// based on the replica port
for (int i = 0; i < targetServiceDescription.Replicas; i++)
{
var port = targetBinding.ReplicaPorts[i];
var url = $"{targetBinding.Protocol}://localhost:{port}";
uris.Add((port, new Uri(url)));
}
_logger.LogInformation("Service {ServiceName} is using {Urls}", targetServiceDescription.Name, string.Join(",", uris.Select(u => u.ToString())));
// The only load balancing strategy here is round robin
long count = 0;
RequestDelegate del = async context =>
{
var next = (int)(Interlocked.Increment(ref count) % uris.Count);
// we find the first `Ready` port
for (int i = 0; i < uris.Count; i++)
{
if (_readyPorts.ContainsKey(uris[next].Port))
{
break;
}
next = (int)(Interlocked.Increment(ref count) % uris.Count);
}
// if we've looped through all the port and didn't find a single one that is `Ready`, we return HTTP BadGateway
if (!_readyPorts.ContainsKey(uris[next].Port))
{
context.Response.StatusCode = (int)HttpStatusCode.BadGateway;
await context.Response.WriteAsync("Bad gateway");
return;
}
var uri = new UriBuilder(uris[next].Uri)
{
Path = rule.PreservePath ? $"{context.Request.Path}" : (string)context.Request.RouteValues["path"] ?? "/",
Query = context.Request.QueryString.Value
};
await context.ProxyRequest(invoker, uri.Uri);
};
IEndpointConventionBuilder conventions =
endpointBuilder.Map((rule.Path?.TrimEnd('/') ?? "") + "/{**path}", del);
if (rule.Host != null)
{
conventions.WithMetadata(new IngressHostMetadata(rule.Host));
}
conventions.WithDisplayName(rule.Service);
}
});
});
});


var webApp = host.Build();

if (rule.Host != null)
{
conventions.WithMetadata(new IngressHostMetadata(rule.Host));
}
_webApplications.Add(webApp);

conventions.WithDisplayName(rule.Service);
}
// For each ingress rule, bind to the path and host

await webApp.StartAsync();

Expand Down
5 changes: 2 additions & 3 deletions src/Microsoft.Tye.Hosting/Microsoft.Tye.Hosting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,11 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Bedrock.Framework" Version="0.1.38-alpha.gd25d5b37ad" />
<PackageReference Include="FeatherHttp" Version="0.1.42-alpha.gf06a8747e7" />
<PackageReference Include="Bedrock.Framework" Version="0.1.62-alpha.g8a965bbcba" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="3.1.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="3.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="System.IO.Pipelines" Version="4.7.0" />
<PackageReference Include="System.IO.Pipelines" Version="4.7.2" />
<PackageReference Include="System.Reactive" Version="4.3.2" />
<PackageReference Include="YamlDotNet" Version="8.1.2" />
</ItemGroup>
Expand Down
Loading

0 comments on commit f4a220d

Please sign in to comment.