github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/cmd/server/server.go (about) 1 package server 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "net" 8 "net/http" 9 "strconv" 10 "time" 11 12 "github.com/authzed/consistent" 13 "github.com/authzed/grpcutil" 14 "github.com/cespare/xxhash/v2" 15 "github.com/ecordell/optgen/helpers" 16 grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" 17 "github.com/hashicorp/go-multierror" 18 "github.com/prometheus/client_golang/prometheus" 19 "github.com/rs/cors" 20 "github.com/rs/zerolog" 21 "github.com/sean-/sysexits" 22 "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" 23 "golang.org/x/sync/errgroup" 24 "google.golang.org/grpc" 25 _ "google.golang.org/grpc/encoding/gzip" // enable gzip compression on all derivative servers 26 27 "github.com/authzed/spicedb/internal/auth" 28 "github.com/authzed/spicedb/internal/datastore/proxy" 29 "github.com/authzed/spicedb/internal/datastore/proxy/schemacaching" 30 "github.com/authzed/spicedb/internal/dispatch" 31 clusterdispatch "github.com/authzed/spicedb/internal/dispatch/cluster" 32 combineddispatch "github.com/authzed/spicedb/internal/dispatch/combined" 33 "github.com/authzed/spicedb/internal/dispatch/graph" 34 "github.com/authzed/spicedb/internal/gateway" 35 log "github.com/authzed/spicedb/internal/logging" 36 "github.com/authzed/spicedb/internal/services" 37 dispatchSvc "github.com/authzed/spicedb/internal/services/dispatch" 38 "github.com/authzed/spicedb/internal/services/health" 39 v1svc "github.com/authzed/spicedb/internal/services/v1" 40 "github.com/authzed/spicedb/internal/telemetry" 41 datastorecfg "github.com/authzed/spicedb/pkg/cmd/datastore" 42 "github.com/authzed/spicedb/pkg/cmd/util" 43 "github.com/authzed/spicedb/pkg/datastore" 44 "github.com/authzed/spicedb/pkg/middleware/requestid" 45 "github.com/authzed/spicedb/pkg/spiceerrors" 46 ) 47 48 // ConsistentHashringBuilder is a balancer Builder that uses xxhash as the 49 // underlying hash for the ConsistentHashringBalancers it creates. 50 var ConsistentHashringBuilder = consistent.NewBuilder(xxhash.Sum64) 51 52 //go:generate go run github.com/ecordell/optgen -output zz_generated.options.go . Config 53 type Config struct { 54 // API config 55 GRPCServer util.GRPCServerConfig `debugmap:"visible"` 56 GRPCAuthFunc grpc_auth.AuthFunc `debugmap:"visible"` 57 PresharedSecureKey []string `debugmap:"sensitive"` 58 ShutdownGracePeriod time.Duration `debugmap:"visible"` 59 DisableVersionResponse bool `debugmap:"visible"` 60 61 // GRPC Gateway config 62 HTTPGateway util.HTTPServerConfig `debugmap:"visible"` 63 HTTPGatewayUpstreamAddr string `debugmap:"visible"` 64 HTTPGatewayUpstreamTLSCertPath string `debugmap:"visible"` 65 HTTPGatewayCorsEnabled bool `debugmap:"visible"` 66 HTTPGatewayCorsAllowedOrigins []string `debugmap:"visible-format"` 67 68 // Datastore 69 DatastoreConfig datastorecfg.Config `debugmap:"visible"` 70 Datastore datastore.Datastore `debugmap:"visible"` 71 72 // Datastore usage 73 MaxCaveatContextSize int `debugmap:"visible" default:"4096"` 74 MaxRelationshipContextSize int `debugmap:"visible" default:"25_000"` 75 76 // Namespace cache 77 EnableExperimentalWatchableSchemaCache bool `debugmap:"visible"` 78 SchemaWatchHeartbeat time.Duration `debugmap:"visible"` 79 NamespaceCacheConfig CacheConfig `debugmap:"visible"` 80 81 // Schema options 82 SchemaPrefixesRequired bool `debugmap:"visible"` 83 84 // Dispatch options 85 DispatchServer util.GRPCServerConfig `debugmap:"visible"` 86 DispatchMaxDepth uint32 `debugmap:"visible"` 87 GlobalDispatchConcurrencyLimit uint16 `debugmap:"visible"` 88 DispatchConcurrencyLimits graph.ConcurrencyLimits `debugmap:"visible"` 89 DispatchUpstreamAddr string `debugmap:"visible"` 90 DispatchUpstreamCAPath string `debugmap:"visible"` 91 DispatchUpstreamTimeout time.Duration `debugmap:"visible"` 92 DispatchClientMetricsEnabled bool `debugmap:"visible"` 93 DispatchClientMetricsPrefix string `debugmap:"visible"` 94 DispatchClusterMetricsEnabled bool `debugmap:"visible"` 95 DispatchClusterMetricsPrefix string `debugmap:"visible"` 96 Dispatcher dispatch.Dispatcher `debugmap:"visible"` 97 DispatchHashringReplicationFactor uint16 `debugmap:"visible"` 98 DispatchHashringSpread uint8 `debugmap:"visible"` 99 100 DispatchSecondaryUpstreamAddrs map[string]string `debugmap:"visible"` 101 DispatchSecondaryUpstreamExprs map[string]string `debugmap:"visible"` 102 103 DispatchCacheConfig CacheConfig `debugmap:"visible"` 104 ClusterDispatchCacheConfig CacheConfig `debugmap:"visible"` 105 106 // API Behavior 107 DisableV1SchemaAPI bool `debugmap:"visible"` 108 V1SchemaAdditiveOnly bool `debugmap:"visible"` 109 MaximumUpdatesPerWrite uint16 `debugmap:"visible"` 110 MaximumPreconditionCount uint16 `debugmap:"visible"` 111 MaxDatastoreReadPageSize uint64 `debugmap:"visible"` 112 StreamingAPITimeout time.Duration `debugmap:"visible"` 113 WatchHeartbeat time.Duration `debugmap:"visible"` 114 MaxReadRelationshipsLimit uint32 `debugmap:"visible"` 115 MaxDeleteRelationshipsLimit uint32 `debugmap:"visible"` 116 MaxLookupResourcesLimit uint32 `debugmap:"visible"` 117 MaxBulkExportRelationshipsLimit uint32 `debugmap:"visible"` 118 119 // Additional Services 120 MetricsAPI util.HTTPServerConfig `debugmap:"visible"` 121 122 // Middleware for grpc API 123 UnaryMiddlewareModification []MiddlewareModification[grpc.UnaryServerInterceptor] `debugmap:"hidden"` 124 StreamingMiddlewareModification []MiddlewareModification[grpc.StreamServerInterceptor] `debugmap:"hidden"` 125 126 // Middleware for internal dispatch API 127 DispatchUnaryMiddleware []grpc.UnaryServerInterceptor `debugmap:"hidden"` 128 DispatchStreamingMiddleware []grpc.StreamServerInterceptor `debugmap:"hidden"` 129 130 // Telemetry 131 SilentlyDisableTelemetry bool `debugmap:"visible"` 132 TelemetryCAOverridePath string `debugmap:"visible"` 133 TelemetryEndpoint string `debugmap:"visible"` 134 TelemetryInterval time.Duration `debugmap:"visible"` 135 136 // Logs 137 EnableRequestLogs bool `debugmap:"visible"` 138 EnableResponseLogs bool `debugmap:"visible"` 139 140 // Metrics 141 DisableGRPCLatencyHistogram bool `debugmap:"visible"` 142 } 143 144 type closeableStack struct { 145 closers []func() error 146 } 147 148 func (c *closeableStack) AddWithError(closer func() error) { 149 c.closers = append(c.closers, closer) 150 } 151 152 func (c *closeableStack) AddCloser(closer io.Closer) { 153 if closer != nil { 154 c.closers = append(c.closers, closer.Close) 155 } 156 } 157 158 func (c *closeableStack) AddWithoutError(closer func()) { 159 c.closers = append(c.closers, func() error { 160 closer() 161 return nil 162 }) 163 } 164 165 func (c *closeableStack) Close() error { 166 var err error 167 // closer in reverse order how it's expected in deferred funcs 168 for i := len(c.closers) - 1; i >= 0; i-- { 169 if closerErr := c.closers[i](); closerErr != nil { 170 err = multierror.Append(err, closerErr) 171 } 172 } 173 return err 174 } 175 176 func (c *closeableStack) CloseIfError(err error) error { 177 if err != nil { 178 return c.Close() 179 } 180 return nil 181 } 182 183 // Complete validates the config and fills out defaults. 184 // if there is no error, a completedServerConfig (with limited options for 185 // mutation) is returned. 186 func (c *Config) Complete(ctx context.Context) (RunnableServer, error) { 187 log.Ctx(ctx).Info().Fields(helpers.Flatten(c.DebugMap())).Msg("configuration") 188 189 closeables := closeableStack{} 190 var err error 191 defer func() { 192 // if an error happens during the execution of Complete, all resources are cleaned up 193 if closeableErr := closeables.CloseIfError(err); closeableErr != nil { 194 log.Ctx(ctx).Err(closeableErr).Msg("failed to clean up resources on Config.Complete") 195 } 196 }() 197 198 if len(c.PresharedSecureKey) < 1 && c.GRPCAuthFunc == nil { 199 return nil, fmt.Errorf("a preshared key must be provided to authenticate API requests") 200 } 201 202 if c.GRPCAuthFunc == nil { 203 log.Ctx(ctx).Trace().Int("preshared-keys-count", len(c.PresharedSecureKey)).Msg("using gRPC auth with preshared key(s)") 204 for index, presharedKey := range c.PresharedSecureKey { 205 if len(presharedKey) == 0 { 206 return nil, fmt.Errorf("preshared key #%d is empty", index+1) 207 } 208 209 log.Ctx(ctx).Trace().Int("preshared-key-"+strconv.Itoa(index+1)+"-length", len(presharedKey)).Msg("preshared key configured") 210 } 211 212 c.GRPCAuthFunc = auth.MustRequirePresharedKey(c.PresharedSecureKey) 213 } else { 214 log.Ctx(ctx).Trace().Msg("using preconfigured auth function") 215 } 216 217 ds := c.Datastore 218 if ds == nil { 219 var err error 220 ds, err = datastorecfg.NewDatastore(context.Background(), c.DatastoreConfig.ToOption()) 221 if err != nil { 222 return nil, spiceerrors.NewTerminationErrorBuilder(fmt.Errorf("failed to create datastore: %w", err)). 223 Component("datastore"). 224 ExitCode(sysexits.Config). 225 Error() 226 } 227 } 228 closeables.AddWithError(ds.Close) 229 230 nscc, err := c.NamespaceCacheConfig.Complete() 231 if err != nil { 232 return nil, fmt.Errorf("failed to create namespace cache: %w", err) 233 } 234 log.Ctx(ctx).Info().EmbedObject(nscc).Msg("configured namespace cache") 235 236 cachingMode := schemacaching.JustInTimeCaching 237 if c.EnableExperimentalWatchableSchemaCache { 238 cachingMode = schemacaching.WatchIfSupported 239 } 240 241 ds = proxy.NewObservableDatastoreProxy(ds) 242 ds = proxy.NewSingleflightDatastoreProxy(ds) 243 ds = schemacaching.NewCachingDatastoreProxy(ds, nscc, c.DatastoreConfig.GCWindow, cachingMode, c.SchemaWatchHeartbeat) 244 closeables.AddWithError(ds.Close) 245 246 specificConcurrencyLimits := c.DispatchConcurrencyLimits 247 concurrencyLimits := specificConcurrencyLimits.WithOverallDefaultLimit(c.GlobalDispatchConcurrencyLimit) 248 249 dispatcher := c.Dispatcher 250 if dispatcher == nil { 251 cc, err := c.DispatchCacheConfig.WithRevisionParameters( 252 c.DatastoreConfig.RevisionQuantization, 253 c.DatastoreConfig.FollowerReadDelay, 254 c.DatastoreConfig.MaxRevisionStalenessPercent, 255 ).Complete() 256 if err != nil { 257 return nil, fmt.Errorf("failed to create dispatcher: %w", err) 258 } 259 closeables.AddWithoutError(cc.Close) 260 log.Ctx(ctx).Info().EmbedObject(cc).Msg("configured dispatch cache") 261 262 dispatchPresharedKey := "" 263 if len(c.PresharedSecureKey) > 0 { 264 dispatchPresharedKey = c.PresharedSecureKey[0] 265 } 266 267 hashringConfigJSON, err := (&consistent.BalancerConfig{ 268 ReplicationFactor: c.DispatchHashringReplicationFactor, 269 Spread: c.DispatchHashringSpread, 270 }).ServiceConfigJSON() 271 if err != nil { 272 return nil, fmt.Errorf("failed to create gRPC hashring balancer config: %w", err) 273 } 274 275 dispatcher, err = combineddispatch.NewDispatcher( 276 combineddispatch.UpstreamAddr(c.DispatchUpstreamAddr), 277 combineddispatch.UpstreamCAPath(c.DispatchUpstreamCAPath), 278 combineddispatch.SecondaryUpstreamAddrs(c.DispatchSecondaryUpstreamAddrs), 279 combineddispatch.SecondaryUpstreamExprs(c.DispatchSecondaryUpstreamExprs), 280 combineddispatch.GrpcPresharedKey(dispatchPresharedKey), 281 combineddispatch.GrpcDialOpts( 282 grpc.WithStatsHandler(otelgrpc.NewClientHandler()), 283 grpc.WithDefaultServiceConfig(hashringConfigJSON), 284 grpc.WithChainUnaryInterceptor( 285 requestid.UnaryClientInterceptor(), 286 ), 287 grpc.WithChainStreamInterceptor( 288 requestid.StreamClientInterceptor(), 289 ), 290 ), 291 combineddispatch.MetricsEnabled(c.DispatchClientMetricsEnabled), 292 combineddispatch.PrometheusSubsystem(c.DispatchClientMetricsPrefix), 293 combineddispatch.Cache(cc), 294 combineddispatch.ConcurrencyLimits(concurrencyLimits), 295 ) 296 if err != nil { 297 return nil, fmt.Errorf("failed to create dispatcher: %w", err) 298 } 299 300 log.Ctx(ctx).Info().EmbedObject(concurrencyLimits).RawJSON("balancerconfig", []byte(hashringConfigJSON)).Msg("configured dispatcher") 301 } 302 closeables.AddWithError(dispatcher.Close) 303 304 if len(c.DispatchUnaryMiddleware) == 0 && len(c.DispatchStreamingMiddleware) == 0 { 305 if c.GRPCAuthFunc == nil { 306 c.DispatchUnaryMiddleware, c.DispatchStreamingMiddleware = DefaultDispatchMiddleware(log.Logger, auth.MustRequirePresharedKey(c.PresharedSecureKey), ds, c.DisableGRPCLatencyHistogram) 307 } else { 308 c.DispatchUnaryMiddleware, c.DispatchStreamingMiddleware = DefaultDispatchMiddleware(log.Logger, c.GRPCAuthFunc, ds, c.DisableGRPCLatencyHistogram) 309 } 310 } 311 312 var cachingClusterDispatch dispatch.Dispatcher 313 if c.DispatchServer.Enabled { 314 cdcc, err := c.ClusterDispatchCacheConfig.WithRevisionParameters( 315 c.DatastoreConfig.RevisionQuantization, 316 c.DatastoreConfig.FollowerReadDelay, 317 c.DatastoreConfig.MaxRevisionStalenessPercent, 318 ).Complete() 319 if err != nil { 320 return nil, fmt.Errorf("failed to configure cluster dispatch: %w", err) 321 } 322 log.Ctx(ctx).Info().EmbedObject(cdcc).Msg("configured cluster dispatch cache") 323 closeables.AddWithoutError(cdcc.Close) 324 325 cachingClusterDispatch, err = clusterdispatch.NewClusterDispatcher( 326 dispatcher, 327 clusterdispatch.MetricsEnabled(c.DispatchClusterMetricsEnabled), 328 clusterdispatch.PrometheusSubsystem(c.DispatchClusterMetricsPrefix), 329 clusterdispatch.Cache(cdcc), 330 clusterdispatch.RemoteDispatchTimeout(c.DispatchUpstreamTimeout), 331 clusterdispatch.ConcurrencyLimits(concurrencyLimits), 332 ) 333 if err != nil { 334 return nil, fmt.Errorf("failed to configure cluster dispatch: %w", err) 335 } 336 closeables.AddWithError(cachingClusterDispatch.Close) 337 } 338 339 dispatchGrpcServer, err := c.DispatchServer.Complete(zerolog.InfoLevel, 340 func(server *grpc.Server) { 341 dispatchSvc.RegisterGrpcServices(server, cachingClusterDispatch) 342 }, 343 grpc.ChainUnaryInterceptor(c.DispatchUnaryMiddleware...), 344 grpc.ChainStreamInterceptor(c.DispatchStreamingMiddleware...), 345 grpc.StatsHandler(otelgrpc.NewServerHandler()), 346 ) 347 if err != nil { 348 return nil, fmt.Errorf("failed to create dispatch gRPC server: %w", err) 349 } 350 closeables.AddWithoutError(dispatchGrpcServer.GracefulStop) 351 352 datastoreFeatures, err := ds.Features(ctx) 353 if err != nil { 354 return nil, fmt.Errorf("error determining datastore features: %w", err) 355 } 356 357 v1SchemaServiceOption := services.V1SchemaServiceEnabled 358 if c.DisableV1SchemaAPI { 359 v1SchemaServiceOption = services.V1SchemaServiceDisabled 360 } else if c.V1SchemaAdditiveOnly { 361 v1SchemaServiceOption = services.V1SchemaServiceAdditiveOnly 362 } 363 364 watchServiceOption := services.WatchServiceEnabled 365 if !datastoreFeatures.Watch.Enabled { 366 log.Ctx(ctx).Warn().Str("reason", datastoreFeatures.Watch.Reason).Msg("watch api disabled; underlying datastore does not support it") 367 watchServiceOption = services.WatchServiceDisabled 368 } 369 370 opts := MiddlewareOption{ 371 log.Logger, 372 c.GRPCAuthFunc, 373 !c.DisableVersionResponse, 374 dispatcher, 375 ds, 376 c.EnableRequestLogs, 377 c.EnableResponseLogs, 378 c.DisableGRPCLatencyHistogram, 379 } 380 defaultUnaryMiddlewareChain, err := DefaultUnaryMiddleware(opts) 381 if err != nil { 382 return nil, fmt.Errorf("error building default middlewares: %w", err) 383 } 384 385 defaultStreamingMiddlewareChain, err := DefaultStreamingMiddleware(opts) 386 if err != nil { 387 return nil, fmt.Errorf("error building default middlewares: %w", err) 388 } 389 390 sameMiddlewares := defaultUnaryMiddlewareChain.Names().Equal(defaultStreamingMiddlewareChain.Names()) 391 if !sameMiddlewares { 392 return nil, fmt.Errorf("unary and streaming middlewares differ: %v / %v", 393 defaultUnaryMiddlewareChain.Names().AsSlice(), 394 defaultStreamingMiddlewareChain.Names().AsSlice(), 395 ) 396 } 397 398 unaryMiddleware, err := c.buildUnaryMiddleware(defaultUnaryMiddlewareChain) 399 if err != nil { 400 return nil, fmt.Errorf("error building unary middlewares: %w", err) 401 } 402 403 streamingMiddleware, err := c.buildStreamingMiddleware(defaultStreamingMiddlewareChain) 404 if err != nil { 405 return nil, fmt.Errorf("error building streaming middlewares: %w", err) 406 } 407 408 permSysConfig := v1svc.PermissionsServerConfig{ 409 MaxPreconditionsCount: c.MaximumPreconditionCount, 410 MaxUpdatesPerWrite: c.MaximumUpdatesPerWrite, 411 MaximumAPIDepth: c.DispatchMaxDepth, 412 MaxCaveatContextSize: c.MaxCaveatContextSize, 413 MaxRelationshipContextSize: c.MaxRelationshipContextSize, 414 MaxDatastoreReadPageSize: c.MaxDatastoreReadPageSize, 415 StreamingAPITimeout: c.StreamingAPITimeout, 416 MaxReadRelationshipsLimit: c.MaxReadRelationshipsLimit, 417 MaxDeleteRelationshipsLimit: c.MaxDeleteRelationshipsLimit, 418 MaxLookupResourcesLimit: c.MaxLookupResourcesLimit, 419 MaxBulkExportRelationshipsLimit: c.MaxBulkExportRelationshipsLimit, 420 } 421 422 healthManager := health.NewHealthManager(dispatcher, ds) 423 grpcServer, err := c.GRPCServer.Complete(zerolog.InfoLevel, 424 func(server *grpc.Server) { 425 services.RegisterGrpcServices( 426 server, 427 healthManager, 428 dispatcher, 429 v1SchemaServiceOption, 430 watchServiceOption, 431 permSysConfig, 432 c.WatchHeartbeat, 433 ) 434 }, 435 ) 436 if err != nil { 437 return nil, fmt.Errorf("failed to create gRPC server: %w", err) 438 } 439 closeables.AddWithoutError(grpcServer.GracefulStop) 440 441 gatewayServer, gatewayCloser, err := c.initializeGateway(ctx) 442 if err != nil { 443 return nil, err 444 } 445 closeables.AddCloser(gatewayCloser) 446 closeables.AddWithoutError(gatewayServer.Close) 447 448 var telemetryRegistry *prometheus.Registry 449 450 reporter := telemetry.DisabledReporter 451 if c.SilentlyDisableTelemetry { 452 reporter = telemetry.SilentlyDisabledReporter 453 } else if c.TelemetryEndpoint != "" && c.DatastoreConfig.DisableStats { 454 reporter = telemetry.DisabledReporter 455 } else if c.TelemetryEndpoint != "" { 456 log.Ctx(ctx).Debug().Msg("initializing telemetry collector") 457 registry, err := telemetry.RegisterTelemetryCollector(c.DatastoreConfig.Engine, ds) 458 if err != nil { 459 log.Warn().Err(err).Msg("unable to initialize telemetry collector") 460 } else { 461 telemetryRegistry = registry 462 reporter, err = telemetry.RemoteReporter( 463 telemetryRegistry, c.TelemetryEndpoint, c.TelemetryCAOverridePath, c.TelemetryInterval, 464 ) 465 if err != nil { 466 log.Warn().Err(err).Msg("unable to initialize telemetry reporter") 467 telemetryRegistry = nil 468 reporter = telemetry.DisabledReporter 469 } 470 } 471 } 472 473 metricsServer, err := c.MetricsAPI.Complete(zerolog.InfoLevel, MetricsHandler(telemetryRegistry, c)) 474 if err != nil { 475 return nil, fmt.Errorf("failed to initialize metrics server: %w", err) 476 } 477 closeables.AddWithoutError(metricsServer.Close) 478 479 return &completedServerConfig{ 480 ds: ds, 481 gRPCServer: grpcServer, 482 dispatchGRPCServer: dispatchGrpcServer, 483 gatewayServer: gatewayServer, 484 metricsServer: metricsServer, 485 unaryMiddleware: unaryMiddleware, 486 streamingMiddleware: streamingMiddleware, 487 presharedKeys: c.PresharedSecureKey, 488 telemetryReporter: reporter, 489 healthManager: healthManager, 490 closeFunc: closeables.Close, 491 }, nil 492 } 493 494 func (c *Config) buildUnaryMiddleware(defaultMiddleware *MiddlewareChain[grpc.UnaryServerInterceptor]) ([]grpc.UnaryServerInterceptor, error) { 495 chain := MiddlewareChain[grpc.UnaryServerInterceptor]{} 496 if defaultMiddleware != nil { 497 chain.chain = append(chain.chain, defaultMiddleware.chain...) 498 } 499 500 if err := chain.modify(c.UnaryMiddlewareModification...); err != nil { 501 return nil, err 502 } 503 504 return chain.ToGRPCInterceptors(), nil 505 } 506 507 func (c *Config) buildStreamingMiddleware(defaultMiddleware *MiddlewareChain[grpc.StreamServerInterceptor]) ([]grpc.StreamServerInterceptor, error) { 508 chain := MiddlewareChain[grpc.StreamServerInterceptor]{} 509 if defaultMiddleware != nil { 510 chain.chain = append(chain.chain, defaultMiddleware.chain...) 511 } 512 513 if err := chain.modify(c.StreamingMiddlewareModification...); err != nil { 514 return nil, err 515 } 516 517 return chain.ToGRPCInterceptors(), nil 518 } 519 520 // initializeGateway Configures the gateway to serve HTTP 521 func (c *Config) initializeGateway(ctx context.Context) (util.RunnableHTTPServer, io.Closer, error) { 522 if len(c.HTTPGatewayUpstreamAddr) == 0 { 523 c.HTTPGatewayUpstreamAddr = c.GRPCServer.Address 524 } else { 525 log.Ctx(ctx).Info().Str("upstream", c.HTTPGatewayUpstreamAddr).Msg("Overriding REST gateway upstream") 526 } 527 528 if len(c.HTTPGatewayUpstreamTLSCertPath) == 0 { 529 c.HTTPGatewayUpstreamTLSCertPath = c.GRPCServer.TLSCertPath 530 } else { 531 log.Ctx(ctx).Info().Str("cert-path", c.HTTPGatewayUpstreamTLSCertPath).Msg("Overriding REST gateway upstream TLS") 532 } 533 534 // If the requested network is a buffered one, then disable the HTTPGateway. 535 if c.GRPCServer.Network == util.BufferedNetwork { 536 c.HTTPGateway.HTTPEnabled = false 537 gatewayServer, err := c.HTTPGateway.Complete(zerolog.InfoLevel, nil) 538 if err != nil { 539 return nil, nil, fmt.Errorf("failed skipping rest gateway initialization: %w", err) 540 } 541 return gatewayServer, nil, nil 542 } 543 544 var gatewayHandler http.Handler 545 closeableGatewayHandler, err := gateway.NewHandler(ctx, c.HTTPGatewayUpstreamAddr, c.HTTPGatewayUpstreamTLSCertPath) 546 if err != nil { 547 return nil, nil, fmt.Errorf("failed to initialize rest gateway: %w", err) 548 } 549 gatewayHandler = closeableGatewayHandler 550 551 if c.HTTPGatewayCorsEnabled { 552 log.Ctx(ctx).Info().Strs("origins", c.HTTPGatewayCorsAllowedOrigins).Msg("Setting REST gateway CORS policy") 553 gatewayHandler = cors.New(cors.Options{ 554 AllowedOrigins: c.HTTPGatewayCorsAllowedOrigins, 555 AllowCredentials: true, 556 AllowedHeaders: []string{"Authorization", "Content-Type"}, 557 Debug: log.Debug().Enabled(), 558 }).Handler(gatewayHandler) 559 } 560 561 if c.HTTPGateway.HTTPEnabled { 562 log.Ctx(ctx).Info().Str("upstream", c.HTTPGatewayUpstreamAddr).Msg("starting REST gateway") 563 } 564 565 gatewayServer, err := c.HTTPGateway.Complete(zerolog.InfoLevel, gatewayHandler) 566 if err != nil { 567 return nil, nil, fmt.Errorf("failed to initialize rest gateway: %w", err) 568 } 569 return gatewayServer, closeableGatewayHandler, nil 570 } 571 572 // RunnableServer is a spicedb service set ready to run 573 type RunnableServer interface { 574 Run(ctx context.Context) error 575 GRPCDialContext(ctx context.Context, opts ...grpc.DialOption) (*grpc.ClientConn, error) 576 DispatchNetDialContext(ctx context.Context, s string) (net.Conn, error) 577 } 578 579 // completedServerConfig holds the full configuration to run a spicedb server, 580 // but is assumed have already been validated via `Complete()` on Config. 581 // It offers limited options for mutation before Run() starts the services. 582 type completedServerConfig struct { 583 ds datastore.Datastore 584 585 gRPCServer util.RunnableGRPCServer 586 dispatchGRPCServer util.RunnableGRPCServer 587 gatewayServer util.RunnableHTTPServer 588 metricsServer util.RunnableHTTPServer 589 telemetryReporter telemetry.Reporter 590 healthManager health.Manager 591 592 unaryMiddleware []grpc.UnaryServerInterceptor 593 streamingMiddleware []grpc.StreamServerInterceptor 594 presharedKeys []string 595 closeFunc func() error 596 } 597 598 func (c *completedServerConfig) GRPCDialContext(ctx context.Context, opts ...grpc.DialOption) (*grpc.ClientConn, error) { 599 if len(c.presharedKeys) == 0 { 600 return c.gRPCServer.DialContext(ctx, opts...) 601 } 602 if c.gRPCServer.Insecure() { 603 opts = append(opts, grpcutil.WithInsecureBearerToken(c.presharedKeys[0])) 604 } else { 605 opts = append(opts, grpcutil.WithBearerToken(c.presharedKeys[0])) 606 } 607 return c.gRPCServer.DialContext(ctx, opts...) 608 } 609 610 func (c *completedServerConfig) DispatchNetDialContext(ctx context.Context, s string) (net.Conn, error) { 611 return c.dispatchGRPCServer.NetDialContext(ctx, s) 612 } 613 614 func (c *completedServerConfig) Run(ctx context.Context) error { 615 log.Ctx(ctx).Info().Type("datastore", c.ds).Msg("running server") 616 if startable := datastore.UnwrapAs[datastore.StartableDatastore](c.ds); startable != nil { 617 log.Ctx(ctx).Info().Msg("Start-ing datastore") 618 err := startable.Start(ctx) 619 if err != nil { 620 return err 621 } 622 } 623 624 g, ctx := errgroup.WithContext(ctx) 625 626 stopOnCancelWithErr := func(stopFn func() error) func() error { 627 return func() error { 628 <-ctx.Done() 629 return stopFn() 630 } 631 } 632 633 grpcServer := c.gRPCServer.WithOpts( 634 grpc.ChainUnaryInterceptor(c.unaryMiddleware...), 635 grpc.ChainStreamInterceptor(c.streamingMiddleware...), 636 grpc.StatsHandler(otelgrpc.NewServerHandler())) 637 638 g.Go(c.healthManager.Checker(ctx)) 639 g.Go(grpcServer.Listen(ctx)) 640 g.Go(c.dispatchGRPCServer.Listen(ctx)) 641 g.Go(c.gatewayServer.ListenAndServe) 642 g.Go(c.metricsServer.ListenAndServe) 643 g.Go(func() error { return c.telemetryReporter(ctx) }) 644 645 g.Go(stopOnCancelWithErr(c.closeFunc)) 646 647 if err := g.Wait(); err != nil { 648 log.Ctx(ctx).Warn().Err(err).Msg("error shutting down server") 649 return err 650 } 651 652 return nil 653 }