mirror of
https://github.com/versity/versitygw.git
synced 2026-09-23 16:34:18 +00:00
Add --rdma-rc-enable (VGW_RDMA_RC_ENABLE, default false) so the RC control routes and data plane start without implying the cuObject v1 backend. The global CLI hook resolves the mode first: gateway commands require either --rdma-ip or --rdma-rc-enable, and neither path implies the other, so a v2-only deployment boots without a v1 address. The v1 port, retry, pool, and DCI validations also ran for every mode, so stale v1 environment values blocked v2-only startup with unrelated errors. Those validations moved behind the v1 check as a cgo-free helper in internal/rdmamode, exercised alongside the mode matrix, and the CQ-depth limit keeps its 32-bit boundary check there. The RC data plane builds its IAM service, starts the session server, and mounts the three control routes behind SigV4. Startup and shutdown own the backend chain through idempotent guards: the gateway wraps the input backend in a once guard and defers a rollback closure that follows the chain as it grows; the completed v1 chain gets its own once owner, and the RC service is closed first through a backend wrapper installed right after a successful session-server init. Startup failures close exactly what was built, the RunVersityGW lifecycle consumes the same guards instead of closing again, and the RC sessions drain before the backend chain shuts down.
122 lines
4.1 KiB
Go
122 lines
4.1 KiB
Go
// Copyright 2026 Versity Software
|
|
// This file is licensed under the Apache License, Version 2.0
|
|
// (the "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an "AS
|
|
// IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
|
// express or implied. See the License for the specific language
|
|
// governing permissions and limitations under the License.
|
|
|
|
// Package rdmamode resolves which RDMA paths a gateway runs and
|
|
// orders their shutdown. It is deliberately free of cgo so the
|
|
// behavior is testable on any build platform.
|
|
package rdmamode
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/versity/versitygw/backend"
|
|
)
|
|
|
|
// Mode resolves which RDMA paths run: the cuObject v1 backend
|
|
// follows the legacy --rdma-ip contract, and the hipobj-rc-v2
|
|
// control routes follow --rdma-rc-enable. Neither flag implies
|
|
// the other.
|
|
func Mode(rdmaIP string, rcEnable bool) (v1, v2 bool) {
|
|
return strings.TrimSpace(rdmaIP) != "", rcEnable
|
|
}
|
|
|
|
// V1Settings carries the cuObject v1 tunings that are validated
|
|
// only while the v1 backend runs.
|
|
type V1Settings struct {
|
|
Port uint
|
|
RetryCount uint
|
|
PoolBufSize int
|
|
PoolBufCnt int
|
|
TunablesSet bool
|
|
NumDCIs int
|
|
CQDepth uint
|
|
}
|
|
|
|
// V1ValidationError describes the first invalid v1 setting, or
|
|
// the empty string when every setting is valid. Stale v1 values
|
|
// in the environment must not block v2-only or plain-S3
|
|
// startup, so the gateway consults this only when v1 is on.
|
|
func V1ValidationError(s V1Settings) string {
|
|
switch {
|
|
case s.Port > 65535:
|
|
return fmt.Sprintf("rdma-port %d is out of range (0-65535)", s.Port)
|
|
case s.RetryCount > 7:
|
|
return fmt.Sprintf("rdma-retry-count %d is out of range (0-7)", s.RetryCount)
|
|
case s.PoolBufSize <= 0:
|
|
return fmt.Sprintf("pool-buf-size %d must be positive", s.PoolBufSize)
|
|
case s.PoolBufCnt <= 0:
|
|
return fmt.Sprintf("pool-buf-count %d must be positive", s.PoolBufCnt)
|
|
case s.TunablesSet && s.NumDCIs <= 0:
|
|
return fmt.Sprintf("rdma-num-dcis %d must be positive", s.NumDCIs)
|
|
case s.TunablesSet && s.CQDepth > math.MaxUint32:
|
|
return fmt.Sprintf("rdma-cq-depth %d exceeds the 32-bit limit", s.CQDepth)
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// Closer is the close operation of the RC session service. It is
|
|
// idempotent.
|
|
type Closer interface{ Close() }
|
|
|
|
// BackendShutdownAfterRC forwards a backend and closes the RC
|
|
// service before the wrapped backend shuts down. The RC handlers
|
|
// reference the backend and IAM service, so the RC service must
|
|
// stop accepting and drain before either dependency is closed by
|
|
// the gateway lifecycle. The whole shutdown, including the
|
|
// delegated backend call, runs exactly once: several closers
|
|
// (the gateway lifecycle and a deferred rollback in the startup
|
|
// path) may call Shutdown on the same instance, and the wrapped
|
|
// backends are not guaranteed to be idempotent.
|
|
type BackendShutdownAfterRC struct {
|
|
backend.Backend
|
|
rc Closer
|
|
closed sync.Once
|
|
}
|
|
|
|
// Shutdown closes the RC service, then the wrapped backend, once.
|
|
func (b *BackendShutdownAfterRC) Shutdown() {
|
|
b.closed.Do(func() {
|
|
b.rc.Close()
|
|
b.Backend.Shutdown()
|
|
})
|
|
}
|
|
|
|
// ShutdownOnceBackend makes a backend Shutdown idempotent. The
|
|
// gateway startup path defers a rollback close while the gateway
|
|
// lifecycle also shuts its backend down; a bare backend has no
|
|
// idempotency guarantee of its own.
|
|
type ShutdownOnceBackend struct {
|
|
backend.Backend
|
|
once sync.Once
|
|
}
|
|
|
|
// Shutdown closes the wrapped backend at most once.
|
|
func (b *ShutdownOnceBackend) Shutdown() {
|
|
b.once.Do(func() { b.Backend.Shutdown() })
|
|
}
|
|
|
|
// WrapShutdownOnce returns be with an idempotent Shutdown.
|
|
func WrapShutdownOnce(be backend.Backend) backend.Backend {
|
|
return &ShutdownOnceBackend{Backend: be}
|
|
}
|
|
|
|
// WrapBackendShutdownAfterRC returns be with a Shutdown that
|
|
// closes rc first.
|
|
func WrapBackendShutdownAfterRC(be backend.Backend, rc Closer) backend.Backend {
|
|
return &BackendShutdownAfterRC{Backend: be, rc: rc}
|
|
}
|