Skip to content

Commit 6b365c6

Browse files
authored
Validate buffer names (#182)
1 parent e8ad60a commit 6b365c6

File tree

7 files changed

+61
-8
lines changed

7 files changed

+61
-8
lines changed

cli/integrationtest/controlplane_test.go

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,7 @@ maxReplicas: 1
688688
require.Equal(t, "ubuntu", receivedSpec.Image)
689689
}
690690
func TestInvalidCodespecNames(t *testing.T) {
691+
t.Parallel()
691692
testCases := []struct {
692693
name string
693694
valid bool
@@ -718,8 +719,42 @@ func TestInvalidCodespecNames(t *testing.T) {
718719
}
719720
}
720721

721-
func TestCodespecNameRequirements(t *testing.T) {
722-
runTyger("codespec", "create", "Foo", "--image", BasicImage)
722+
func TestInvalidBufferNames(t *testing.T) {
723+
t.Parallel()
724+
testCases := []string{
725+
"FOO",
726+
"foo_bar",
727+
"-foo",
728+
"bar-",
729+
}
730+
for _, tC := range testCases {
731+
t.Run(tC, func(t *testing.T) {
732+
_, stdErr, err := runTyger("codespec", "create", "testinvalidbuffernames", "--image", BasicImage, "--input", tC)
733+
require.NotNil(t, err)
734+
require.Contains(t, stdErr, "Buffer names must consist")
735+
})
736+
}
737+
}
738+
739+
func TestInvalidBufferNamesInInlineCodespec(t *testing.T) {
740+
t.Parallel()
741+
require := require.New(t)
742+
743+
runSpec := fmt.Sprintf(`
744+
job:
745+
codespec:
746+
image: %s
747+
buffers:
748+
inputs: ["INVALID_NAME"]
749+
timeoutSeconds: 600`, BasicImage)
750+
751+
tempDir := t.TempDir()
752+
runSpecPath := filepath.Join(tempDir, "runspec.yaml")
753+
require.NoError(os.WriteFile(runSpecPath, []byte(runSpec), 0644))
754+
755+
_, stdErr, err := runTyger("run", "exec", "--file", runSpecPath)
756+
require.NotNil(err)
757+
require.Contains(stdErr, "Buffer names must consist")
723758
}
724759

725760
// Verify that a run using a codespec that requires a GPU

server/ControlPlane/Compute/Docker/DockerRunCreator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ public async Task<Run> CreateRun(Run run, string? idempotencyKey, CancellationTo
107107
throw new ArgumentException($"The codespec for the job is required to be a job codespec");
108108
}
109109

110+
Validator.ValidateObject(jobCodespec, new(jobCodespec));
111+
110112
try
111113
{
112114
await _client.Images.InspectImageAsync(jobCodespec.Image, cancellationToken: cancellationToken);

server/ControlPlane/Compute/Kubernetes/KubernetesRunCreator.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,18 @@ private async Task ProcessPageOfNewRuns(IReadOnlyList<Run> runs, CancellationTok
109109
catch (OperationCanceledException) when (ct.IsCancellationRequested)
110110
{
111111
}
112+
catch (HttpOperationException e) when (e.Response.StatusCode is HttpStatusCode.TooManyRequests or HttpStatusCode.InternalServerError or HttpStatusCode.ServiceUnavailable or HttpStatusCode.GatewayTimeout)
113+
{
114+
_logger.RetryableErrorCreatingRunResources(run.Id!.Value, e);
115+
}
116+
catch (IOException e)
117+
{
118+
_logger.RetryableErrorCreatingRunResources(run.Id!.Value, e);
119+
}
112120
catch (Exception ex)
113121
{
114122
_logger.ErrorCreatingRunResources(run.Id!.Value, ex);
123+
await Repository.ForceUpdateRun(run with { Status = RunStatus.Failed, StatusReason = "Failed to create Kubernetes objects. See server logs." }, cancellationToken);
115124
}
116125
});
117126
}
@@ -127,6 +136,8 @@ private async Task<Run> CreateRunCore(Run run, string? idempotencyKey, Cancellat
127136
throw new ArgumentException($"The codespec for the job is required to be a job codespec");
128137
}
129138

139+
Validator.ValidateObject(jobCodespec, new(jobCodespec));
140+
130141
run = run with
131142
{
132143
Cluster = targetCluster.Name,

server/ControlPlane/Compute/Kubernetes/KubernetesRunLogReader.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ namespace Tyger.ControlPlane.Compute.Kubernetes;
1717

1818
public class KubernetesRunLogReader : ILogSource
1919
{
20-
private static readonly Pipeline s_emptyPipeline = new([]);
2120
private readonly k8s.Kubernetes _client;
2221
private readonly Repository _repository;
2322
private readonly ILogArchive _logArchive;
@@ -63,7 +62,7 @@ public KubernetesRunLogReader(
6362
return await FollowLogs(run, options, cancellationToken);
6463
}
6564

66-
return (await InnerGetLogs()) ?? s_emptyPipeline;
65+
return (await InnerGetLogs()) ?? new([]);
6766
}
6867

6968
private Task<Pipeline> FollowLogs(Run run, GetLogsOptions options, CancellationToken cancellationToken)

server/ControlPlane/Compute/Kubernetes/LoggerExtensions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ public static partial class LoggerExtensions
2020
[LoggerMessage(LogLevel.Error, "Error creating run {runId} resources.")]
2121
public static partial void ErrorCreatingRunResources(this ILogger logger, long runId, Exception exception);
2222

23+
[LoggerMessage(LogLevel.Warning, "Retryable error creating run {runId} resources.")]
24+
public static partial void RetryableErrorCreatingRunResources(this ILogger logger, long runId, Exception exception);
25+
2326
[LoggerMessage(LogLevel.Error, "Error while watching resources.")]
2427
public static partial void ErrorWatchingResources(this ILogger logger, Exception exception);
2528

server/ControlPlane/Database/Repository.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -723,9 +723,9 @@ RETURNING modified_at
723723

724724
await tx.CommitAsync(cancellationToken);
725725

726-
if (updatedRun.Status is RunStatus.Succeeded or RunStatus.Failed)
726+
if (updatedRun.Status is RunStatus.Succeeded or RunStatus.Failed && updatedRun.FinishedAt.HasValue)
727727
{
728-
var timeToDetect = DateTimeOffset.UtcNow - updatedRun.FinishedAt!.Value;
728+
var timeToDetect = DateTimeOffset.UtcNow - updatedRun.FinishedAt.Value;
729729
_logger.TerminalStateRecorded(state.Id, timeToDetect);
730730
}
731731
}, cancellationToken);

server/ControlPlane/Model/Model.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,9 @@ public IEnumerable<ValidationResult> Validate(ValidationContext validationContex
243243
yield return new ValidationResult(string.Format(CultureInfo.InvariantCulture, "All buffer names must be unique across inputs and outputs. Buffer names are case-insensitive. '{0}' is duplicated", group.Key));
244244
}
245245

246-
if (group.Key.Contains('/'))
246+
if (!BufferNameRegex().IsMatch(group.Key))
247247
{
248-
yield return new ValidationResult(string.Format(CultureInfo.InvariantCulture, "The buffer '{0}' cannot contain '/' in its name.", group.Key));
248+
yield return new ValidationResult(string.Format(CultureInfo.InvariantCulture, "The name of buffer '{0}' is invalid. Buffer names must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character.", group.Key));
249249
}
250250
}
251251
}
@@ -345,6 +345,9 @@ private static IEnumerable<ValidationResult> VerifyNoBufferReferencesUsedBySocke
345345

346346
[GeneratedRegex(@"\$\(([^)]+)\)|\$\$([^)]+)")]
347347
internal static partial Regex EnvironmentVariableExpansionRegex();
348+
349+
[GeneratedRegex(@"^[a-z0-9]([-a-z0-9]*[a-z0-9])?$")]
350+
internal static partial Regex BufferNameRegex();
348351
}
349352

350353
[Equatable]

0 commit comments

Comments
 (0)