Make AddRoute idempotent (upsert instead of reject duplicates)
AddRoute now updates an existing route if one already exists for the same (listener, hostname) pair, instead of returning AlreadyExists. This makes repeated deploys idempotent — the MCP agent can register routes on every deploy without needing to remove them first. - DB: INSERT ... ON CONFLICT DO UPDATE (SQLite upsert) - In-memory: overwrite existing route unconditionally - gRPC: error code changed from AlreadyExists to Internal (for real DB errors) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -250,15 +250,30 @@ func TestRouteL7Fields(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouteDuplicateHostname(t *testing.T) {
|
||||
func TestRouteUpsert(t *testing.T) {
|
||||
store := openTestDB(t)
|
||||
|
||||
listenerID, _ := store.CreateListener(":443", false, 0)
|
||||
if _, err := store.CreateRoute(listenerID, "example.com", "127.0.0.1:8443", "l4", "", "", false, false); err != nil {
|
||||
t.Fatalf("first create: %v", err)
|
||||
}
|
||||
if _, err := store.CreateRoute(listenerID, "example.com", "127.0.0.1:9443", "l4", "", "", false, false); err == nil {
|
||||
t.Fatal("expected error for duplicate hostname on same listener")
|
||||
// Same (listener, hostname) with different backend — should upsert, not error.
|
||||
if _, err := store.CreateRoute(listenerID, "example.com", "127.0.0.1:9443", "l7", "/cert.pem", "/key.pem", false, false); err != nil {
|
||||
t.Fatalf("upsert: %v", err)
|
||||
}
|
||||
|
||||
routes, err := store.ListRoutes(listenerID)
|
||||
if err != nil {
|
||||
t.Fatalf("list routes: %v", err)
|
||||
}
|
||||
if len(routes) != 1 {
|
||||
t.Fatalf("expected 1 route after upsert, got %d", len(routes))
|
||||
}
|
||||
if routes[0].Backend != "127.0.0.1:9443" {
|
||||
t.Fatalf("expected updated backend, got %q", routes[0].Backend)
|
||||
}
|
||||
if routes[0].Mode != "l7" {
|
||||
t.Fatalf("expected updated mode, got %q", routes[0].Mode)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,11 +39,20 @@ func (s *Store) ListRoutes(listenerID int64) ([]Route, error) {
|
||||
return routes, rows.Err()
|
||||
}
|
||||
|
||||
// CreateRoute inserts a route and returns its ID.
|
||||
// CreateRoute inserts or updates a route and returns its ID. If a route
|
||||
// for the same (listener_id, hostname) already exists, it is updated
|
||||
// with the new values (upsert), making the operation idempotent.
|
||||
func (s *Store) CreateRoute(listenerID int64, hostname, backend, mode, tlsCert, tlsKey string, backendTLS, sendProxyProtocol bool) (int64, error) {
|
||||
result, err := s.db.Exec(
|
||||
`INSERT INTO routes (listener_id, hostname, backend, mode, tls_cert, tls_key, backend_tls, send_proxy_protocol)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(listener_id, hostname) DO UPDATE SET
|
||||
backend = excluded.backend,
|
||||
mode = excluded.mode,
|
||||
tls_cert = excluded.tls_cert,
|
||||
tls_key = excluded.tls_key,
|
||||
backend_tls = excluded.backend_tls,
|
||||
send_proxy_protocol = excluded.send_proxy_protocol`,
|
||||
listenerID, hostname, backend, mode, tlsCert, tlsKey, backendTLS, sendProxyProtocol,
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user