Skip to content

Commit e970e85

Browse files
yroblataskbot
andauthored
fix: throw error when there is a duplicate workload name (#1395)
Co-authored-by: taskbot <[email protected]>
1 parent 8ab0786 commit e970e85

File tree

5 files changed

+91
-0
lines changed

5 files changed

+91
-0
lines changed

cmd/thv/app/run.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,13 @@ func runCmdFunc(cmd *cobra.Command, args []string) error {
149149
return fmt.Errorf("failed to create workload manager: %v", err)
150150
}
151151

152+
exists, err := workloadManager.DoesWorkloadExist(ctx, runFlags.Name)
153+
if err != nil {
154+
return fmt.Errorf("failed to check if workload exists: %v", err)
155+
}
156+
if exists {
157+
return fmt.Errorf("workload with name '%s' already exists", runFlags.Name)
158+
}
152159
err = validateGroup(ctx, workloadManager, serverOrImage)
153160
if err != nil {
154161
return err

pkg/api/v1/workloads.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,17 @@ func (s *WorkloadRoutes) createWorkload(w http.ResponseWriter, r *http.Request)
275275
return
276276
}
277277

278+
// check if the workload already exists
279+
exists, err := s.workloadManager.DoesWorkloadExist(ctx, req.Name)
280+
if err != nil {
281+
http.Error(w, fmt.Sprintf("Failed to check if workload exists: %v", err), http.StatusInternalServerError)
282+
return
283+
}
284+
if exists {
285+
http.Error(w, fmt.Sprintf("Workload with name %s already exists", req.Name), http.StatusConflict)
286+
return
287+
}
288+
278289
// NOTE: None of the k8s-related config logic is included here.
279290
runSecrets := secrets.SecretParametersToCLI(req.Secrets)
280291
runConfig, err := runner.NewRunConfigBuilder().

pkg/workloads/manager.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ type Manager interface {
6262
MoveToDefaultGroup(ctx context.Context, workloadNames []string, groupName string) error
6363
// ListWorkloadsInGroup returns all workload names that belong to the specified group, including stopped workloads.
6464
ListWorkloadsInGroup(ctx context.Context, groupName string) ([]string, error)
65+
// DoesWorkloadExist checks if a workload with the given name exists.
66+
DoesWorkloadExist(ctx context.Context, workloadName string) (bool, error)
6567
}
6668

6769
type defaultManager struct {
@@ -114,6 +116,23 @@ func (d *defaultManager) GetWorkload(ctx context.Context, workloadName string) (
114116
return d.statuses.GetWorkload(ctx, workloadName)
115117
}
116118

119+
func (d *defaultManager) DoesWorkloadExist(ctx context.Context, workloadName string) (bool, error) {
120+
// check if workload exists by trying to get it
121+
workload, err := d.statuses.GetWorkload(ctx, workloadName)
122+
if err != nil {
123+
if errors.Is(err, rt.ErrWorkloadNotFound) {
124+
return false, nil
125+
}
126+
return false, fmt.Errorf("failed to check if workload exists: %w", err)
127+
}
128+
129+
// now check if the workload is not in error
130+
if workload.Status == rt.WorkloadStatusError {
131+
return false, nil
132+
}
133+
return true, nil
134+
}
135+
117136
func (d *defaultManager) ListWorkloads(ctx context.Context, listAll bool, labelFilters ...string) ([]core.Workload, error) {
118137
// For the sake of minimizing changes, delegate to the status manager.
119138
// Whether this method should still belong to the workload manager is TBD.

pkg/workloads/mocks/mock_manager.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/e2e/osv_mcp_server_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,4 +408,43 @@ var _ = Describe("OsvMcpServer", Serial, func() {
408408
})
409409
})
410410
})
411+
412+
Describe("We cannot create duplicate servers", func() {
413+
It("should reject starting a second workload with the same name [Serial]", func() {
414+
// unique name for this test
415+
serverName := generateUniqueServerName("osv-duplicate-name-test")
416+
417+
By("Starting the first OSV MCP server")
418+
e2e.NewTHVCommand(config, "run",
419+
"--name", serverName,
420+
"--transport", "sse", "osv").ExpectSuccess()
421+
422+
// ensure it's actually up before attempting the duplicate
423+
err := e2e.WaitForMCPServer(config, serverName, 60*time.Second)
424+
Expect(err).ToNot(HaveOccurred(), "first server should start")
425+
426+
By("Attempting to start a second server with the same name")
427+
// Use Run() (not ExpectSuccess) so we can assert failure +
428+
// examine stdout/stderr
429+
stdout, stderr, runErr := e2e.NewTHVCommand(config, "run",
430+
"--name", serverName,
431+
"--transport", "sse",
432+
"osv").Run()
433+
434+
// The second run must fail because the name already exists
435+
Expect(runErr).To(HaveOccurred(), "second server with same name should fail")
436+
// Be flexible on the exact message, but check for a helpful hint
437+
Expect(stdout+stderr).To(
438+
ContainSubstring("already exists"),
439+
"CLI should report a duplicate-name conflict",
440+
)
441+
442+
// Cleanup
443+
if config.CleanupAfter {
444+
cerr := e2e.StopAndRemoveMCPServer(config, serverName)
445+
Expect(cerr).ToNot(HaveOccurred(), "cleanup should succeed")
446+
}
447+
})
448+
449+
})
411450
})

0 commit comments

Comments
 (0)