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
4 changes: 4 additions & 0 deletions components/api-server/openapi/openapi.gateways.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,10 @@ components:
type: string
readOnly: true
description: Web console address populated by the control plane
gateway_version:
type: string
readOnly: true
description: Runtime version from the last successful gateway health response
oidc:
type: string
readOnly: true
Expand Down
220 changes: 169 additions & 51 deletions components/api-server/pkg/api/grpc/hypershell/v1/gateways.pb.go

Large diffs are not rendered by default.

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

7 changes: 7 additions & 0 deletions components/api-server/pkg/api/openapi/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2907,6 +2907,10 @@ components:
description: Web console address populated by the control plane
readOnly: true
type: string
gateway_version:
description: Runtime version from the last successful gateway health response
readOnly: true
type: string
oidc:
description: JSON-encoded OIDC authentication configuration (auto-populated
by Keycloak provisioning)
Expand Down Expand Up @@ -2949,6 +2953,7 @@ components:
id: id
href: href
fleet_id: fleet_id
gateway_version: gateway_version
external_dns: external_dns
phase: phase
image: image
Expand Down Expand Up @@ -2996,6 +3001,7 @@ components:
id: id
href: href
fleet_id: fleet_id
gateway_version: gateway_version
external_dns: external_dns
phase: phase
image: image
Expand Down Expand Up @@ -3024,6 +3030,7 @@ components:
id: id
href: href
fleet_id: fleet_id
gateway_version: gateway_version
external_dns: external_dns
phase: phase
image: image
Expand Down
26 changes: 26 additions & 0 deletions components/api-server/pkg/api/openapi/docs/Gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Name | Type | Description | Notes
**ServerDnsNames** | Pointer to **[]string** | DNS names for TLS certificate SANs | [optional]
**RouteAddress** | Pointer to **string** | External route address populated by the control plane | [optional] [readonly]
**ConsoleAddress** | Pointer to **string** | Web console address populated by the control plane | [optional] [readonly]
**GatewayVersion** | Pointer to **string** | Runtime version from the last successful gateway health response | [optional] [readonly]
**Oidc** | Pointer to **string** | JSON-encoded OIDC authentication configuration (auto-populated by Keycloak provisioning) | [optional] [readonly]
**Route** | Pointer to **string** | JSON-encoded route configuration | [optional]
**CredentialDriver** | Pointer to **string** | JSON-encoded credential storage driver configuration | [optional]
Expand Down Expand Up @@ -545,6 +546,31 @@ SetConsoleAddress sets ConsoleAddress field to given value.

HasConsoleAddress returns a boolean if a field has been set.

### GetGatewayVersion

`func (o *Gateway) GetGatewayVersion() string`

GetGatewayVersion returns the GatewayVersion field if non-nil, zero value otherwise.

### GetGatewayVersionOk

`func (o *Gateway) GetGatewayVersionOk() (*string, bool)`

GetGatewayVersionOk returns a tuple with the GatewayVersion field if it's non-nil, zero value otherwise
and a boolean to check if the value has been set.

### SetGatewayVersion

`func (o *Gateway) SetGatewayVersion(v string)`

SetGatewayVersion sets GatewayVersion field to given value.

### HasGatewayVersion

`func (o *Gateway) HasGatewayVersion() bool`

HasGatewayVersion returns a boolean if a field has been set.

### GetOidc

`func (o *Gateway) GetOidc() string`
Expand Down
37 changes: 37 additions & 0 deletions components/api-server/pkg/api/openapi/model_gateway.go

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

63 changes: 58 additions & 5 deletions components/api-server/plugins/gateways/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ type GatewayDao interface {
// return and event-emission contract matches AdjustActiveSandboxCount.
SetActiveSandboxCount(ctx context.Context, namespace string, count int) (resulting int, err error)

// SetGatewayVersion atomically sets the runtime version of the live gateway.
// It emits an update event only when the stored value changes.
SetGatewayVersion(ctx context.Context, id, version string) (resulting string, err error)

CountByPhase(ctx context.Context) (map[string]int64, error)
}

Expand All @@ -44,6 +48,11 @@ type sandboxCountRow struct {
ActiveSandboxCount *int
}

type gatewayVersionRow struct {
ID string
GatewayVersion *string
}

var _ GatewayDao = &sqlGatewayDao{}

type sqlGatewayDao struct {
Expand Down Expand Up @@ -83,11 +92,9 @@ func (d *sqlGatewayDao) Create(ctx context.Context, gateway *Gateway) (*Gateway,

func (d *sqlGatewayDao) Replace(ctx context.Context, gateway *Gateway) (*Gateway, error) {
g2 := (*d.sessionFactory).New(ctx)
// Omit active_sandbox_count: it is owned exclusively by the atomic
// AdjustActiveSandboxCount / SetActiveSandboxCount path. Saving it here would
// write back the value read into `gateway`, clobbering any concurrent
// count adjustment with a stale number.
if err := g2.Omit(clause.Associations, "ActiveSandboxCount").Save(gateway).Error; err != nil {
// Omit fields that have dedicated reconciliation writers. Saving these
// values here could overwrite a concurrent observation with stale data.
if err := g2.Omit(clause.Associations, "ActiveSandboxCount", "GatewayVersion").Save(gateway).Error; err != nil {
db.MarkForRollback(ctx, err)
return nil, err
}
Expand Down Expand Up @@ -147,6 +154,45 @@ RETURNING id, active_sandbox_count`
return d.execSandboxCount(ctx, namespace, stmt, count, namespace, count)
}

func (d *sqlGatewayDao) SetGatewayVersion(ctx context.Context, id, version string) (string, error) {
g2 := (*d.sessionFactory).New(ctx)

var resulting string
txErr := g2.Transaction(func(tx *gorm.DB) error {
var row gatewayVersionRow
if err := tx.Raw(`
UPDATE gateways
SET gateway_version = ?, updated_at = NOW()
WHERE id = ? AND deleted_at IS NULL
AND gateway_version IS DISTINCT FROM ?
RETURNING id, gateway_version`, version, id, version).Scan(&row).Error; err != nil {
return err
}
if row.ID != "" {
resulting = derefString(row.GatewayVersion)
return emitGatewayEventTx(tx, row.ID)
}

var current gatewayVersionRow
if err := tx.Raw(
`SELECT id, gateway_version FROM gateways WHERE id = ? AND deleted_at IS NULL`,
id,
).Scan(&current).Error; err != nil {
return err
}
if current.ID == "" {
return gorm.ErrRecordNotFound
}
resulting = derefString(current.GatewayVersion)
return nil
})
if txErr != nil {
db.MarkForRollback(ctx, txErr)
return "", txErr
}
return resulting, nil
}

// execSandboxCount runs a guarded sandbox-count UPDATE ... RETURNING inside a
// single transaction. When the UPDATE changes the stored value it emits the
// Gateway update Event in that same transaction (transactional outbox), so the
Expand Down Expand Up @@ -214,6 +260,13 @@ func derefCount(v *int) int {
return *v
}

func derefString(value *string) string {
if value == nil {
return ""
}
return *value
}

func (d *sqlGatewayDao) CountByPhase(ctx context.Context) (map[string]int64, error) {
g2 := (*d.sessionFactory).New(ctx)
type row struct {
Expand Down
21 changes: 11 additions & 10 deletions components/api-server/plugins/gateways/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@ func newGateway(id string) (*gateways.Gateway, error) {
gatewayService := gateways.Service(&environments.Environment().Services)

gateway := &gateways.Gateway{
Name: "test-name",
FleetId: "test-fleet_id",
ClusterId: "test-cluster_id",
ReleaseId: "test-release_id",
DatabaseId: "test-database_id",
ExternalDns: stringPtr("test-external_dns"),
TlsMode: stringPtr("test-tls_mode"),
ServiceType: stringPtr("test-service_type"),
Status: stringPtr("test-status"),
Phase: stringPtr("test-phase"),
Name: "test-name",
FleetId: "test-fleet_id",
ClusterId: "test-cluster_id",
ReleaseId: "test-release_id",
DatabaseId: "test-database_id",
ExternalDns: stringPtr("test-external_dns"),
TlsMode: stringPtr("test-tls_mode"),
ServiceType: stringPtr("test-service_type"),
Status: stringPtr("test-status"),
Phase: stringPtr("test-phase"),
GatewayVersion: stringPtr("0.0.109"),
}

sub, err := gatewayService.Create(context.Background(), gateway)
Expand Down
17 changes: 17 additions & 0 deletions components/api-server/plugins/gateways/grpc_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,23 @@ func (h *gatewayGRPCHandler) SetActiveSandboxCount(ctx context.Context, req *pb.
return &pb.SetActiveSandboxCountResponse{ActiveSandboxCount: int32(count)}, nil
}

// SetGatewayVersion stores only the last runtime version that the health
// reconciler observed. The dedicated write cannot overwrite unrelated fields.
func (h *gatewayGRPCHandler) SetGatewayVersion(ctx context.Context, req *pb.SetGatewayVersionRequest) (*pb.SetGatewayVersionResponse, error) {
if err := grpcutil.ValidateRequiredID(req.Id); err != nil {
return nil, err
}
if err := grpcutil.ValidateStringField("gateway_version", req.GatewayVersion, true); err != nil {
return nil, err
}

version, svcErr := h.service.SetGatewayVersion(ctx, req.Id, req.GatewayVersion)
if svcErr != nil {
return nil, grpcutil.ServiceErrorToGRPC(svcErr)
}
return &pb.SetGatewayVersionResponse{GatewayVersion: version}, nil
}

func (h *gatewayGRPCHandler) DeleteGateway(ctx context.Context, req *pb.DeleteGatewayRequest) (*pb.DeleteGatewayResponse, error) {
if err := grpcutil.ValidateRequiredID(req.Id); err != nil {
return nil, err
Expand Down
Loading
Loading