Files
versitygw/rdma/rcroutes/errors.go
T
Jihyeon Gim 1596531003 rdma: keep RC route XML fidelity
Classify the platform-stub answer before the internal-error
logging decision, so the expected 501 no longer logs as an
internal 500 while debugging production servers.

Serialize the full S3 error XML body instead of the base error
alone: per-type diagnostics such as the access key and the
string-to-sign survive the route boundary. A regression test
wraps a signature failure with both diagnostic fields and
asserts the response keeps the status, the code, and both
fields.

Assert the response body identifiers equal the request-ID
headers, pinning the two views of the same response.
2026-09-04 19:44:45 +09:00

133 lines
4.5 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 rcroutes
import (
"errors"
"github.com/gofiber/fiber/v3"
"github.com/versity/versitygw/debuglogger"
"github.com/versity/versitygw/s3api/utils"
"github.com/versity/versitygw/s3err"
)
// ErrRouteNotImplemented is the platform-stub answer of the
// control routes on builds without the RC data plane.
type ErrRouteNotImplemented struct{}
func (ErrRouteNotImplemented) Error() string {
return "RDMA not supported on this platform"
}
// isRouteNotImplemented reports whether err is the platform-stub
// answer of the control routes. The marker type is shared, so
// every build classifies it the same way.
func isRouteNotImplemented(err error) bool {
var ni ErrRouteNotImplemented
return errors.As(err, &ni)
}
// WriteRouteError renders err as the terminal S3-style XML response
// of an RC control route. The production S3 error handler converts
// ordinary Fiber errors into a generic 500 response, so the route
// must send its final status and body itself. Errors that already
// carry S3 semantics (authentication, authorization, object
// backend) keep their status and code; anything else is mapped to
// a protocol error without exposing internal detail. The gateway
// auth adapter uses it for the same reason.
func WriteRouteError(ctx fiber.Ctx, err error) error {
requestID, hostID := utils.EnsureRequestIDs(ctx)
apiErr := routeError(err)
if isRouteNotImplemented(err) {
apiErr = s3err.APIError{
Code: "NotImplemented",
Description: "RDMA is not supported on this platform",
HTTPStatusCode: fiber.StatusNotImplemented,
}
}
if apiErr.HTTPStatusCode == fiber.StatusInternalServerError {
logInternalRouteError(ctx, err)
}
// A full S3 error keeps its own richer XML body - per-type
// diagnostics such as signature details would be lost if
// only the base error were serialized.
var body []byte
var s3Err s3err.S3Error
if errors.As(err, &s3Err) {
body = s3Err.XMLBody(requestID, hostID)
} else {
body = apiErr.XMLBody(requestID, hostID)
}
ctx.Response().Header.SetContentType(fiber.MIMEApplicationXML)
return ctx.Status(apiErr.HTTPStatusCode).Send(body)
}
// routeError resolves err to its S3-style response. Errors that
// already carry S3 semantics (authentication, authorization,
// object backend) keep their status and code; RC transport errors
// map to the closest protocol error.
func routeError(err error) s3err.APIError {
var s3Err s3err.S3Error
if errors.As(err, &s3Err) {
return s3Err.BaseError()
}
code, status, description := routeErrorDetails(err)
return s3err.APIError{
Code: code,
Description: description,
HTTPStatusCode: status,
}
}
// logInternalRouteError keeps a server-side trace of unexpected
// failures. The wire response stays generic; without this the
// terminal serializer would hide the production diagnostics the
// global error handler used to log.
func logInternalRouteError(ctx fiber.Ctx, err error) {
debuglogger.InternalError(err)
}
// routeErrorDetails maps a non-S3 route error to its protocol
// error code, HTTP status, and description.
func routeErrorDetails(err error) (code string, status int, description string) {
switch {
case isRouteBadRequest(err):
return "InvalidRdmaRequest", fiber.StatusBadRequest,
"Invalid RDMA control request"
case isRouteNotFound(err):
return "NoSuchRdmaSession", fiber.StatusNotFound,
"No such RDMA session"
case isRouteConflict(err):
return "RdmaSessionConflict", fiber.StatusConflict,
"RDMA session state conflict"
case isRouteLimit(err):
return "RdmaResourceLimit", fiber.StatusTooManyRequests,
"RDMA resource limit reached"
case isRouteBadGateway(err):
return "RdmaTransferFailed", fiber.StatusBadGateway,
"RDMA transfer failed"
case isRouteUnavailable(err):
return "RdmaServiceUnavailable", fiber.StatusServiceUnavailable,
"RDMA service is shutting down"
default:
return s3err.GetAPIError(s3err.ErrInternalError).Code,
fiber.StatusInternalServerError, "Internal Error"
}
}