/// <inheritdoc/> protected async override Task <int> OnRunAsync() { // Read the configuration environment variable or file to initialize // endpoint response text. responseText = "UNCONFIGURED"; var resultVar = GetEnvironmentVariable("WEB_RESULT"); if (resultVar != null) { responseText = resultVar; } else { var configPath = GetConfigFilePath("/etc/complex/response"); if (configPath != null && File.Exists(configPath)) { responseText = File.ReadAllText(configPath); } } // Start the web service. var endpoint = Description.Endpoints.Default; webHost = new WebHostBuilder() .UseStartup <ComplexServiceStartup>() .UseKestrel(options => options.Listen(IPAddress.Any, endpoint.Port)) .ConfigureServices(services => services.AddSingleton(typeof(ComplexService), this)) .Build(); webHost.Start(); // Start the worker thread. thread = NeonHelper.StartThread(ThreadFunc); // Start the service task task = Task.Run(async() => await TaskFunc()); // Indicate that the service is running. await StartedAsync(); // Handle termination gracefully. await Terminator.StopEvent.WaitAsync(); thread.Join(); await task; Terminator.ReadyToExit(); return(0); }
/// <inheritdoc/> protected async override Task <int> OnRunAsync() { await SetStatusAsync(NeonServiceStatus.Starting); Kubernetes = new KubernetesWithRetry(KubernetesClientConfiguration.BuildDefaultConfig()); // Start the web service. var port = 443; if (NeonHelper.IsDevWorkstation) { port = 11004; } webHost = new WebHostBuilder() .ConfigureAppConfiguration( (hostingcontext, config) => { config.Sources.Clear(); }) .UseStartup <Startup>() .UseKestrel(options => { options.Listen(IPAddress.Any, port, listenOptions => { if (!NeonHelper.IsDevWorkstation) { listenOptions.UseHttps(X509Certificate2.CreateFromPem( File.ReadAllText(@"/tls/tls.crt"), File.ReadAllText(@"/tls/tls.key"))); } }); }) .ConfigureServices(services => services.AddSingleton(typeof(Service), this)) .UseStaticWebAssets() .Build(); _ = webHost.RunAsync(); Log.LogInfo($"Listening on {IPAddress.Any}:{port}"); // Indicate that the service is ready for business. await SetStatusAsync(NeonServiceStatus.Running); // Handle termination gracefully. await Terminator.StopEvent.WaitAsync(); Terminator.ReadyToExit(); return(0); }
/// <inheritdoc/> protected async override Task <int> OnRunAsync() { await SetStatusAsync(NeonServiceStatus.Starting); Config = await ProxyConfig.FromFileAsync(GetConfigFilePath(ConfigFile)); DnsClient = new LookupClient(new LookupClientOptions() { UseCache = Config.Dns.UseCache, MaximumCacheTimeout = TimeSpan.FromSeconds(Config.Dns.MaximumCacheTimeoutSeconds), MinimumCacheTimeout = TimeSpan.FromSeconds(Config.Dns.MinimumCacheTimeoutSeconds), CacheFailedResults = Config.Dns.CacheFailedResults }); AesCipher = new AesCipher(GetEnvironmentVariable("COOKIE_CIPHER", AesCipher.GenerateKey(), redacted: !Log.IsLogDebugEnabled)); CurrentConnections = new HashSet <string>(); // Start the web service. webHost = new WebHostBuilder() .ConfigureAppConfiguration( (hostingcontext, config) => { config.Sources.Clear(); }) .UseStartup <Startup>() .UseKestrel(options => options.Listen(IPAddress.Any, Config.Port)) .ConfigureServices(services => services.AddSingleton(typeof(Service), this)) .UseStaticWebAssets() .Build(); _ = webHost.RunAsync(); Log.LogInfo($"Listening on {IPAddress.Any}:{Config.Port}"); // Indicate that the service is ready for business. await SetStatusAsync(NeonServiceStatus.Running); // Handle termination gracefully. await Terminator.StopEvent.WaitAsync(); Terminator.ReadyToExit(); return(0); }
/// <inheritdoc/> protected async override Task <int> OnRunAsync() { // Query the service map for the [web-service] endpoint and setup // the HTTP client we'll use to communicate with that service. var webService = ServiceMap["web-service"]; if (webService == null) { Log.LogError("Service description for [web-service] not found."); Exit(1); } httpClient = new HttpClient() { BaseAddress = webService.Endpoints.Default.Uri }; // Start the HTTP service. var endpoint = Description.Endpoints.Default; webHost = new WebHostBuilder() .UseStartup <RelayServiceStartup>() .UseKestrel(options => options.Listen(IPAddress.Any, endpoint.Port)) .ConfigureServices(services => services.AddSingleton(typeof(RelayService), this)) .Build(); webHost.Start(); // Indicate that the service is running. await StartedAsync(); // Handle termination gracefully. await Terminator.StopEvent.WaitAsync(); Terminator.ReadyToExit(); return(0); }
/// <inheritdoc/> protected async override Task <int> OnRunAsync() { await SetStatusAsync(NeonServiceStatus.Starting); var configFile = GetConfigFilePath("/etc/neonkube/neon-sso-session-proxy/config.yaml"); using (StreamReader reader = new StreamReader(new FileStream(configFile, FileMode.Open, FileAccess.Read))) { Config = NeonHelper.YamlDeserializeViaJson <DexConfig>(await reader.ReadToEndAsync()); } // Start the web service. webHost = new WebHostBuilder() .ConfigureAppConfiguration( (hostingcontext, config) => { config.Sources.Clear(); }) .UseStartup <Startup>() .UseKestrel(options => options.Listen(IPAddress.Any, 80)) .ConfigureServices(services => services.AddSingleton(typeof(Service), this)) .UseStaticWebAssets() .Build(); _ = webHost.RunAsync(); Log.LogInfo($"Listening on {IPAddress.Any}:80"); // Indicate that the service is ready for business. await SetStatusAsync(NeonServiceStatus.Running); // Handle termination gracefully. await Terminator.StopEvent.WaitAsync(); Terminator.ReadyToExit(); return(0); }
/// <inheritdoc/> protected async override Task <int> OnRunAsync() { // Load the configuration environment variables, exiting with a // non-zero exit code if they don't exist. NatsServerUri = Environment.Get("NATS_URI", string.Empty); if (string.IsNullOrEmpty(NatsServerUri)) { Log.LogCritical("Invalid configuration: [NATS_URI] environment variable is missing or invalid."); Exit(1); } // Start the HTTP service. var endpoint = Description.Endpoints.Default; webHost = new WebHostBuilder() .UseStartup <Startup>() .UseKestrel(options => { options.Listen(IPAddress.Any, endpoint.Port); }) .ConfigureServices(services => services.AddSingleton(typeof(WebService), this)) .Build(); webHost.Start(); // Indicate that the service is running. await StartedAsync(); // Handle termination gracefully. await Terminator.StopEvent.WaitAsync(); Terminator.ReadyToExit(); return(0); }
/// <inheritdoc/> protected async override Task <int> OnRunAsync() { // Parse the environment variables. var portVariable = GetEnvironmentVariable("PORT", "80"); if (!int.TryParse(portVariable, out var port) || !NetHelper.IsValidPort(port)) { Log.LogCritical($"[PORT={port}] environment variable is not valid."); return(1); } // Start the HTTP service. webHost = new WebHostBuilder() .UseStartup <Startup>() .UseKestrel(options => options.Listen(IPAddress.Any, port)) .ConfigureServices(services => services.AddSingleton(typeof(Service), this)) .Build(); webHost.Start(); // Start a do-nothing thread that we can use to set breakpoints // to verify that Bridge to Kubernetes works. var nothingThread = NeonHelper.StartThread(NothingThread); // Indicate that the service is running. await StartedAsync(); // Handle termination gracefully. await Terminator.StopEvent.WaitAsync(); nothingThread.Join(); Terminator.ReadyToExit(); return(0); }
/// <inheritdoc/> protected async override Task<int> OnRunAsync() { // Load the configuration environment variables, exiting with a // non-zero exit code if they don't exist. natsServerUri = Environment.Get("NATS_URI", string.Empty); if (string.IsNullOrEmpty(natsServerUri)) { Log.LogCritical("Invalid configuration: [NATS_URI] environment variable is missing or invalid."); Exit(1); } natsQueue = GetEnvironmentVariable("NATS_QUEUE"); if (string.IsNullOrEmpty(natsQueue)) { Log.LogCritical("Invalid configuration: [NATS_QUEUE] environment variable is missing or invalid."); Exit(1); } // Connect to NATS. var connectionFactory = new ConnectionFactory(); var natOptions = ConnectionFactory.GetDefaultOptions(); natOptions.Servers = new string[] { natsServerUri }; nats = connectionFactory.CreateConnection(natOptions); // Start the service tasks sendTask = Task.Run(async () => await SendTaskFunc()); receiveTask = Task.Run(async () => await ReceiveTaskFunc()); // Indicate that the service is running. await StartedAsync(); // Handle termination gracefully. await Terminator.StopEvent.WaitAsync(); // We're going to dispose the NATS connection which will cause any // pending or future NAT calls to fail and throw an exception. This // will ultimately cause the tasks to exit. // // Note that we're setting [terminate=true] so the task exception // handlers can handle termination related exceptions by not logging // them and exiting the task. terminating = true; nats.Dispose(); // Wait for the service task to exit and then indicate that the // service has exited cleanly. await sendTask; Terminator.ReadyToExit(); return 0; }
/// <summary> /// Starts the service if it's not already running. This will call <see cref="OnRunAsync"/>, /// which actually implements the service. /// </summary> /// <param name="disableProcessExit"> /// Optionally specifies that the hosting process should not be terminated /// when the service exists. This is typically used for testing or debugging. /// This defaults to <c>false</c>. /// </param> /// <remarks> /// <note> /// For production, this method will not return until the service is expicitly /// stopped via a call to <see cref="Stop"/> or the <see cref="Terminator"/> /// handles a stop signal. For test environments, this method will call /// <see cref="OnRunAsync"/> on a new thread and returns immediately while the /// service continues to run in parallel. /// </note> /// <para> /// Service implementations must honor <see cref="Terminator"/> termination /// signals exiting the <see cref="OnRunAsync"/> method reasonably quickly (within /// 30 seconds by default) when these occur. They can do this by passing /// <see cref="ProcessTerminator.CancellationToken"/> for <c>async</c> calls /// and then catching the <see cref="TaskCanceledException"/> and returning /// from <see cref="OnRunAsync"/>. /// </para> /// <para> /// Another technique for synchronous code is to explicitly check the /// <see cref="ProcessTerminator.CancellationToken"/> token's /// <see cref="CancellationToken.IsCancellationRequested"/> property and /// return from your <see cref="OnRunAsync"/> method when this is <c>true</c>. /// This You'll need to perform this check frequently so you may need /// to use timeouts to prevent blocking code from blocking for too long. /// </para> /// </remarks> /// <returns>The service exit code.</returns> /// <remarks> /// <note> /// It is not possible to restart a service after it's been stopped. /// </note> /// </remarks> public async virtual Task <int> RunAsync(bool disableProcessExit = false) { lock (syncLock) { if (isRunning) { throw new InvalidOperationException($"Service [{Name}] is already running."); } if (isDisposed) { throw new InvalidOperationException($"Service [{Name}] cannot be restarted after it's been stopped."); } isRunning = true; } // [disableProcessExit] will be typically passed as true when testing or // debugging. We'll let the terminator know so it won't do this. if (disableProcessExit) { Terminator.DisableProcessExit = true; } // Initialize the logger. if (GlobalLogging) { LogManager = global::Neon.Diagnostics.LogManager.Default; } else { LogManager = new LogManager(parseLogLevel: false); } LogManager.SetLogLevel(GetEnvironmentVariable("LOG_LEVEL", "info")); Log = LogManager.GetLogger(); Log.LogInfo(() => $"Starting [{Name}:{GitVersion}]"); // Start and run the service. try { await OnRunAsync(); ExitCode = 0; } catch (TaskCanceledException) { // Ignore these as a normal consequence of a service // being signalled to terminate. ExitCode = 0; } catch (ProgramExitException e) { // Don't override a non-zero ExitCode that was set earlier // with a zero exit code. if (e.ExitCode != 0) { ExitCode = e.ExitCode; } } catch (Exception e) { ExitException = e; Log.LogError(e); } // Perform last rights for the service before it passes away. Log.LogInfo(() => $"Exiting [{Name}] with [exitcode={ExitCode}]."); Terminator.ReadyToExit(); Status = KubeServiceStatus.Terminated; return(ExitCode); }
/// <inheritdoc/> protected async override Task <int> OnRunAsync() { //----------------------------------------------------------------- // Start the controllers: these need to be started before starting KubeOps var k8s = new Kubernetes(KubernetesClientConfiguration.BuildDefaultConfig()); //################################ // $debug(jefflill): RESTORE THIS! //await NodeTaskController.StartAsync(k8s); //################################ await ContainerRegistryController.StartAsync(k8s); //----------------------------------------------------------------- // Start KubeOps. // $hack(jefflill): https://github.com/nforgeio/neonKUBE/issues/1599 // // We're temporarily using our poor man's operator #if DISABLED _ = Host.CreateDefaultBuilder() .ConfigureHostOptions( options => { // Ensure that the processor terminator and ASP.NET shutdown times match. options.ShutdownTimeout = ProcessTerminator.DefaultMinShutdownTime; }) .ConfigureAppConfiguration( (hostingContext, config) => { // $note(jefflill): // // The .NET runtime watches the entire file system for configuration // changes which can cause real problems on Linux. We're working around // this by removing all configuration sources which we aren't using // anyway for Kubernetes apps. // // https://github.com/nforgeio/neonKUBE/issues/1390 config.Sources.Clear(); }) .ConfigureLogging( logging => { logging.ClearProviders(); logging.AddProvider(base.LogManager); }) .ConfigureWebHostDefaults(builder => builder.UseStartup <Startup>()) .Build() .RunOperatorAsync(Array.Empty <string>()); #endif // Indicate that the service is running. await StartedAsync(); // Handle termination gracefully. await Terminator.StopEvent.WaitAsync(); Terminator.ReadyToExit(); return(0); }
/// <inheritdoc/> protected async override Task <int> OnRunAsync() { // Verify the environment variables. var settings = new TemporalSettings(); var hostPort = GetEnvironmentVariable("TEMPORAL_HOSTPORT"); var @namespace = GetEnvironmentVariable("TEMPORAL_NAMESPACE"); var taskQueue = GetEnvironmentVariable("TEMPORAL_TASKQUEUE"); var error = false; Log.LogInfo(() => $"TEMPORAL_HOSTPORT: {hostPort}"); Log.LogInfo(() => $"TEMPORAL_NAMESPACE: {@namespace}"); Log.LogInfo(() => $"TEMPORAL_TASKQUEUE: {taskQueue}"); if (string.IsNullOrEmpty(hostPort)) { error = true; Log.LogError("The [TEMPORAL_HOSTPORT] environment variable is required."); } settings.HostPort = hostPort; if (string.IsNullOrEmpty(@namespace)) { error = true; Log.LogError("The [TEMPORAL_NAMESPACE] environment variable is required."); } if (string.IsNullOrEmpty(taskQueue)) { error = true; Log.LogError("The [TEMPORAL_TASKQUEUE] environment variable is required."); } if (error) { return(1); } // Connect to Temporal and register the workflows and activities. Log.LogInfo("Connecting to Temporal."); settings.Namespace = @namespace; settings.TaskQueue = taskQueue; using (var client = await TemporalClient.ConnectAsync(settings)) { // Create a worker and register the workflow and activity // implementations to let Temporal know we're open for business. using (var worker = await client.NewWorkerAsync()) { Log.LogInfo("Registering workflows."); await worker.RegisterAssemblyAsync(Assembly.GetExecutingAssembly()); Log.LogInfo("Starting worker."); await worker.StartAsync(); // Indicate that the service is running. Log.LogInfo("Ready for work."); await StartedAsync(); } } // Handle termination gracefully. await Terminator.StopEvent.WaitAsync(); Terminator.ReadyToExit(); return(0); }
/// <inheritdoc/> protected async override Task <int> OnRunAsync() { await SetStatusAsync(NeonServiceStatus.Starting); var port = 80; Kubernetes = new KubernetesWithRetry(KubernetesClientConfiguration.BuildDefaultConfig()); _ = Kubernetes.WatchAsync <V1ConfigMap>(async(@event) => { await SyncContext.Clear; ClusterInfo = TypeSafeConfigMap <ClusterInfo> .From(@event.Value).Config; Log.LogInfo($"Updated cluster info"); }, KubeNamespace.NeonStatus, fieldSelector: $"metadata.name={KubeConfigMapName.ClusterInfo}"); Dashboards = new List <Dashboard>(); Dashboards.Add( new Dashboard( id: "neonkube", name: "neonKUBE", displayOrder: 0)); _ = Kubernetes.WatchAsync <V1NeonDashboard>(async(@event) => { await SyncContext.Clear; switch (@event.Type) { case WatchEventType.Added: await AddDashboardAsync(@event.Value); break; case WatchEventType.Deleted: await RemoveDashboardAsync(@event.Value); break; case WatchEventType.Modified: await RemoveDashboardAsync(@event.Value); await AddDashboardAsync(@event.Value); break; default: break; } Dashboards = Dashboards.OrderBy(d => d.DisplayOrder) .ThenBy(d => d.Name).ToList(); }); if (NeonHelper.IsDevWorkstation) { port = 11001; SetEnvironmentVariable("LOG_LEVEL", "debug"); SetEnvironmentVariable("DO_NOT_TRACK", "true"); SetEnvironmentVariable("COOKIE_CIPHER", "/HwPfpfACC70Rh1DeiMdubHINQHRGfc4JP6DYcSkAQ8="); await ConfigureDevAsync(); } var metricsHost = GetEnvironmentVariable("METRICS_HOST", "http://mimir-query-frontend.neon-monitor.svc.cluster.local:8080"); PrometheusClient = new PrometheusClient($"{metricsHost}/prometheus/"); SsoClientSecret = GetEnvironmentVariable("SSO_CLIENT_SECRET", redacted: !Log.IsLogDebugEnabled); AesCipher = new AesCipher(GetEnvironmentVariable("COOKIE_CIPHER", AesCipher.GenerateKey(), redacted: !Log.IsLogDebugEnabled)); // Start the web service. webHost = new WebHostBuilder() .ConfigureAppConfiguration( (hostingcontext, config) => { config.Sources.Clear(); }) .UseStartup <Startup>() .UseKestrel(options => options.Listen(IPAddress.Any, port)) .ConfigureServices(services => services.AddSingleton(typeof(Service), this)) .UseStaticWebAssets() .Build(); _ = webHost.RunAsync(); Log.LogInfo($"Listening on {IPAddress.Any}:{port}"); // Indicate that the service is running. await StartedAsync(); // Handle termination gracefully. await Terminator.StopEvent.WaitAsync(); Terminator.ReadyToExit(); return(0); }
/// <inheritdoc/> protected async override Task <int> OnRunAsync() { // Verify the environment variables. var settings = new CadenceSettings(); var servers = GetEnvironmentVariable("CADENCE_SERVERS"); var domain = GetEnvironmentVariable("CADENCE_DOMAIN"); var taskList = GetEnvironmentVariable("CADENCE_TASKLIST"); var error = false; Log.LogInfo(() => $"CADENCE_SERVERS: {servers}"); Log.LogInfo(() => $"CADENCE_DOMAIN: {domain}"); Log.LogInfo(() => $"CADENCE_TASKLIST: {taskList}"); if (string.IsNullOrEmpty(servers)) { error = true; Log.LogError("The [CADENCE_SERVERS] environment variable is required."); } try { foreach (var item in servers.Split(',')) { var uri = new Uri(item.Trim(), UriKind.Absolute); settings.Servers.Add(uri.ToString()); } } catch { error = true; Log.LogError(() => $"One or more URIs are invalid: CADENCE_SERVERS={servers}"); } if (string.IsNullOrEmpty(domain)) { error = true; Log.LogError("The [CADENCE_DOMAIN] environment variable is required."); } if (string.IsNullOrEmpty(taskList)) { error = true; Log.LogError("The [CADENCE_TASKLIST] environment variable is required."); } if (error) { return(1); } // Connect to Cadence and register the workflows and activities. Log.LogInfo("Connecting to Cadence."); settings.DefaultDomain = domain; using (var client = await CadenceClient.ConnectAsync(settings)) { // Register the workflows. Log.LogInfo("Registering workflows."); await client.RegisterAssemblyAsync(Assembly.GetExecutingAssembly()); // Start the worker. Log.LogInfo("Starting worker."); using (var worker = client.StartWorkerAsync(taskList)) { // Indicate that the service is running. Log.LogInfo("Ready for work."); await StartedAsync(); } } // Handle termination gracefully. await Terminator.StopEvent.WaitAsync(); Terminator.ReadyToExit(); return(0); }