mirror of
https://github.com/logos-storage/logos-storage-nim-cs-dist-tests.git
synced 2026-05-26 19:19:54 +00:00
Replace the indirect `SetSchedulingAffinity(notIn: "false")` / `allow-tests-pods` mechanism with `ScheduleInPoolsWithLabel(key, value)` and `AddToleration(key, value, effect)` in ContainerRecipeFactory. This is much more readable from an API perspective. `SetSchedulingAffinity(notIn: "false")` was a double-negative (hard to reason about) and it was not clear that this was meant to schedule on pools with labels `allow-tests-pods=true`. Previously, pods were steered to the spot node pool via a node affinity exclusion on a boolean label (`allow-tests-pods NotIn ["false"]`), and spot taint toleration was added implicitly by using the `system-node-critical` priority class. The priority class was removed earlier because it caused a ResourceQuota admission error in GCP, which silently broke spot node scheduling. The new API is explicit: recipes call `ScheduleInPoolsWithLabel` to set a nodeSelector label that targets the intended pool, and `AddToleration` to declare any taints the pool carries. Tolerations are set at the recipe level to allow for the recipe to move back to Digital Ocean if needed (removing the unneeded toleration). All four recipes (storage, prometheus, discord bot, rewarder bot) now call both. Cleanup applied alongside: - `PodToleration` converted to a record for structural equality and simpler deduplication - `ExposedPorts`, `InternalPorts`, `EnvVars`, `Volumes` on `ContainerRecipe` changed to `IReadOnlyList<T>` for consistent immutable typing - `SetCriticalPriority` property renamed to `IsCriticalPriority` - `GetPriorityClassName` returns `string?` instead of `null!` - `Reset()` extracted in `ContainerRecipeFactory` to consolidate post-create state reset - Fixed bug: `nodePoolLabels` and `tolerations` were passed by reference and then cleared, leaving the recipe with empty collections; now snapshotted before clearing - `SchedulingAffinity.cs` deleted (no remaining callers)
152 lines
5.3 KiB
C#
152 lines
5.3 KiB
C#
namespace KubernetesWorkflow.Recipe
|
|
{
|
|
public class ContainerRecipe
|
|
{
|
|
public ContainerRecipe(DateTime recipeCreatedUtc, int number, string? nameOverride, string image, ContainerResources resources, IReadOnlyDictionary<string, string> nodePoolLabels, IReadOnlyList<PodToleration> tolerations, CommandOverride commandOverride, bool isCriticalPriority, IReadOnlyList<Port> exposedPorts, IReadOnlyList<Port> internalPorts, IReadOnlyList<EnvVar> envVars, PodLabels podLabels, PodAnnotations podAnnotations, IReadOnlyList<VolumeMount> volumes, ContainerAdditionals additionals)
|
|
{
|
|
RecipeCreatedUtc = recipeCreatedUtc;
|
|
Number = number;
|
|
NameOverride = nameOverride;
|
|
Image = image;
|
|
Resources = resources;
|
|
NodePoolLabels = nodePoolLabels;
|
|
Tolerations = tolerations;
|
|
CommandOverride = commandOverride;
|
|
IsCriticalPriority = isCriticalPriority;
|
|
ExposedPorts = exposedPorts;
|
|
InternalPorts = internalPorts;
|
|
EnvVars = envVars;
|
|
PodLabels = podLabels;
|
|
PodAnnotations = podAnnotations;
|
|
Volumes = volumes;
|
|
Additionals = additionals;
|
|
|
|
if (NameOverride != null)
|
|
{
|
|
Name = $"{K8sNameUtils.Format(NameOverride)}-{Number}";
|
|
}
|
|
else
|
|
{
|
|
Name = $"ctnr{Number}";
|
|
}
|
|
|
|
if (exposedPorts.Any(p => string.IsNullOrEmpty(p.Tag))) throw new Exception("Port tags are required for all exposed ports.");
|
|
}
|
|
|
|
public DateTime RecipeCreatedUtc { get; }
|
|
public string Name { get; }
|
|
public int Number { get; }
|
|
public string? NameOverride { get; }
|
|
public ContainerResources Resources { get; }
|
|
public IReadOnlyDictionary<string, string> NodePoolLabels { get; }
|
|
public IReadOnlyList<PodToleration> Tolerations { get; }
|
|
public CommandOverride CommandOverride { get; }
|
|
public bool IsCriticalPriority { get; }
|
|
public string Image { get; }
|
|
public IReadOnlyList<Port> ExposedPorts { get; }
|
|
public IReadOnlyList<Port> InternalPorts { get; }
|
|
public IReadOnlyList<EnvVar> EnvVars { get; }
|
|
public PodLabels PodLabels { get; }
|
|
public PodAnnotations PodAnnotations { get; }
|
|
public IReadOnlyList<VolumeMount> Volumes { get; }
|
|
public ContainerAdditionals Additionals { get; }
|
|
|
|
public Port? GetPortByTag(string tag)
|
|
{
|
|
return ExposedPorts.Concat(InternalPorts).SingleOrDefault(p => p.Tag == tag);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"(container-recipe: {Name}, image: {Image}, " +
|
|
$"exposedPorts: {string.Join(",", ExposedPorts.Select(p => p.Number))}, " +
|
|
$"internalPorts: {string.Join(",", InternalPorts.Select(p => p.Number))}, " +
|
|
$"envVars: {string.Join(",", EnvVars.Select(v => v.ToString()))}, " +
|
|
$"limits: {Resources}, " +
|
|
$"nodePoolLabels: [{string.Join(",", NodePoolLabels.Select(kvp => $"{kvp.Key}={kvp.Value}"))}], " +
|
|
$"tolerations: [{string.Join(",", Tolerations.Select(t => $"{t.Key}={t.Value}:{t.Effect}"))}], " +
|
|
$"volumes: {string.Join(",", Volumes.Select(v => $"'{v.MountPath}'"))}";
|
|
}
|
|
}
|
|
|
|
public class Port
|
|
{
|
|
public Port(int number, string tag, PortProtocol protocol)
|
|
{
|
|
Number = number;
|
|
Tag = tag;
|
|
Protocol = protocol;
|
|
|
|
if (string.IsNullOrWhiteSpace(Tag))
|
|
{
|
|
throw new Exception("A unique port tag is required");
|
|
}
|
|
}
|
|
|
|
public int Number { get; }
|
|
public string Tag { get; }
|
|
public PortProtocol Protocol { get; }
|
|
|
|
public bool IsTcp()
|
|
{
|
|
return Protocol == PortProtocol.TCP;
|
|
}
|
|
|
|
public bool IsUdp()
|
|
{
|
|
return Protocol == PortProtocol.UDP;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
if (string.IsNullOrEmpty(Tag)) return $"untagged-port={Number}/{Protocol}";
|
|
return $"{Tag}={Number}/{Protocol}";
|
|
}
|
|
}
|
|
|
|
public enum PortProtocol
|
|
{
|
|
TCP,
|
|
UDP
|
|
}
|
|
|
|
public record PodToleration(string Key, string Value, string Effect);
|
|
|
|
public class EnvVar
|
|
{
|
|
public EnvVar(string name, string value)
|
|
{
|
|
Name = name;
|
|
Value = value;
|
|
}
|
|
|
|
public string Name { get; }
|
|
public string Value { get; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"'{Name}' = '{Value}'";
|
|
}
|
|
}
|
|
|
|
public class VolumeMount
|
|
{
|
|
public VolumeMount(string volumeName, string mountPath, string? subPath = null, string? resourceQuantity = null, string? secret = null, string? hostPath = null)
|
|
{
|
|
VolumeName = volumeName;
|
|
MountPath = mountPath;
|
|
SubPath = subPath;
|
|
ResourceQuantity = resourceQuantity;
|
|
Secret = secret;
|
|
HostPath = hostPath;
|
|
}
|
|
|
|
public string VolumeName { get; }
|
|
public string MountPath { get; }
|
|
public string? SubPath { get; }
|
|
public string? ResourceQuantity { get; }
|
|
public string? Secret { get; }
|
|
public string? HostPath { get; }
|
|
}
|
|
}
|