Bump reva deps (#8412)

* bump dependencies

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* bump reva and add config options

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

---------

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
This commit is contained in:
Jörn Friedrich Dreyer
2024-02-21 10:20:36 +01:00
committed by GitHub
parent c92ebf4b46
commit 5ed57cc09a
490 changed files with 19128 additions and 11161 deletions
@@ -22,15 +22,12 @@ import (
"context"
"fmt"
"go-micro.dev/v4/util/log"
"google.golang.org/grpc"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
v1beta12 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/v2/pkg/appctx"
revactx "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/cs3org/reva/v2/pkg/events/stream"
@@ -38,6 +35,7 @@ import (
"github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/cs3org/reva/v2/pkg/utils"
"github.com/mitchellh/mapstructure"
"google.golang.org/grpc"
)
const (
@@ -190,7 +188,7 @@ func NewUnary(m map[string]interface{}) (grpc.UnaryServerInterceptor, int, error
if ev != nil {
if err := events.Publish(ctx, publisher, ev); err != nil {
log.Error(err)
appctx.GetLogger(ctx).Error().Err(err).Interface("event", ev).Msg("publishing event failed")
}
}
@@ -211,7 +209,7 @@ func NewStream() grpc.StreamServerInterceptor {
// common interface to all responses
type su interface {
GetStatus() *v1beta12.Status
GetStatus() *rpc.Status
}
func isSuccess(res su) bool {
@@ -55,6 +55,7 @@ type config struct {
Drivers map[string]map[string]interface{} `mapstructure:"drivers"`
GatewayAddr string `mapstructure:"gateway_addr"`
AllowedPathsForShares []string `mapstructure:"allowed_paths_for_shares"`
DisableResharing bool `mapstructure:"disable_resharing"`
}
func (c *config) init() {
@@ -67,6 +68,7 @@ type service struct {
sm share.Manager
gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
allowedPathsForShares []*regexp.Regexp
disableResharing bool
}
func getShareManager(c *config) (share.Manager, error) {
@@ -127,15 +129,16 @@ func NewDefault(m map[string]interface{}, ss *grpc.Server) (rgrpc.Service, error
return nil, err
}
return New(gatewaySelector, sm, allowedPathsForShares), nil
return New(gatewaySelector, sm, allowedPathsForShares, c.DisableResharing), nil
}
// New creates a new user share provider svc
func New(gatewaySelector pool.Selectable[gateway.GatewayAPIClient], sm share.Manager, allowedPathsForShares []*regexp.Regexp) rgrpc.Service {
func New(gatewaySelector pool.Selectable[gateway.GatewayAPIClient], sm share.Manager, allowedPathsForShares []*regexp.Regexp, disableResharing bool) rgrpc.Service {
service := &service{
sm: sm,
gatewaySelector: gatewaySelector,
allowedPathsForShares: allowedPathsForShares,
disableResharing: disableResharing,
}
return service
@@ -157,6 +160,13 @@ func (s *service) CreateShare(ctx context.Context, req *collaboration.CreateShar
log := appctx.GetLogger(ctx)
user := ctxpkg.ContextMustGetUser(ctx)
// when resharing is disabled grants must not allow grant permissions
if s.disableResharing && HasGrantPermissions(req.GetGrant().GetPermissions().GetPermissions()) {
return &collaboration.CreateShareResponse{
Status: status.NewInvalidArg(ctx, "resharing not supported"),
}, nil
}
gatewayClient, err := s.gatewaySelector.Next()
if err != nil {
return nil, err
@@ -235,6 +245,10 @@ func (s *service) CreateShare(ctx context.Context, req *collaboration.CreateShar
}, nil
}
func HasGrantPermissions(p *provider.ResourcePermissions) bool {
return p.GetAddGrant() || p.GetUpdateGrant() || p.GetRemoveGrant() || p.GetDenyGrant()
}
func (s *service) RemoveShare(ctx context.Context, req *collaboration.RemoveShareRequest) (*collaboration.RemoveShareResponse, error) {
log := appctx.GetLogger(ctx)
user := ctxpkg.ContextMustGetUser(ctx)
@@ -327,6 +341,14 @@ func (s *service) ListShares(ctx context.Context, req *collaboration.ListSharesR
func (s *service) UpdateShare(ctx context.Context, req *collaboration.UpdateShareRequest) (*collaboration.UpdateShareResponse, error) {
log := appctx.GetLogger(ctx)
user := ctxpkg.ContextMustGetUser(ctx)
// when resharing is disabled grants must not allow grant permissions
if s.disableResharing && HasGrantPermissions(req.GetShare().GetPermissions().GetPermissions()) {
return &collaboration.UpdateShareResponse{
Status: status.NewInvalidArg(ctx, "resharing not supported"),
}, nil
}
gatewayClient, err := s.gatewaySelector.Next()
if err != nil {
return nil, err
@@ -50,8 +50,6 @@ func init() {
const (
// TokenTransportHeader holds the header key for the reva transfer token
TokenTransportHeader = "X-Reva-Transfer"
// UploadExpiresHeader holds the timestamp for the transport token expiry, defined in https://tus.io/protocols/resumable-upload.html#expiration
UploadExpiresHeader = "Upload-Expires"
)
func init() {
@@ -133,36 +131,13 @@ func (s *svc) setHandler() {
semconv.HTTPURLKey.String(r.URL.String()),
)
r = r.WithContext(ctx)
switch r.Method {
case "HEAD":
addCorsHeader(w)
s.doHead(w, r)
return
case "GET":
s.doGet(w, r)
return
case "PUT":
s.doPut(w, r)
return
case "PATCH":
s.doPatch(w, r)
return
default:
w.WriteHeader(http.StatusNotImplemented)
return
}
s.doRequest(w, r)
})
}
func addCorsHeader(res http.ResponseWriter) {
headers := res.Header()
headers.Set("Access-Control-Allow-Origin", "*")
headers.Set("Access-Control-Allow-Headers", "Content-Type, Origin, Authorization")
headers.Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, HEAD")
}
// verify extracts the transfer token from the request
// If it is not set as header we assume that it's the last path segment instead.
func (s *svc) verify(ctx context.Context, r *http.Request) (*transferClaims, error) {
// Extract transfer token from request header. If not existing, assume that it's the last path segment instead.
token := r.Header.Get(TokenTransportHeader)
if token == "" {
token = path.Base(r.URL.Path)
@@ -185,112 +160,7 @@ func (s *svc) verify(ctx context.Context, r *http.Request) (*transferClaims, err
return nil, err
}
func (s *svc) doHead(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := appctx.GetLogger(ctx)
claims, err := s.verify(ctx, r)
if err != nil {
err = errors.Wrap(err, "datagateway: error validating transfer token")
log.Error().Err(err).Str("token", r.Header.Get(TokenTransportHeader)).Msg("invalid transfer token")
w.WriteHeader(http.StatusForbidden)
return
}
log.Debug().Str("target", claims.Target).Msg("sending request to internal data server")
httpClient := s.client
httpReq, err := rhttp.NewRequest(ctx, "HEAD", claims.Target, nil)
if err != nil {
log.Error().Err(err).Msg("wrong request")
w.WriteHeader(http.StatusInternalServerError)
return
}
httpReq.Header = r.Header
httpRes, err := httpClient.Do(httpReq)
if err != nil {
log.Error().Err(err).Msg("error doing HEAD request to data service")
w.WriteHeader(http.StatusInternalServerError)
return
}
defer httpRes.Body.Close()
copyHeader(w.Header(), httpRes.Header)
// add upload expiry / transfer token expiry header for tus https://tus.io/protocols/resumable-upload.html#expiration
w.Header().Set(UploadExpiresHeader, time.Unix(claims.ExpiresAt, 0).Format(time.RFC1123))
if httpRes.StatusCode != http.StatusOK {
// swallow the body and set content-length to 0 to prevent reverse proxies from trying to read from it
w.Header().Set("Content-Length", "0")
w.WriteHeader(httpRes.StatusCode)
return
}
w.WriteHeader(http.StatusOK)
}
func (s *svc) doGet(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := appctx.GetLogger(ctx)
claims, err := s.verify(ctx, r)
if err != nil {
err = errors.Wrap(err, "datagateway: error validating transfer token")
log.Error().Err(err).Str("token", r.Header.Get(TokenTransportHeader)).Msg("invalid transfer token")
w.WriteHeader(http.StatusForbidden)
return
}
log.Debug().Str("target", claims.Target).Msg("sending request to internal data server")
httpClient := s.client
httpReq, err := rhttp.NewRequest(ctx, "GET", claims.Target, nil)
if err != nil {
log.Error().Err(err).Msg("wrong request")
w.WriteHeader(http.StatusInternalServerError)
return
}
httpReq.Header = r.Header
httpRes, err := httpClient.Do(httpReq)
if err != nil {
log.Error().Err(err).Msg("error doing GET request to data service")
w.WriteHeader(http.StatusInternalServerError)
return
}
defer httpRes.Body.Close()
copyHeader(w.Header(), httpRes.Header)
switch httpRes.StatusCode {
case http.StatusOK:
case http.StatusPartialContent:
default:
// swallow the body and set content-length to 0 to prevent reverse proxies from trying to read from it
w.Header().Set("Content-Length", "0")
w.WriteHeader(httpRes.StatusCode)
return
}
w.WriteHeader(httpRes.StatusCode)
var c int64
c, err = io.Copy(w, httpRes.Body)
if err != nil {
log.Error().Err(err).Msg("error writing body after headers were sent")
}
if httpRes.Header.Get("Content-Length") != "" {
i, err := strconv.ParseInt(httpRes.Header.Get("Content-Length"), 10, 64)
if err != nil {
log.Error().Err(err).Str("content-length", httpRes.Header.Get("Content-Length")).Msg("invalid content length in dataprovider response")
}
if i != c {
log.Error().Int64("content-length", i).Int64("transferred-bytes", c).Msg("content length vs transferred bytes mismatch")
}
}
}
func (s *svc) doPut(w http.ResponseWriter, r *http.Request) {
func (s *svc) doRequest(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := appctx.GetLogger(ctx)
@@ -314,10 +184,9 @@ func (s *svc) doPut(w http.ResponseWriter, r *http.Request) {
targetURL.RawQuery = r.URL.RawQuery
target = targetURL.String()
log.Debug().Str("target", claims.Target).Msg("sending request to internal data server")
log.Debug().Str("target", target).Msg("sending request to internal data server")
httpClient := s.client
httpReq, err := rhttp.NewRequest(ctx, "PUT", target, r.Body)
httpReq, err := rhttp.NewRequest(ctx, r.Method, target, r.Body)
if err != nil {
log.Err(err).Msg("wrong request")
w.WriteHeader(http.StatusInternalServerError)
@@ -326,68 +195,9 @@ func (s *svc) doPut(w http.ResponseWriter, r *http.Request) {
httpReq.Header = r.Header
httpReq.ContentLength = r.ContentLength
httpRes, err := httpClient.Do(httpReq)
httpRes, err := s.client.Do(httpReq)
if err != nil {
log.Err(err).Msg("error doing PUT request to data service")
w.WriteHeader(http.StatusInternalServerError)
return
}
defer httpRes.Body.Close()
copyHeader(w.Header(), httpRes.Header)
if httpRes.StatusCode != http.StatusOK {
// swallow the body and set content-length to 0 to prevent reverse proxies from trying to read from it
w.Header().Set("Content-Length", "0")
w.WriteHeader(httpRes.StatusCode)
return
}
w.WriteHeader(http.StatusOK)
_, err = io.Copy(w, httpRes.Body)
if err != nil {
log.Err(err).Msg("error writing body after header were set")
}
}
// TODO: put and post code is pretty much the same. Should be solved in a nicer way in the long run.
func (s *svc) doPatch(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := appctx.GetLogger(ctx)
claims, err := s.verify(ctx, r)
if err != nil {
err = errors.Wrap(err, "datagateway: error validating transfer token")
log.Err(err).Str("token", r.Header.Get(TokenTransportHeader)).Msg("invalid transfer token")
w.WriteHeader(http.StatusForbidden)
return
}
target := claims.Target
// add query params to target, clients can send checksums and other information.
targetURL, err := url.Parse(target)
if err != nil {
log.Err(err).Msg("datagateway: error parsing target url")
w.WriteHeader(http.StatusInternalServerError)
return
}
targetURL.RawQuery = r.URL.RawQuery
target = targetURL.String()
log.Debug().Str("target", claims.Target).Msg("sending request to internal data server")
httpClient := s.client
httpReq, err := rhttp.NewRequest(ctx, "PATCH", target, r.Body)
if err != nil {
log.Err(err).Msg("wrong request")
w.WriteHeader(http.StatusInternalServerError)
return
}
httpReq.Header = r.Header
httpRes, err := httpClient.Do(httpReq)
if err != nil {
log.Err(err).Msg("error doing PATCH request to data service")
log.Err(err).Msg("error doing " + r.Method + " request to data service")
w.WriteHeader(http.StatusInternalServerError)
return
}
@@ -400,12 +210,22 @@ func (s *svc) doPatch(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(httpRes.StatusCode)
return
}
w.WriteHeader(httpRes.StatusCode)
_, err = io.Copy(w, httpRes.Body)
var c int64
c, err = io.Copy(w, httpRes.Body)
if err != nil {
log.Err(err).Msg("error writing body after header were set")
}
if httpRes.Header.Get("Content-Length") != "" {
i, err := strconv.ParseInt(httpRes.Header.Get("Content-Length"), 10, 64)
if err != nil {
log.Error().Err(err).Str("content-length", httpRes.Header.Get("Content-Length")).Msg("invalid content length in dataprovider response")
}
if i != c {
log.Error().Int64("content-length", i).Int64("transferred-bytes", c).Msg("content length vs transferred bytes mismatch")
}
}
}
func copyHeader(dst, src http.Header) {
@@ -693,7 +693,7 @@ func (h *Handler) GetShare(w http.ResponseWriter, r *http.Request) {
if share == nil {
sublog.Debug().Msg("no share found with this id")
response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "share not found", nil)
response.WriteOCSError(w, r, response.MetaPathNotFound.StatusCode, "share not found", nil) // MetaNotFount with code 998 would be cleaner, but this is a legacy api
return
}
+1 -1
View File
@@ -28,7 +28,7 @@ import (
"strings"
"time"
oidc "github.com/coreos/go-oidc"
oidc "github.com/coreos/go-oidc/v3/oidc"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
+27 -7
View File
@@ -30,6 +30,7 @@ import (
"github.com/rs/zerolog"
"go-micro.dev/v4/broker"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc/credentials"
)
// Option defines a single option function.
@@ -50,11 +51,10 @@ type Options struct {
FavoriteManager favorite.Manager
GatewaySelector pool.Selectable[gateway.GatewayAPIClient]
TracingEnabled bool
TracingInsecure bool
TracingExporter string
TracingCollector string
TracingEndpoint string
TracingEnabled bool
TracingInsecure bool
TracingEndpoint string
TracingTransportCredentials credentials.TransportCredentials
TraceProvider trace.TracerProvider
@@ -230,11 +230,25 @@ func LockSystem(val ocdav.LockSystem) Option {
}
// Tracing enables tracing
// Deprecated: use WithTracingEndpoint and WithTracingEnabled, Collector is unused
func Tracing(endpoint, collector string) Option {
return func(o *Options) {
o.TracingEnabled = true
o.TracingEndpoint = endpoint
o.TracingCollector = collector
}
}
// WithTracingEnabled option
func WithTracingEnabled(enabled bool) Option {
return func(o *Options) {
o.TracingEnabled = enabled
}
}
// WithTracingEndpoint option
func WithTracingEndpoint(endpoint string) Option {
return func(o *Options) {
o.TracingEndpoint = endpoint
}
}
@@ -246,9 +260,15 @@ func WithTracingInsecure() Option {
}
// WithTracingExporter option
// Deprecated: unused
func WithTracingExporter(exporter string) Option {
return func(o *Options) {}
}
// WithTracingTransportCredentials option
func WithTracingTransportCredentials(v credentials.TransportCredentials) Option {
return func(o *Options) {
o.TracingExporter = exporter
o.TracingTransportCredentials = v
}
}
+3 -2
View File
@@ -90,9 +90,7 @@ func Service(opts ...Option) (micro.Service, error) {
if tp == nil {
topts := []rtrace.Option{
rtrace.WithExporter(sopts.TracingExporter),
rtrace.WithEndpoint(sopts.TracingEndpoint),
rtrace.WithCollector(sopts.TracingCollector),
rtrace.WithServiceName(sopts.Name),
}
if sopts.TracingEnabled {
@@ -101,6 +99,9 @@ func Service(opts ...Option) (micro.Service, error) {
if sopts.TracingInsecure {
topts = append(topts, rtrace.WithInsecure())
}
if sopts.TracingTransportCredentials != nil {
topts = append(topts, rtrace.WithTransportCredentials(sopts.TracingTransportCredentials))
}
tp = rtrace.NewTracerProvider(topts...)
}
if err := useMiddlewares(r, &sopts, revaService, tp); err != nil {
+3 -8
View File
@@ -326,10 +326,6 @@ func (s *Server) getInterceptors(unprotected []string) ([]grpc.ServerOption, err
}
unaryInterceptors := []grpc.UnaryServerInterceptor{
otelgrpc.UnaryServerInterceptor(
otelgrpc.WithTracerProvider(s.tracerProvider),
otelgrpc.WithPropagators(rtrace.Propagator),
),
appctx.NewUnary(s.log, s.tracerProvider),
token.NewUnary(),
useragent.NewUnary(),
@@ -372,10 +368,6 @@ func (s *Server) getInterceptors(unprotected []string) ([]grpc.ServerOption, err
}
streamInterceptors := []grpc.StreamServerInterceptor{
otelgrpc.StreamServerInterceptor(
otelgrpc.WithTracerProvider(s.tracerProvider),
otelgrpc.WithPropagators(rtrace.Propagator),
),
appctx.NewStream(s.log, s.tracerProvider),
token.NewStream(),
useragent.NewStream(),
@@ -391,6 +383,9 @@ func (s *Server) getInterceptors(unprotected []string) ([]grpc.ServerOption, err
streamChain := grpc_middleware.ChainStreamServer(streamInterceptors...)
opts := []grpc.ServerOption{
grpc.StatsHandler(otelgrpc.NewServerHandler(
otelgrpc.WithTracerProvider(s.tracerProvider),
otelgrpc.WithPropagators(rtrace.Propagator))),
grpc.UnaryInterceptor(unaryChain),
grpc.StreamInterceptor(streamChain),
}
+1 -11
View File
@@ -90,7 +90,7 @@ func NewConn(address string, opts ...Option) (*grpc.ClientConn, error) {
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(maxRcvMsgSize),
),
grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor(
grpc.WithStatsHandler(otelgrpc.NewClientHandler(
otelgrpc.WithTracerProvider(
options.tracerProvider,
),
@@ -98,16 +98,6 @@ func NewConn(address string, opts ...Option) (*grpc.ClientConn, error) {
rtrace.Propagator,
),
)),
grpc.WithUnaryInterceptor(
otelgrpc.UnaryClientInterceptor(
otelgrpc.WithTracerProvider(
options.tracerProvider,
),
otelgrpc.WithPropagators(
rtrace.Propagator,
),
),
),
)
if err != nil {
return nil, err
+33 -4
View File
@@ -23,6 +23,7 @@ import (
"log"
"net/http"
"path"
"regexp"
"github.com/pkg/errors"
tusd "github.com/tus/tusd/pkg/handler"
@@ -45,14 +46,25 @@ func init() {
registry.Register("tus", New)
}
type TusConfig struct {
cache.Config
CorsEnabled bool `mapstructure:"cors_enabled"`
CorsAllowOrigin string `mapstructure:"cors_allow_origin"`
CorsAllowCredentials bool `mapstructure:"cors_allow_credentials"`
CorsAllowMethods string `mapstructure:"cors_allow_methods"`
CorsAllowHeaders string `mapstructure:"cors_allow_headers"`
CorsMaxAge string `mapstructure:"cors_max_age"`
CorsExposeHeaders string `mapstructure:"cors_expose_headers"`
}
type manager struct {
conf *cache.Config
conf *TusConfig
publisher events.Publisher
statCache cache.StatCache
}
func parseConfig(m map[string]interface{}) (*cache.Config, error) {
c := &cache.Config{}
func parseConfig(m map[string]interface{}) (*TusConfig, error) {
c := &TusConfig{}
if err := mapstructure.Decode(m, c); err != nil {
err = errors.Wrap(err, "error decoding conf")
return nil, err
@@ -69,7 +81,7 @@ func New(m map[string]interface{}, publisher events.Publisher) (datatx.DataTX, e
return &manager{
conf: c,
publisher: publisher,
statCache: cache.GetStatCache(*c),
statCache: cache.GetStatCache(c.Config),
}, nil
}
@@ -94,6 +106,23 @@ func (m *manager) Handler(fs storage.FS) (http.Handler, error) {
Logger: log.New(appctx.GetLogger(context.Background()), "", 0),
}
if m.conf.CorsEnabled {
allowOrigin, err := regexp.Compile(m.conf.CorsAllowOrigin)
if m.conf.CorsAllowOrigin != "" && err != nil {
return nil, err
}
config.Cors = &tusd.CorsConfig{
Disable: false,
AllowOrigin: allowOrigin,
AllowCredentials: m.conf.CorsAllowCredentials,
AllowMethods: m.conf.CorsAllowMethods,
AllowHeaders: m.conf.CorsAllowHeaders,
MaxAge: m.conf.CorsMaxAge,
ExposeHeaders: m.conf.CorsExposeHeaders,
}
}
handler, err := tusd.NewUnroutedHandler(config)
if err != nil {
return nil, err
@@ -202,6 +202,11 @@ func (c *Cache) Add(ctx context.Context, storageID, spaceID, shareID string, sha
log.Debug().Msg("precondition failed when persisting added provider share: etag changed. retrying...")
// actually, this is the wrong status code and we treat it like errtypes.Aborted because of inconsistencies on the server side
// continue with sync below
case errtypes.AlreadyExists:
log.Debug().Msg("already exists when persisting added provider share. retrying...")
// CS3 uses an already exists error instead of precondition failed when using an If-None-Match=* header / IfExists flag in the InitiateFileUpload call.
// Thas happens when the cache thinks there is no file.
// continue with sync below
default:
span.SetStatus(codes.Error, fmt.Sprintf("persisting added provider share failed. giving up: %s", err.Error()))
log.Error().Err(err).Msg("persisting added provider share failed")
@@ -393,10 +398,8 @@ func (c *Cache) Persist(ctx context.Context, storageID, spaceID string) error {
// > If the field value is "*", the condition is false if the origin server has a current representation for the target resource.
if space.Etag == "" {
ur.IfNoneMatch = []string{"*"}
log.Debug().Msg("setting IfNoneMatch to *")
} else {
log.Debug().Msg("setting IfMatchEtag")
}
res, err := c.storage.Upload(ctx, ur)
if err != nil {
span.RecordError(err)
@@ -405,6 +408,7 @@ func (c *Cache) Persist(ctx context.Context, storageID, spaceID string) error {
return err
}
space.Etag = res.Etag
span.SetStatus(codes.Ok, "")
shares := []string{}
for _, s := range space.Shares {
@@ -146,6 +146,11 @@ func (c *Cache) Add(ctx context.Context, userID, spaceID string, rs *collaborati
log.Debug().Msg("precondition failed when persisting added received share: etag changed. retrying...")
// actually, this is the wrong status code and we treat it like errtypes.Aborted because of inconsistencies on the server side
// continue with sync below
case errtypes.AlreadyExists:
log.Debug().Msg("already exists when persisting added received share. retrying...")
// CS3 uses an already exists error instead of precondition failed when using an If-None-Match=* header / IfExists flag in the InitiateFileUpload call.
// Thas happens when the cache thinks there is no file.
// continue with sync below
default:
span.SetStatus(codes.Error, fmt.Sprintf("persisting added received share failed. giving up: %s", err.Error()))
log.Error().Err(err).Msg("persisting added received share failed")
@@ -294,12 +299,14 @@ func (c *Cache) persist(ctx context.Context, userID string) error {
ur.IfNoneMatch = []string{"*"}
}
_, err = c.storage.Upload(ctx, ur)
res, err := c.storage.Upload(ctx, ur)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return err
}
rss.etag = res.Etag
span.SetStatus(codes.Ok, "")
return nil
}
@@ -137,6 +137,11 @@ func (c *Cache) Add(ctx context.Context, userid, shareID string) error {
log.Debug().Msg("precondition failed when persisting added share: etag changed. retrying...")
// actually, this is the wrong status code and we treat it like errtypes.Aborted because of inconsistencies on the server side
// continue with sync below
case errtypes.AlreadyExists:
log.Debug().Msg("already exists when persisting added share. retrying...")
// CS3 uses an already exists error instead of precondition failed when using an If-None-Match=* header / IfExists flag in the InitiateFileUpload call.
// Thas happens when the cache thinks there is no file.
// continue with sync below
default:
span.SetStatus(codes.Error, fmt.Sprintf("persisting added share failed. giving up: %s", err.Error()))
log.Error().Err(err).Msg("persisting added share failed")
@@ -330,6 +335,7 @@ func (c *Cache) Persist(ctx context.Context, userid string) error {
if us.Etag == "" {
ur.IfNoneMatch = []string{"*"}
}
res, err := c.storage.Upload(ctx, ur)
if err != nil {
span.RecordError(err)
@@ -337,6 +343,7 @@ func (c *Cache) Persist(ctx context.Context, userid string) error {
return err
}
us.Etag = res.Etag
span.SetStatus(codes.Ok, "")
return nil
}
@@ -37,11 +37,22 @@ import (
type Blobstore struct {
client *minio.Client
defaultPutOptions Options
bucket string
}
type Options struct {
DisableContentSha256 bool
DisableMultipart bool
SendContentMd5 bool
ConcurrentStreamParts bool
NumThreads uint
PartSize uint64
}
// New returns a new Blobstore
func New(endpoint, region, bucket, accessKey, secretKey string) (*Blobstore, error) {
func New(endpoint, region, bucket, accessKey, secretKey string, defaultPutOptions Options) (*Blobstore, error) {
u, err := url.Parse(endpoint)
if err != nil {
return nil, errors.Wrap(err, "failed to parse s3 endpoint")
@@ -58,8 +69,9 @@ func New(endpoint, region, bucket, accessKey, secretKey string) (*Blobstore, err
}
return &Blobstore{
client: client,
bucket: bucket,
client: client,
bucket: bucket,
defaultPutOptions: defaultPutOptions,
}, nil
}
@@ -71,7 +83,15 @@ func (bs *Blobstore) Upload(node *node.Node, source string) error {
}
defer reader.Close()
_, err = bs.client.PutObject(context.Background(), bs.bucket, bs.path(node), reader, node.Blobsize, minio.PutObjectOptions{ContentType: "application/octet-stream", SendContentMd5: true})
_, err = bs.client.PutObject(context.Background(), bs.bucket, bs.path(node), reader, node.Blobsize, minio.PutObjectOptions{
ContentType: "application/octet-stream",
SendContentMd5: bs.defaultPutOptions.SendContentMd5,
ConcurrentStreamParts: bs.defaultPutOptions.ConcurrentStreamParts,
NumThreads: bs.defaultPutOptions.NumThreads,
PartSize: bs.defaultPutOptions.PartSize,
DisableMultipart: bs.defaultPutOptions.DisableMultipart,
DisableContentSha256: bs.defaultPutOptions.DisableContentSha256,
})
if err != nil {
return errors.Wrapf(err, "could not store object '%s' into bucket '%s'", bs.path(node), bs.bucket)
+29
View File
@@ -43,6 +43,24 @@ type Options struct {
// Secret key for the s3 blobstore
S3SecretKey string `mapstructure:"s3.secret_key"`
// disable sending content sha256
DisableContentSha256 bool `mapstructure:"s3.disable_content_sha254"`
// disable multipart uploads
DisableMultipart bool `mapstructure:"s3.disable_multipart"`
// enable sending content md5, defaults to true if unset
SendContentMd5 bool `mapstructure:"s3.send_content_md5"`
// use concurrent stream parts
ConcurrentStreamParts bool `mapstructure:"s3.concurrent_stream_parts"`
// number of concurrent uploads
NumThreads uint `mapstructure:"s3.num_threads"`
// part size for concurrent uploads
PartSize uint64 `mapstructure:"s3.part_size"`
}
// S3ConfigComplete return true if all required s3 fields are set
@@ -60,5 +78,16 @@ func parseConfig(m map[string]interface{}) (*Options, error) {
err = errors.Wrap(err, "error decoding conf")
return nil, err
}
// if unset we set these defaults
if m["s3.send_content_md5"] == nil {
o.SendContentMd5 = true
}
if m["s3.concurrent_stream_parts"] == nil {
o.ConcurrentStreamParts = true
}
if m["s3.num_threads"] == nil {
o.NumThreads = 4
}
return o, nil
}
+10 -1
View File
@@ -44,7 +44,16 @@ func New(m map[string]interface{}, stream events.Stream) (storage.FS, error) {
return nil, fmt.Errorf("S3 configuration incomplete")
}
bs, err := blobstore.New(o.S3Endpoint, o.S3Region, o.S3Bucket, o.S3AccessKey, o.S3SecretKey)
defaultPutOptions := blobstore.Options{
DisableContentSha256: o.DisableContentSha256,
DisableMultipart: o.DisableMultipart,
SendContentMd5: o.SendContentMd5,
ConcurrentStreamParts: o.ConcurrentStreamParts,
NumThreads: o.NumThreads,
PartSize: o.PartSize,
}
bs, err := blobstore.New(o.S3Endpoint, o.S3Region, o.S3Bucket, o.S3AccessKey, o.S3SecretKey, defaultPutOptions)
if err != nil {
return nil, err
}
+3 -1
View File
@@ -165,7 +165,9 @@ func (cs3 *CS3) Upload(ctx context.Context, req UploadRequest) (*UploadResponse,
}
if len(req.IfNoneMatch) > 0 {
if req.IfNoneMatch[0] == "*" {
ifuReq.Options = &provider.InitiateFileUploadRequest_IfNotExist{}
ifuReq.Options = &provider.InitiateFileUploadRequest_IfNotExist{
IfNotExist: true,
}
}
// else {
// the http upload will carry all if-not-match etags
+2 -1
View File
@@ -22,6 +22,7 @@ import (
"context"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
)
type ctxKey struct{}
@@ -43,5 +44,5 @@ func ContextGetTracerProvider(ctx context.Context) trace.TracerProvider {
if p, ok := ctx.Value(ctxKey{}).(trace.TracerProvider); ok {
return p
}
return trace.NewNoopTracerProvider()
return noop.NewTracerProvider()
}
+9
View File
@@ -24,6 +24,7 @@ func WithEnabled() Option {
}
// WithExporter option
// Deprecated: unused
func WithExporter(v string) Option {
return func(o *Options) {
o.Exporter = v
@@ -38,6 +39,7 @@ func WithInsecure() Option {
}
// WithCollector option
// Deprecated: unused
func WithCollector(v string) Option {
return func(o *Options) {
o.Collector = v
@@ -57,3 +59,10 @@ func WithServiceName(v string) Option {
o.ServiceName = v
}
}
// WithTransportCredentials option
func WithTransportCredentials(v credentials.TransportCredentials) Option {
return func(o *Options) {
o.TransportCredentials = v
}
}
+12 -92
View File
@@ -21,25 +21,21 @@ package trace
import (
"context"
"fmt"
"net/url"
"os"
"strings"
"sync"
"time"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/jaeger"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
)
var (
@@ -66,7 +62,7 @@ func NewTracerProvider(opts ...Option) trace.TracerProvider {
}
if !options.Enabled {
return trace.NewNoopTracerProvider()
return noop.NewTracerProvider()
}
// default to 'reva' as service name if not set
@@ -74,12 +70,7 @@ func NewTracerProvider(opts ...Option) trace.TracerProvider {
options.ServiceName = "reva"
}
switch options.Exporter {
case "otlp":
return getOtlpTracerProvider(options)
default:
return getJaegerTracerProvider(options)
}
return getOtlpTracerProvider(options)
}
// SetDefaultTracerProvider sets the default trace provider
@@ -97,11 +88,10 @@ func InitDefaultTracerProvider(collector, endpoint string) {
defaultProvider.mutex.Lock()
defer defaultProvider.mutex.Unlock()
if !defaultProvider.initialized {
SetDefaultTracerProvider(getJaegerTracerProvider(Options{
Enabled: true,
Collector: collector,
SetDefaultTracerProvider(getOtlpTracerProvider(Options{
Endpoint: endpoint,
ServiceName: "reva default jaeger provider",
ServiceName: "reva default otlp provider",
Insecure: true,
}))
}
}
@@ -114,89 +104,19 @@ func DefaultProvider() trace.TracerProvider {
return otel.GetTracerProvider()
}
// getJaegerTracerProvider returns a new TracerProvider, configure for the specified service
func getJaegerTracerProvider(options Options) trace.TracerProvider {
var exp *jaeger.Exporter
var err error
if options.Endpoint != "" {
var agentHost string
var agentPort string
agentHost, agentPort, err = parseAgentConfig(options.Endpoint)
if err != nil {
panic(err)
}
exp, err = jaeger.New(
jaeger.WithAgentEndpoint(
jaeger.WithAgentHost(agentHost),
jaeger.WithAgentPort(agentPort),
),
)
if err != nil {
panic(err)
}
}
if options.Collector != "" {
exp, err = jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(options.Collector)))
if err != nil {
panic(err)
}
}
hostname, err := os.Hostname()
if err != nil {
panic(err)
}
return sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exp),
sdktrace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String(options.ServiceName),
semconv.HostNameKey.String(hostname),
)),
)
}
func parseAgentConfig(ae string) (string, string, error) {
u, err := url.Parse(ae)
// as per url.go:
// [...] Trying to parse a hostname and path
// without a scheme is invalid but may not necessarily return an
// error, due to parsing ambiguities.
if err == nil && u.Hostname() != "" && u.Port() != "" {
return u.Hostname(), u.Port(), nil
}
p := strings.Split(ae, ":")
if len(p) != 2 {
return "", "", fmt.Errorf(fmt.Sprintf("invalid agent endpoint `%s`. expected format: `hostname:port`", ae))
}
switch {
case p[0] == "" && p[1] == "": // case ae = ":"
return "", "", fmt.Errorf(fmt.Sprintf("invalid agent endpoint `%s`. expected format: `hostname:port`", ae))
case p[0] == "":
return "", "", fmt.Errorf(fmt.Sprintf("invalid agent endpoint `%s`. expected format: `hostname:port`", ae))
}
return p[0], p[1], nil
}
// getOtelTracerProvider returns a new TracerProvider, configure for the specified service
func getOtlpTracerProvider(options Options) trace.TracerProvider {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
transportCredentials := options.TransportCredentials
if options.Insecure {
transportCredentials = insecure.NewCredentials()
}
conn, err := grpc.DialContext(ctx, options.Endpoint,
// Note the use of insecure transport here. TLS is recommended in production.
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithBlock(),
grpc.WithTransportCredentials(transportCredentials),
)
if err != nil {
panic(fmt.Errorf("failed to create gRPC connection to collector: %w", err))
panic(fmt.Errorf("failed to create gRPC connection to endpoint: %w", err))
}
exporter, err := otlptracegrpc.New(
context.Background(),