示例#1
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="rule">The associated traffic manager rule.</param>
        /// <param name="frontend">The frontend that generated this mapping.</param>
        /// <param name="backendName">The backend name.</param>
        public HostPathMapping(TrafficHttpRule rule, TrafficHttpFrontend frontend, string backendName)
        {
            Covenant.Requires <ArgumentNullException>(rule != null);
            Covenant.Requires <ArgumentNullException>(frontend != null);
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(backendName));

            this.Rule        = rule;
            this.Frontend    = frontend;
            this.BackendName = backendName;
        }
示例#2
0
        //---------------------------------------------------------------------
        // Instance members.

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="frontend">The associated frontend.</param>
        public HAProxyHttpFrontend(TrafficHttpFrontend frontend)
        {
            Covenant.Requires <ArgumentNullException>(frontend != null);

            this.Frontend = frontend;
        }
示例#3
0
        /// <summary>
        /// Verify that we can create HTTPS traffic manager rules for a
        /// site on the proxy port using a specific hostname and various
        /// path prefixes and then verify that that the traffic manager actually
        /// works  by spinning up a [vegomatic] based service to accept the traffic.
        /// </summary>
        /// <param name="testName">Simple name (without spaces) used to ensure that URIs cached for different tests won't conflict.</param>
        /// <param name="proxyPort">The inbound proxy port.</param>
        /// <param name="network">The proxy network.</param>
        /// <param name="trafficManager">The traffic manager.</param>
        /// <param name="useCache">Optionally enable caching and verify.</param>
        /// <param name="serviceName">Optionally specifies the backend service name prefix (defaults to <b>vegomatic</b>).</param>
        /// <returns>The tracking <see cref="Task"/>.</returns>
        private async Task TestHttpsPrefix(string testName, int proxyPort, string network, TrafficManager trafficManager, bool useCache = false, string serviceName = "vegomatic")
        {
            // Append a GUID to the test name to ensure that we won't
            // conflict with what any previous test runs may have loaded
            // into the cache.

            testName += "-" + Guid.NewGuid().ToString("D");

            // Verify that we can create an HTTP traffic manager rule for a
            // site on the proxy port using a specific hostname and then
            // verify that that the traffic manager actually works by spinning
            // up a [vegomatic] based service to accept the traffic.

            var manager  = hive.GetReachableManager();
            var hostname = testHostname;

            manager.Connect();

            // Allow self-signed certificates.

            var handler = new HttpClientHandler()
            {
                ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
            };

            using (var client = new TestHttpClient(disableConnectionReuse: true, handler: handler, disposeHandler: true))
            {
                // Add the test certificate.

                hive.Certificate.Set("test-load-balancer", certificate);

                // Setup the client to query the [vegomatic] service through the
                // proxy without needing to configure a hive DNS entry.

                client.BaseAddress = new Uri($"https://{manager.PrivateAddress}:{proxyPort}/");
                client.DefaultRequestHeaders.Host = hostname;

                // Create the traffic manager rules, one without a path prefix and
                // some others, some with intersecting prefixes so we can verify
                // that the longest prefixes are matched first.
                //
                // Each rule's backend will be routed to a service whose name
                // will be constructed from [testName] plus the prefix with the
                // slashes replaced with dashes.  Each service will be configured
                // to return its name.

                var prefixes = new PrefixInfo[]
                {
                    new PrefixInfo("/", $"{serviceName}"),
                    new PrefixInfo("/foo/", $"{serviceName}-foo"),
                    new PrefixInfo("/foo/bar/", $"{serviceName}-foo-bar"),
                    new PrefixInfo("/foobar/", $"{serviceName}-foobar"),
                    new PrefixInfo("/bar/", $"{serviceName}-bar")
                };

                // Spin the services up first in parallel (for speed).  Each of
                // these service will respond to requests with its service name.

                var tasks = new List <Task>();

                foreach (var prefix in prefixes)
                {
                    tasks.Add(Task.Run(
                                  () =>
                    {
                        manager.SudoCommand($"docker service create --name {prefix.ServiceName} --network {network} --replicas 1 {vegomaticImage} test-server server-id={prefix.ServiceName}").EnsureSuccess();
                    }));
                }

                await NeonHelper.WaitAllAsync(tasks, TimeSpan.FromSeconds(30));

                // Create the traffic manager rules.

                foreach (var prefix in prefixes)
                {
                    var rule = new TrafficHttpRule()
                    {
                        Name         = prefix.ServiceName,
                        CheckExpect  = "status 200",
                        CheckSeconds = 1,
                    };

                    if (useCache)
                    {
                        rule.Cache = new TrafficHttpCache()
                        {
                            Enabled = true
                        };
                    }

                    var frontend = new TrafficHttpFrontend()
                    {
                        Host      = hostname,
                        ProxyPort = proxyPort,
                        CertName  = "test-load-balancer"
                    };

                    if (!string.IsNullOrEmpty(prefix.Path))
                    {
                        frontend.PathPrefix = prefix.Path;
                    }

                    rule.Frontends.Add(frontend);

                    rule.Backends.Add(
                        new TrafficHttpBackend()
                    {
                        Server = prefix.ServiceName,
                        Port   = 80
                    });

                    trafficManager.SetRule(rule, deferUpdate: true);
                }

                trafficManager.Update();

                // Wait for all of the services to report being ready.

                await NeonHelper.WaitForAsync(
                    async() =>
                {
                    foreach (var prefix in prefixes)
                    {
                        try
                        {
                            var response = await client.GetAsync(prefix.Path);

                            response.EnsureSuccessStatusCode();
                        }
                        catch
                        {
                            return(false);
                        }
                    }

                    return(true);
                },
                    timeout : TimeSpan.FromSeconds(60),
                    pollTime : TimeSpan.FromSeconds(1));

                // Give everything a chance to stablize.

                await Task.Delay(TimeSpan.FromSeconds(5));

                // Now verify that prefix rules route to the correct backend service.

                foreach (var prefix in prefixes)
                {
                    var response = await client.GetAsync($"{prefix.Path}{testName}?expires=60");

                    response.EnsureSuccessStatusCode();

                    var body = await response.Content.ReadAsStringAsync();

                    Assert.Equal(prefix.ServiceName, body.Trim());

                    if (useCache)
                    {
                        // Verify that the request routed through Varnish.

                        Assert.True(ViaVarnish(response));

                        // This is the first request using the globally unique [testName]
                        // so it should not be a cache hit.

                        Assert.False(CacheHit(response));
                    }
                }

                // If caching is enabled, perform the requests again to ensure that
                // we see cache hits.

                if (useCache)
                {
                    foreach (var prefix in prefixes)
                    {
                        // Request the item again and verify that it was a cache hit.

                        var response = await client.GetAsync($"{prefix.Path}{testName}?expires=60");

                        response.EnsureSuccessStatusCode();

                        var body = await response.Content.ReadAsStringAsync();

                        Assert.Equal(prefix.ServiceName, body.Trim());
                        Assert.True(CacheHit(response));
                    }
                }
            }
        }
示例#4
0
        /// <summary>
        /// Generates a traffic manager rule for each <see cref="Redirection"/> passed that
        /// will redirect from one URI to another.
        /// </summary>
        /// <param name="trafficManager">The target traffic manager.</param>
        /// <param name="testName">Used to name the traffic manager rules.</param>
        /// <param name="singleRule">
        /// Pass <c>true</c> to test a single rule with all of the redirections or
        /// <c>false</c> to test with one redirection per rule.
        /// </param>
        /// <param name="redirections">The redirections.</param>
        private async Task TestRedirect(TrafficManager trafficManager, string testName, bool singleRule, params Redirection[] redirections)
        {
            var manager = hive.GetReachableManager();

            // We need local DNS mappings for each of the URI hosts to target a hive node.

            var hosts = new HashSet <string>(StringComparer.InvariantCultureIgnoreCase);

            foreach (var redirect in redirections)
            {
                if (!hosts.Contains(redirect.FromUri.Host))
                {
                    hosts.Add(redirect.FromUri.Host);
                }

                if (!hosts.Contains(redirect.ToUri.Host))
                {
                    hosts.Add(redirect.ToUri.Host);
                }
            }

            foreach (var host in hosts)
            {
                hiveFixture.LocalMachineHosts.AddHostAddress(host, manager.PrivateAddress.ToString(), deferCommit: true);
            }

            hiveFixture.LocalMachineHosts.Commit();

            // Generate and upload a self-signed certificate for each redirect host that
            // uses HTTPS and upload these to the hive.  Each certificate will be named
            // the same as the hostname.

            var hostToCertificate = new Dictionary <string, TlsCertificate>(StringComparer.InvariantCultureIgnoreCase);

            foreach (var redirect in redirections.Where(r => r.FromUri.Scheme == "https"))
            {
                var host = redirect.FromUri.Host;

                if (hostToCertificate.ContainsKey(host))
                {
                    continue;
                }

                hostToCertificate[host] = TlsCertificate.CreateSelfSigned(host);
            }

            foreach (var item in hostToCertificate)
            {
                hive.Certificate.Set(item.Key, item.Value);
            }

            // Create the traffic manager rule(s).

            if (singleRule)
            {
                var rule = new TrafficHttpRule()
                {
                    Name = testName,
                };

                foreach (var redirect in redirections)
                {
                    var frontend = new TrafficHttpFrontend()
                    {
                        Host       = redirect.FromUri.Host,
                        ProxyPort  = redirect.FromUri.Port,
                        RedirectTo = redirect.ToUri
                    };

                    if (redirect.FromUri.Scheme == "https")
                    {
                        frontend.CertName = redirect.FromUri.Host;
                    }

                    rule.Frontends.Add(frontend);
                }

                trafficManager.SetRule(rule);
            }
            else
            {
                var redirectIndex = 0;

                foreach (var redirect in redirections)
                {
                    var rule = new TrafficHttpRule()
                    {
                        Name = $"{testName}-{redirectIndex}",
                    };

                    var frontend = new TrafficHttpFrontend()
                    {
                        Host       = redirect.FromUri.Host,
                        ProxyPort  = redirect.FromUri.Port,
                        RedirectTo = redirect.ToUri
                    };

                    if (redirect.FromUri.Scheme == "https")
                    {
                        frontend.CertName = redirect.FromUri.Host;
                    }

                    rule.Frontends.Add(frontend);
                    trafficManager.SetRule(rule);
                    redirectIndex++;
                }
            }

            // Give the new rules some time to deploy.

            await Task.Delay(TimeSpan.FromSeconds(5));

            // Now all we need to do is hit all of the redirect [FromUri]s
            // and verify that we get redirects to the corresponding
            // [ToUri]s.

            // Allow self-signed certificates and disable client-side automatic redirect handling
            // so we'll be able to see the redirect responses.

            var handler = new HttpClientHandler()
            {
                ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator,
                AllowAutoRedirect = false   // We need to see the redirects
            };

            using (var client = new TestHttpClient(disableConnectionReuse: true, handler: handler, disposeHandler: true))
            {
                foreach (var redirect in redirections)
                {
                    var response = await client.GetAsync(redirect.FromUri);

                    Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
                    Assert.True(response.Headers.TryGetValues("Location", out var locations));
                    Assert.Equal(redirect.ToUri.ToString(), locations.Single());
                }
            }
        }