2023-04-25 09:31:15 +00:00
|
|
|
|
using Logging;
|
2023-06-21 06:28:40 +00:00
|
|
|
|
using Utils;
|
2023-04-17 08:31:14 +00:00
|
|
|
|
|
|
|
|
|
namespace KubernetesWorkflow
|
2023-04-12 11:53:55 +00:00
|
|
|
|
{
|
2023-09-11 14:57:57 +00:00
|
|
|
|
public interface IStartupWorkflow
|
|
|
|
|
{
|
|
|
|
|
RunningContainers Start(int numberOfContainers, Location location, ContainerRecipeFactory recipeFactory, StartupConfig startupConfig);
|
2023-09-20 10:55:09 +00:00
|
|
|
|
CrashWatcher CreateCrashWatcher(RunningContainer container);
|
2023-09-11 14:57:57 +00:00
|
|
|
|
void Stop(RunningContainers runningContainers);
|
2023-09-12 11:32:06 +00:00
|
|
|
|
void DownloadContainerLog(RunningContainer container, ILogHandler logHandler, int? tailLines = null);
|
2023-09-11 14:57:57 +00:00
|
|
|
|
string ExecuteCommand(RunningContainer container, string command, params string[] args);
|
2023-09-12 08:31:55 +00:00
|
|
|
|
void DeleteNamespace();
|
|
|
|
|
void DeleteNamespacesStartingWith(string namespacePrefix);
|
2023-09-11 14:57:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class StartupWorkflow : IStartupWorkflow
|
2023-04-12 11:53:55 +00:00
|
|
|
|
{
|
2023-09-12 08:31:55 +00:00
|
|
|
|
private readonly ILog log;
|
2023-04-12 13:11:36 +00:00
|
|
|
|
private readonly WorkflowNumberSource numberSource;
|
|
|
|
|
private readonly K8sCluster cluster;
|
|
|
|
|
private readonly KnownK8sPods knownK8SPods;
|
2023-09-12 08:31:55 +00:00
|
|
|
|
private readonly string k8sNamespace;
|
2023-04-12 11:53:55 +00:00
|
|
|
|
private readonly RecipeComponentFactory componentFactory = new RecipeComponentFactory();
|
|
|
|
|
|
2023-09-12 08:31:55 +00:00
|
|
|
|
internal StartupWorkflow(ILog log, WorkflowNumberSource numberSource, K8sCluster cluster, KnownK8sPods knownK8SPods, string k8sNamespace)
|
2023-04-12 11:53:55 +00:00
|
|
|
|
{
|
2023-04-25 09:31:15 +00:00
|
|
|
|
this.log = log;
|
2023-04-12 13:11:36 +00:00
|
|
|
|
this.numberSource = numberSource;
|
|
|
|
|
this.cluster = cluster;
|
|
|
|
|
this.knownK8SPods = knownK8SPods;
|
2023-09-12 08:31:55 +00:00
|
|
|
|
this.k8sNamespace = k8sNamespace;
|
2023-04-12 11:53:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-04-12 13:11:36 +00:00
|
|
|
|
public RunningContainers Start(int numberOfContainers, Location location, ContainerRecipeFactory recipeFactory, StartupConfig startupConfig)
|
2023-04-12 11:53:55 +00:00
|
|
|
|
{
|
2023-04-12 13:11:36 +00:00
|
|
|
|
return K8s(controller =>
|
|
|
|
|
{
|
|
|
|
|
var recipes = CreateRecipes(numberOfContainers, recipeFactory, startupConfig);
|
|
|
|
|
var runningPod = controller.BringOnline(recipes, location);
|
2023-09-11 14:57:57 +00:00
|
|
|
|
var containers = CreateContainers(runningPod, recipes, startupConfig);
|
2023-04-12 11:53:55 +00:00
|
|
|
|
|
2023-09-13 13:10:19 +00:00
|
|
|
|
var rc = new RunningContainers(startupConfig, runningPod, containers);
|
|
|
|
|
cluster.Configuration.Hooks.OnContainersStarted(rc);
|
|
|
|
|
return rc;
|
2023-09-11 14:57:57 +00:00
|
|
|
|
});
|
2023-08-14 13:10:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-20 10:55:09 +00:00
|
|
|
|
public CrashWatcher CreateCrashWatcher(RunningContainer container)
|
|
|
|
|
{
|
|
|
|
|
return K8s(c => c.CreateCrashWatcher(container));
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-13 09:07:36 +00:00
|
|
|
|
public void Stop(RunningContainers runningContainers)
|
|
|
|
|
{
|
|
|
|
|
K8s(controller =>
|
|
|
|
|
{
|
|
|
|
|
controller.Stop(runningContainers.RunningPod);
|
2023-09-13 13:10:19 +00:00
|
|
|
|
cluster.Configuration.Hooks.OnContainersStopped(runningContainers);
|
2023-04-13 09:07:36 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-12 11:32:06 +00:00
|
|
|
|
public void DownloadContainerLog(RunningContainer container, ILogHandler logHandler, int? tailLines = null)
|
2023-04-13 09:30:19 +00:00
|
|
|
|
{
|
|
|
|
|
K8s(controller =>
|
|
|
|
|
{
|
2023-08-16 14:13:29 +00:00
|
|
|
|
controller.DownloadPodLog(container.Pod, container.Recipe, logHandler, tailLines);
|
2023-04-13 09:30:19 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-14 07:54:07 +00:00
|
|
|
|
public string ExecuteCommand(RunningContainer container, string command, params string[] args)
|
|
|
|
|
{
|
|
|
|
|
return K8s(controller =>
|
|
|
|
|
{
|
|
|
|
|
return controller.ExecuteCommand(container.Pod, container.Recipe.Name, command, args);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-12 08:31:55 +00:00
|
|
|
|
public void DeleteNamespace()
|
2023-04-12 13:11:36 +00:00
|
|
|
|
{
|
|
|
|
|
K8s(controller =>
|
|
|
|
|
{
|
2023-09-12 08:31:55 +00:00
|
|
|
|
controller.DeleteNamespace();
|
2023-04-12 13:11:36 +00:00
|
|
|
|
});
|
2023-04-12 11:53:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-12 08:31:55 +00:00
|
|
|
|
public void DeleteNamespacesStartingWith(string namespacePrefix)
|
2023-05-03 12:18:37 +00:00
|
|
|
|
{
|
|
|
|
|
K8s(controller =>
|
|
|
|
|
{
|
2023-09-12 08:31:55 +00:00
|
|
|
|
controller.DeleteAllNamespacesStartingWith(namespacePrefix);
|
2023-05-03 12:18:37 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-30 08:56:19 +00:00
|
|
|
|
private RunningContainer[] CreateContainers(RunningPod runningPod, ContainerRecipe[] recipes, StartupConfig startupConfig)
|
2023-04-12 11:53:55 +00:00
|
|
|
|
{
|
2023-04-25 09:31:15 +00:00
|
|
|
|
log.Debug();
|
2023-05-04 09:34:43 +00:00
|
|
|
|
return recipes.Select(r =>
|
|
|
|
|
{
|
|
|
|
|
var servicePorts = runningPod.GetServicePortsForContainerRecipe(r);
|
|
|
|
|
log.Debug($"{r} -> service ports: {string.Join(",", servicePorts.Select(p => p.Number))}");
|
|
|
|
|
|
2023-06-23 08:35:23 +00:00
|
|
|
|
var name = GetContainerName(r, startupConfig);
|
|
|
|
|
|
|
|
|
|
return new RunningContainer(runningPod, r, servicePorts, name,
|
2023-06-01 07:35:18 +00:00
|
|
|
|
GetContainerExternalAddress(runningPod, servicePorts),
|
2023-06-01 10:31:14 +00:00
|
|
|
|
GetContainerInternalAddress(r));
|
2023-06-01 07:35:18 +00:00
|
|
|
|
|
2023-05-04 09:34:43 +00:00
|
|
|
|
}).ToArray();
|
2023-04-12 11:53:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-23 08:35:23 +00:00
|
|
|
|
private string GetContainerName(ContainerRecipe recipe, StartupConfig startupConfig)
|
|
|
|
|
{
|
|
|
|
|
if (startupConfig == null) return "";
|
|
|
|
|
if (!string.IsNullOrEmpty(startupConfig.NameOverride))
|
|
|
|
|
{
|
|
|
|
|
return $"<{startupConfig.NameOverride}{recipe.Number}>";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return $"<{recipe.Name}>";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-21 06:28:40 +00:00
|
|
|
|
private Address GetContainerExternalAddress(RunningPod pod, Port[] servicePorts)
|
2023-06-01 07:35:18 +00:00
|
|
|
|
{
|
2023-06-21 06:28:40 +00:00
|
|
|
|
return new Address(
|
2023-06-01 07:35:18 +00:00
|
|
|
|
pod.Cluster.HostAddress,
|
|
|
|
|
GetServicePort(servicePorts));
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-21 06:28:40 +00:00
|
|
|
|
private Address GetContainerInternalAddress(ContainerRecipe recipe)
|
2023-06-01 07:35:18 +00:00
|
|
|
|
{
|
2023-06-01 10:31:14 +00:00
|
|
|
|
var serviceName = "service-" + numberSource.WorkflowNumber;
|
2023-06-01 10:44:48 +00:00
|
|
|
|
var port = GetInternalPort(recipe);
|
2023-06-01 07:35:18 +00:00
|
|
|
|
|
2023-06-21 06:28:40 +00:00
|
|
|
|
return new Address(
|
2023-09-12 08:31:55 +00:00
|
|
|
|
$"http://{serviceName}.{k8sNamespace}.svc.cluster.local",
|
2023-06-01 08:14:32 +00:00
|
|
|
|
port);
|
2023-06-01 07:35:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static int GetServicePort(Port[] servicePorts)
|
|
|
|
|
{
|
|
|
|
|
if (servicePorts.Any()) return servicePorts.First().Number;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-01 10:44:48 +00:00
|
|
|
|
private static int GetInternalPort(ContainerRecipe recipe)
|
|
|
|
|
{
|
|
|
|
|
if (recipe.ExposedPorts.Any()) return recipe.ExposedPorts.First().Number;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-12 11:53:55 +00:00
|
|
|
|
private ContainerRecipe[] CreateRecipes(int numberOfContainers, ContainerRecipeFactory recipeFactory, StartupConfig startupConfig)
|
|
|
|
|
{
|
2023-04-25 09:31:15 +00:00
|
|
|
|
log.Debug();
|
2023-04-12 11:53:55 +00:00
|
|
|
|
var result = new List<ContainerRecipe>();
|
|
|
|
|
for (var i = 0; i < numberOfContainers; i++)
|
|
|
|
|
{
|
2023-09-13 14:06:05 +00:00
|
|
|
|
var recipe = recipeFactory.CreateRecipe(i, numberSource.GetContainerNumber(), componentFactory, startupConfig);
|
|
|
|
|
if (cluster.Configuration.AddAppPodLabel) recipe.PodLabels.Add("app", recipeFactory.AppName);
|
|
|
|
|
cluster.Configuration.Hooks.OnContainerRecipeCreated(recipe);
|
|
|
|
|
result.Add(recipe);
|
2023-04-12 11:53:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result.ToArray();
|
|
|
|
|
}
|
2023-04-12 13:11:36 +00:00
|
|
|
|
|
|
|
|
|
private void K8s(Action<K8sController> action)
|
|
|
|
|
{
|
2023-09-12 08:31:55 +00:00
|
|
|
|
var controller = new K8sController(log, cluster, knownK8SPods, numberSource, k8sNamespace);
|
2023-04-12 13:11:36 +00:00
|
|
|
|
action(controller);
|
|
|
|
|
controller.Dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private T K8s<T>(Func<K8sController, T> action)
|
2023-08-07 13:51:44 +00:00
|
|
|
|
{
|
2023-09-12 08:31:55 +00:00
|
|
|
|
var controller = new K8sController(log, cluster, knownK8SPods, numberSource, k8sNamespace);
|
2023-08-10 09:47:34 +00:00
|
|
|
|
var result = action(controller);
|
|
|
|
|
controller.Dispose();
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2023-04-13 09:30:19 +00:00
|
|
|
|
}
|
2023-04-12 13:11:36 +00:00
|
|
|
|
|
2023-04-13 09:30:19 +00:00
|
|
|
|
public interface ILogHandler
|
|
|
|
|
{
|
|
|
|
|
void Log(Stream log);
|
2023-04-12 11:53:55 +00:00
|
|
|
|
}
|
2023-04-17 08:31:14 +00:00
|
|
|
|
|
|
|
|
|
public abstract class LogHandler : ILogHandler
|
|
|
|
|
{
|
|
|
|
|
public void Log(Stream log)
|
|
|
|
|
{
|
|
|
|
|
using var reader = new StreamReader(log);
|
|
|
|
|
var line = reader.ReadLine();
|
|
|
|
|
while (line != null)
|
|
|
|
|
{
|
|
|
|
|
ProcessLine(line);
|
|
|
|
|
line = reader.ReadLine();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected abstract void ProcessLine(string line);
|
|
|
|
|
}
|
2023-04-12 11:53:55 +00:00
|
|
|
|
}
|