protected override void Update(GameTime gameTime) { base.Update(gameTime); //Get input HandleInput(gameTime); //Get time of first frame if (startTime == -1) { startTime = (int)gameTime.TotalGameTime.TotalSeconds; } //Countdown countdown = startTime + TrafficGame.Duration - (int)gameTime.TotalGameTime.TotalSeconds; countdownUI.Update(countdown); if (countdown == -3) { EndGame(); } //Detect if in target lane int lane = Road.GetLane(player.Position.X, 0.35f); inTargetLane = lane == environment.road.GetHighlightAtPlayerPos(); environment.road.SetHighlightStatus(lane); //Step forward time (slo-mo if player crashed or countdown finished) float timeScale = 1.0f; if (player.crashed || countdown <= 0) { timeScale = 0.25f; } world.Step((float)gameTime.ElapsedGameTime.TotalSeconds * timeScale); if (state == GameState.RUNNING) { if (!player.crashed) { //Minimum of 1 point per frame. //Score based on velocity, double points if in correct lane //TODO: For low velocities, we may want less than 1 point per frame. int deltaScore = (int)Math.Max(1, (player.Velocity.Y * gameTime.ElapsedGameTime.TotalSeconds)); if (inTargetLane) { deltaScore *= 2; } score += deltaScore; } } else if (state == GameState.STARTING) { Camera.main.Zoom = Math.Min(1, Camera.main.Zoom + 0.05f); //Zoom out camera to normal position if (gameTime.TotalGameTime.TotalSeconds - stateChangeTime > 3) //Start game after 3 seconds { state = GameState.RUNNING; stateChangeTime = gameTime.TotalGameTime.TotalSeconds; player.Velocity = new Vector2(player.Velocity.X, adjustedSpeed); } } else if (state == GameState.RESTARTING) { Camera.main.Zoom = Math.Max(0, Camera.main.Zoom - 0.05f); //Zoom camera in as a transition if (gameTime.TotalGameTime.TotalSeconds - stateChangeTime > 1) //Reset world after 1 second { state = GameState.STARTING; stateChangeTime = gameTime.TotalGameTime.TotalSeconds; environment.Reset(); trafficManager.Reset(); player.Reset(); score = 0; } } //Update game stuff trafficManager.Update(gameTime, player, state); environment.Update(gameTime, player); player.Update(gameTime, InputManager.LateralMovement); //Light & camera follow player Camera.main.Target = new Vector2(player.Position.X, player.Position.Y); Lighting.Position = new Vector3(Lighting.Position.X, player.Position.Y + 15, Lighting.Position.Z); //Update GUI scoreUI.Update(gameTime, score); fpsUI.Update(gameTime); titleUI.Update(gameTime); }
/// <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)); } } } }
/// <inheritdoc/> public void Run(ModuleContext context) { TrafficManager trafficManager = null; bool isPublic = false; string name = null; string ruleName = null; bool deferUpdate = false; if (!context.ValidateArguments(context.Arguments, validModuleArgs)) { context.Failed = true; return; } // Obtain common arguments. context.WriteLine(AnsibleVerbosity.Trace, $"Parsing [state]"); if (!context.Arguments.TryGetValue <string>("state", out var state)) { state = "present"; } state = state.ToLowerInvariant(); if (context.HasErrors) { return; } context.WriteLine(AnsibleVerbosity.Trace, $"Parsing [name]"); if (!context.Arguments.TryGetValue <string>("name", out name)) { throw new ArgumentException($"[name] module argument is required."); } switch (name) { case "private": trafficManager = HiveHelper.Hive.PrivateTraffic; isPublic = false; break; case "public": trafficManager = HiveHelper.Hive.PublicTraffic; isPublic = true; break; default: throw new ArgumentException($"[name={name}] is not a one of the valid traffic manager names: [private] or [public]."); } if (state == "present" || state == "absent") { context.WriteLine(AnsibleVerbosity.Trace, $"Parsing [rule_name]"); if (!context.Arguments.TryGetValue <string>("rule_name", out ruleName)) { throw new ArgumentException($"[rule_name] module argument is required."); } if (!HiveDefinition.IsValidName(ruleName)) { throw new ArgumentException($"[rule_name={ruleName}] is not a valid traffic manager rule name."); } context.WriteLine(AnsibleVerbosity.Trace, $"Parsing [defer_update]"); if (!context.Arguments.TryGetValue <bool>("defer_update", out deferUpdate)) { deferUpdate = false; } } // We have the required arguments, so perform the operation. switch (state) { case "absent": context.WriteLine(AnsibleVerbosity.Trace, $"Check if rule [{ruleName}] exists."); if (trafficManager.GetRule(ruleName) != null) { context.WriteLine(AnsibleVerbosity.Trace, $"Rule [{ruleName}] does exist."); context.WriteLine(AnsibleVerbosity.Info, $"Deleting rule [{ruleName}]."); if (context.CheckMode) { context.WriteLine(AnsibleVerbosity.Info, $"Rule [{ruleName}] will be deleted when CHECK-MODE is disabled."); } else { trafficManager.RemoveRule(ruleName, deferUpdate: deferUpdate); context.WriteLine(AnsibleVerbosity.Trace, $"Rule [{ruleName}] deleted."); context.Changed = true; } } else { context.WriteLine(AnsibleVerbosity.Trace, $"Rule [{ruleName}] does not exist."); } break; case "present": context.WriteLine(AnsibleVerbosity.Trace, $"Parsing [rule]"); if (!context.Arguments.TryGetValue <JObject>("rule", out var routeObject)) { throw new ArgumentException($"[rule] module argument is required when [state={state}]."); } var ruleText = routeObject.ToString(); context.WriteLine(AnsibleVerbosity.Trace, "Parsing rule"); var newRule = TrafficRule.Parse(ruleText, strict: true); context.WriteLine(AnsibleVerbosity.Trace, "Rule parsed successfully"); // Use the name argument if the deserialized rule doesn't // have a name. This will make it easier on operators because // they won't need to specify the name twice. if (string.IsNullOrWhiteSpace(newRule.Name)) { newRule.Name = ruleName; } // Ensure that the name passed as an argument and the // name within the rule definition match. if (!string.Equals(ruleName, newRule.Name, StringComparison.InvariantCultureIgnoreCase)) { throw new ArgumentException($"The [rule_name={ruleName}] argument and the rule's [{nameof(TrafficRule.Name)}={newRule.Name}] property are not the same."); } context.WriteLine(AnsibleVerbosity.Trace, "Rule name matched."); // Validate the rule. context.WriteLine(AnsibleVerbosity.Trace, "Validating rule."); var proxySettings = trafficManager.GetSettings(); var validationContext = new TrafficValidationContext(name, proxySettings); // $hack(jeff.lill): // // This ensures that [proxySettings.Resolvers] is initialized with // the built-in Docker DNS resolver. proxySettings.Validate(validationContext); // Load the TLS certificates into the validation context so we'll // be able to verify that any referenced certificates mactually exist. // $todo(jeff.lill): // // This code assumes that the operator is currently logged in with // root Vault privileges. We'll have to do something else for // non-root logins. // // One idea might be to save two versions of the certificates. // The primary certificate with private key in Vault and then // just the public certificate in Consul and then load just // the public ones here. // // A good time to make this change might be when we convert to // use the .NET X.509 certificate implementation. if (!context.Login.HasVaultRootCredentials) { throw new ArgumentException("Access Denied: Root Vault credentials are required."); } context.WriteLine(AnsibleVerbosity.Trace, "Reading hive certificates."); using (var vault = HiveHelper.OpenVault(Program.HiveLogin.VaultCredentials.RootToken)) { // List the certificate key/names and then fetch each one // to capture details like the expiration date and covered // hostnames. foreach (var certName in vault.ListAsync("neon-secret/cert").Result) { context.WriteLine(AnsibleVerbosity.Trace, $"Reading: {certName}"); var certificate = vault.ReadJsonAsync <TlsCertificate>(HiveHelper.GetVaultCertificateKey(certName)).Result; validationContext.Certificates.Add(certName, certificate); } } context.WriteLine(AnsibleVerbosity.Trace, $"[{validationContext.Certificates.Count}] hive certificates downloaded."); // Actually perform the rule validation. newRule.Validate(validationContext); if (validationContext.HasErrors) { context.WriteLine(AnsibleVerbosity.Trace, $"[{validationContext.Errors.Count}] Route validation errors."); foreach (var error in validationContext.Errors) { context.WriteLine(AnsibleVerbosity.Important, error); context.WriteErrorLine(error); } context.Failed = true; return; } context.WriteLine(AnsibleVerbosity.Trace, "Rule is valid."); // Try reading any existing rule with this name and then determine // whether the two versions of the rule are actually different. context.WriteLine(AnsibleVerbosity.Trace, $"Looking for existing rule [{ruleName}]"); var existingRule = trafficManager.GetRule(ruleName); var changed = false; if (existingRule != null) { context.WriteLine(AnsibleVerbosity.Trace, $"Rule exists: checking for differences."); // Normalize the new and existing rules so the JSON text comparision // will work properly. newRule.Normalize(isPublic); existingRule.Normalize(isPublic); changed = !NeonHelper.JsonEquals(newRule, existingRule); if (changed) { context.WriteLine(AnsibleVerbosity.Trace, $"Rules are different."); } else { context.WriteLine(AnsibleVerbosity.Info, $"Rules are the same. No need to update."); } } else { changed = true; context.WriteLine(AnsibleVerbosity.Trace, $"Rule [name={ruleName}] does not exist."); } if (changed) { if (context.CheckMode) { context.WriteLine(AnsibleVerbosity.Info, $"Rule [{ruleName}] will be updated when CHECK-MODE is disabled."); } else { context.WriteLine(AnsibleVerbosity.Trace, $"Writing rule [{ruleName}]."); trafficManager.SetRule(newRule); context.WriteLine(AnsibleVerbosity.Info, $"Rule updated."); context.Changed = !context.CheckMode; } } break; case "update": trafficManager.Update(); context.Changed = true; context.WriteLine(AnsibleVerbosity.Info, $"Update signalled."); break; case "purge": var purgeItems = context.ParseStringArray("purge_list"); var purgeCaseSensitive = context.ParseBool("purge_case_sensitive"); if (!purgeCaseSensitive.HasValue) { purgeCaseSensitive = false; } if (purgeItems.Count == 0) { context.WriteLine(AnsibleVerbosity.Important, $"[purge_list] is missing or empty."); break; } trafficManager.Purge(purgeItems.ToArray(), caseSensitive: purgeCaseSensitive.Value); context.Changed = true; context.WriteLine(AnsibleVerbosity.Info, $"Purge request submitted."); break; default: throw new ArgumentException($"[state={state}] is not one of the valid choices: [present], [absent], or [update]."); } }