Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions admin/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ func (s *Server) ListServices(_ context.Context,
Price: svc.Price,
Auth: string(svc.Auth),
AuthScheme: stringToAuthScheme(svc.AuthScheme),
Timeout: svc.Timeout,
})
}

Expand Down Expand Up @@ -278,6 +279,12 @@ func (s *Server) CreateService(ctx context.Context,

authScheme := authSchemeToString(req.AuthScheme)

if req.Timeout < 0 {
return nil, status.Error(
codes.InvalidArgument, "timeout must be >= 0",
)
}

newSvc := &proxy.Service{
Name: req.Name,
Address: req.Address,
Expand All @@ -286,6 +293,7 @@ func (s *Server) CreateService(ctx context.Context,
PathRegexp: req.PathRegexp,
Price: req.Price,
AuthScheme: authScheme,
Timeout: req.Timeout,
}
if normalizedAuth != "" {
newSvc.Auth = auth.Level(normalizedAuth)
Expand All @@ -312,6 +320,7 @@ func (s *Server) CreateService(ctx context.Context,
Auth: string(newSvc.Auth),
AuthScheme: newSvc.AuthScheme,
Price: newSvc.Price,
Timeout: newSvc.Timeout,
},
); err != nil {
log.Errorf("Error persisting service: %v", err)
Expand All @@ -327,6 +336,7 @@ func (s *Server) CreateService(ctx context.Context,
Price: newSvc.Price,
Auth: string(newSvc.Auth),
AuthScheme: stringToAuthScheme(newSvc.AuthScheme),
Timeout: newSvc.Timeout,
}, nil
}

Expand Down Expand Up @@ -438,6 +448,18 @@ func (s *Server) UpdateService(ctx context.Context,
updated.AuthScheme = authSchemeToString(*req.AuthScheme)
}

// Only apply timeout when explicitly set. Using `optional` lets
// callers distinguish "reset to 0 (no expiry)" from "leave as-is".
if req.Timeout != nil {
if req.GetTimeout() < 0 {
return nil, status.Error(
codes.InvalidArgument,
"timeout must be >= 0",
)
}
updated.Timeout = req.GetTimeout()
}

// Replace the pointer in the slice with the updated copy.
for i, svc := range services {
if svc.Name == req.Name {
Expand Down Expand Up @@ -466,6 +488,7 @@ func (s *Server) UpdateService(ctx context.Context,
Auth: string(updated.Auth),
AuthScheme: updated.AuthScheme,
Price: updated.Price,
Timeout: updated.Timeout,
},
); err != nil {
log.Errorf("Error persisting updated service: %v",
Expand All @@ -482,6 +505,7 @@ func (s *Server) UpdateService(ctx context.Context,
Price: updated.Price,
Auth: string(updated.Auth),
AuthScheme: stringToAuthScheme(updated.AuthScheme),
Timeout: updated.Timeout,
}, nil
}

Expand Down
109 changes: 109 additions & 0 deletions admin/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,115 @@ func TestUpdateServiceRejectsInvalidAuth(t *testing.T) {
require.Contains(t, err.Error(), "invalid freebie count")
}

func TestCreateServiceWithTimeout(t *testing.T) {
t.Parallel()

s := newTestServer()

svc, err := s.CreateService(context.Background(),
&adminrpc.CreateServiceRequest{
Name: "timed-svc",
Address: "localhost:9999",
PathRegexp: "^/api/timed/.*",
Price: 100,
Timeout: 60,
},
)
require.NoError(t, err)
require.Equal(t, int64(60), svc.Timeout)

// Verify timeout is returned by ListServices.
resp, err := s.ListServices(
context.Background(), &adminrpc.ListServicesRequest{},
)
require.NoError(t, err)
var found *adminrpc.Service
for _, s := range resp.Services {
if s.Name == "timed-svc" {
found = s
break
}
}
require.NotNil(t, found)
require.Equal(t, int64(60), found.Timeout)
}

func TestUpdateServiceTimeout(t *testing.T) {
t.Parallel()

s := newTestServer()

timeout := int64(120)
svc, err := s.UpdateService(context.Background(),
&adminrpc.UpdateServiceRequest{
Name: "test-svc",
Timeout: &timeout,
},
)
require.NoError(t, err)
require.Equal(t, int64(120), svc.Timeout)
}

func TestUpdateServiceCanSetTimeoutToZero(t *testing.T) {
t.Parallel()

s := newTestServer()

// First set a non-zero timeout.
timeout := int64(60)
_, err := s.UpdateService(context.Background(),
&adminrpc.UpdateServiceRequest{
Name: "test-svc",
Timeout: &timeout,
},
)
require.NoError(t, err)

// Now reset to 0 (no expiry) via optional field.
zero := int64(0)
svc, err := s.UpdateService(context.Background(),
&adminrpc.UpdateServiceRequest{
Name: "test-svc",
Timeout: &zero,
},
)
require.NoError(t, err)
require.Equal(t, int64(0), svc.Timeout)
}

func TestCreateServiceRejectsNegativeTimeout(t *testing.T) {
t.Parallel()

s := newTestServer()

_, err := s.CreateService(context.Background(),
&adminrpc.CreateServiceRequest{
Name: "bad-timeout-svc",
Address: "localhost:1234",
PathRegexp: "^/api/bad/.*",
Timeout: -1,
},
)
require.Error(t, err)
require.Contains(t, err.Error(), "timeout must be >= 0")
}

func TestUpdateServiceRejectsNegativeTimeout(t *testing.T) {
t.Parallel()

s := newTestServer()

timeout := int64(-5)
_, err := s.UpdateService(context.Background(),
&adminrpc.UpdateServiceRequest{
Name: "test-svc",
Timeout: &timeout,
},
)
require.Error(t, err)
require.Contains(t, err.Error(), "timeout must be >= 0")
}

func TestDeleteService(t *testing.T) {
t.Parallel()

Expand Down
53 changes: 44 additions & 9 deletions adminrpc/admin.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions adminrpc/admin.proto
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ message Service {
// auth_scheme specifies which payment auth scheme(s) are used for this
// service. Defaults to AUTH_SCHEME_L402 for backwards compatibility.
AuthScheme auth_scheme = 8;

// timeout is the per-service TTL in seconds. When non-zero, the L402 mint
// will include a _valid_until caveat so macaroons expire after this many
// seconds. A value of 0 means no expiry (no _valid_until caveat minted).
int64 timeout = 9;
}

message CreateServiceRequest {
Expand All @@ -85,6 +90,9 @@ message CreateServiceRequest {
// auth_scheme specifies which payment auth scheme(s) to use. Defaults to
// AUTH_SCHEME_L402 if unset.
AuthScheme auth_scheme = 8;

// timeout is the per-service TTL in seconds. See Service.timeout.
int64 timeout = 9;
}

message UpdateServiceRequest {
Expand All @@ -99,6 +107,10 @@ message UpdateServiceRequest {
// auth_scheme specifies which payment auth scheme(s) to use. When not
// set, the existing auth_scheme is preserved (not reset to L402).
optional AuthScheme auth_scheme = 8;

// timeout is the per-service TTL in seconds. See Service.timeout. When not
// set, the existing timeout is preserved.
optional int64 timeout = 9;
}

message DeleteServiceRequest { string name = 1; }
Expand Down
15 changes: 15 additions & 0 deletions adminrpc/admin.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@
"auth_scheme": {
"$ref": "#/definitions/adminrpcAuthScheme",
"description": "auth_scheme specifies which payment auth scheme(s) to use. When not\nset, the existing auth_scheme is preserved (not reset to L402)."
},
"timeout": {
"type": "string",
"format": "int64",
"description": "timeout is the per-service TTL in seconds. See Service.timeout. When not\nset, the existing timeout is preserved."
}
}
}
Expand Down Expand Up @@ -409,6 +414,11 @@
"auth_scheme": {
"$ref": "#/definitions/adminrpcAuthScheme",
"description": "auth_scheme specifies which payment auth scheme(s) to use. Defaults to\nAUTH_SCHEME_L402 if unset."
},
"timeout": {
"type": "string",
"format": "int64",
"description": "timeout is the per-service TTL in seconds. See Service.timeout."
}
}
},
Expand Down Expand Up @@ -554,6 +564,11 @@
"auth_scheme": {
"$ref": "#/definitions/adminrpcAuthScheme",
"description": "auth_scheme specifies which payment auth scheme(s) are used for this\nservice. Defaults to AUTH_SCHEME_L402 for backwards compatibility."
},
"timeout": {
"type": "string",
"format": "int64",
"description": "timeout is the per-service TTL in seconds. When non-zero, the L402 mint\nwill include a _valid_until caveat so macaroons expire after this many\nseconds. A value of 0 means no expiry (no _valid_until caveat minted)."
}
}
},
Expand Down
Loading