diff --git a/backend/controller/controller.go b/backend/controller/controller.go index cb123e4c18..e0f086934e 100644 --- a/backend/controller/controller.go +++ b/backend/controller/controller.go @@ -23,6 +23,7 @@ import ( "github.com/alecthomas/kong" "github.com/alecthomas/types/either" "github.com/alecthomas/types/optional" + pubsub2 "github.com/alecthomas/types/pubsub" "github.com/jackc/pgx/v5" "github.com/jellydator/ttlcache/v3" "github.com/jpillora/backoff" @@ -216,7 +217,8 @@ type Service struct { increaseReplicaFailures map[string]int asyncCallsLock sync.Mutex - clientLock sync.Mutex + clientLock sync.Mutex + routeTableUpdated *pubsub2.Topic[struct{}] } func New( @@ -257,6 +259,7 @@ func New( clients: ttlcache.New(ttlcache.WithTTL[string, clients](time.Minute)), config: config, increaseReplicaFailures: map[string]int{}, + routeTableUpdated: pubsub2.New[struct{}](), } svc.schemaState.Store(schemaState{routes: map[string]Route{}, schema: &schema.Schema{}}) @@ -694,34 +697,64 @@ func (s *Service) Ping(ctx context.Context, req *connect.Request[ftlv1.PingReque // GetModuleContext retrieves config, secrets and DSNs for a module. func (s *Service) GetModuleContext(ctx context.Context, req *connect.Request[ftlv1.GetModuleContextRequest], resp *connect.ServerStream[ftlv1.GetModuleContextResponse]) error { + logger := log.FromContext(ctx) + logger.Infof("GetModuleContext for %s", req.Msg.Module) name := req.Msg.Module + updates := s.routeTableUpdated.Subscribe(nil) + defer s.routeTableUpdated.Unsubscribe(updates) + logger.Infof("GetModuleContext 2 for %s", req.Msg.Module) // Initialize checksum to -1; a zero checksum does occur when the context contains no settings lastChecksum := int64(-1) + callableModuleNames := []string{} dbTypes := map[string]modulecontext.DBType{} deps, err := s.dal.GetActiveDeployments(ctx) if err != nil { return connect.NewError(connect.CodeInternal, fmt.Errorf("could not get deployments: %w", err)) } for _, dep := range deps { + callableModules := map[string]bool{} if dep.Module == name { + logger.Infof("GetModuleContext reading deps for for %s", req.Msg.Module) for _, decl := range dep.Schema.Decls { - if db, ok := decl.(*schema.Database); ok { - dbType, err := modulecontext.DBTypeFromString(db.Type) + switch entry := decl.(type) { + case *schema.Database: + dbType, err := modulecontext.DBTypeFromString(entry.Type) if err != nil { // Not much we can do here continue } - dbTypes[db.Name] = dbType + dbTypes[entry.Name] = dbType + case *schema.Verb: + for _, md := range entry.Metadata { + if calls, ok := md.(*schema.MetadataCalls); ok { + for _, call := range calls.Calls { + callableModules[call.Module] = true + } + } + } + default: + } } + callableModuleNames = maps.Keys(callableModules) + callableModuleNames = slices.Sort(callableModuleNames) break } } for { + logger.Infof("GetModuleContext entering main loop for %s", req.Msg.Module) h := sha.New() + routeTable := map[string]string{} + routes := s.schemaState.Load().routes + for _, module := range callableModuleNames { + if route, ok := routes[module]; ok { + routeTable[module] = route.Endpoint + } + } + configs, err := s.cm.MapForModule(ctx, name) if err != nil { return connect.NewError(connect.CodeInternal, fmt.Errorf("could not get configs: %w", err)) @@ -737,11 +770,15 @@ func (s *Service) GetModuleContext(ctx context.Context, req *connect.Request[ftl if err := hashConfigurationMap(h, secrets); err != nil { return connect.NewError(connect.CodeInternal, fmt.Errorf("could not detect change on secrets: %w", err)) } + if err := hashRoutesTable(h, routeTable); err != nil { + return connect.NewError(connect.CodeInternal, fmt.Errorf("could not detect change on routes: %w", err)) + } checksum := int64(binary.BigEndian.Uint64((h.Sum(nil))[0:8])) if checksum != lastChecksum { - response := modulecontext.NewBuilder(name).AddConfigs(configs).AddSecrets(secrets).Build().ToProto() + logger.Infof("GetModuleContext sending response %s", req.Msg.Module) + response := modulecontext.NewBuilder(name).AddConfigs(configs).AddSecrets(secrets).AddRoutes(routeTable).Build().ToProto() if err := resp.Send(response); err != nil { return connect.NewError(connect.CodeInternal, fmt.Errorf("could not send response: %w", err)) @@ -754,6 +791,8 @@ func (s *Service) GetModuleContext(ctx context.Context, req *connect.Request[ftl case <-ctx.Done(): return nil case <-time.After(s.config.ModuleUpdateFrequency): + case <-updates: + } } } @@ -772,6 +811,20 @@ func hashConfigurationMap(h hash.Hash, m map[string][]byte) error { return nil } +// hashRoutesTable computes an order invariant checksum on the configuration +// settings supplied in the map. +func hashRoutesTable(h hash.Hash, m map[string]string) error { + keys := maps.Keys(m) + sort.Strings(keys) + for _, k := range keys { + _, err := h.Write(append([]byte(k), m[k]...)) + if err != nil { + return fmt.Errorf("error hashing routes: %w", err) + } + } + return nil +} + // hashDatabaseConfiguration computes an order invariant checksum on the database // configuration settings supplied in the map. func hashDatabaseConfiguration(h hash.Hash, m map[string]modulecontext.Database) error { @@ -1648,6 +1701,7 @@ func (s *Service) syncRoutesAndSchema(ctx context.Context) (ret time.Duration, e old := s.schemaState.Load().routes newRoutes := map[string]Route{} modulesByName := map[string]*schema.Module{} + changed := false builtins := schema.Builtins().ToProto().(*schemapb.Module) //nolint:forcetypeassert modulesByName[builtins.Name], err = schema.ModuleFromProto(builtins) @@ -1681,6 +1735,7 @@ func (s *Service) syncRoutesAndSchema(ctx context.Context) (ret time.Duration, e continue } deploymentLogger.Infof("Deployed %s", v.Key.String()) + changed = true status.UpdateModuleState(ctx, v.Module, status.BuildStateDeployed) } if prev, ok := newRoutes[v.Module]; ok { @@ -1693,6 +1748,7 @@ func (s *Service) syncRoutesAndSchema(ctx context.Context) (ret time.Duration, e if err != nil { deploymentLogger.Errorf(err, "Failed to set replicas to 0 for deployment %s", prev.Deployment.String()) } + changed = true } newRoutes[v.Module] = Route{Module: v.Module, Deployment: v.Key, Endpoint: targetEndpoint} modulesByName[v.Module] = v.Schema @@ -1704,6 +1760,9 @@ func (s *Service) syncRoutesAndSchema(ctx context.Context) (ret time.Duration, e }) combined := &schema.Schema{Modules: orderedModules} s.schemaState.Store(schemaState{schema: combined, routes: newRoutes}) + if changed { + s.routeTableUpdated.Publish(struct{}{}) + } return time.Second, nil } diff --git a/backend/protos/xyz/block/ftl/v1/module.pb.go b/backend/protos/xyz/block/ftl/v1/module.pb.go index 6ac4a9b5b2..caaa43b032 100644 --- a/backend/protos/xyz/block/ftl/v1/module.pb.go +++ b/backend/protos/xyz/block/ftl/v1/module.pb.go @@ -316,10 +316,11 @@ type GetModuleContextResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` - Configs map[string][]byte `protobuf:"bytes,2,rep,name=configs,proto3" json:"configs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - Secrets map[string][]byte `protobuf:"bytes,3,rep,name=secrets,proto3" json:"secrets,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - Databases []*GetModuleContextResponse_DSN `protobuf:"bytes,4,rep,name=databases,proto3" json:"databases,omitempty"` + Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` + Configs map[string][]byte `protobuf:"bytes,2,rep,name=configs,proto3" json:"configs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Secrets map[string][]byte `protobuf:"bytes,3,rep,name=secrets,proto3" json:"secrets,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Databases []*GetModuleContextResponse_DSN `protobuf:"bytes,4,rep,name=databases,proto3" json:"databases,omitempty"` + Routes []*GetModuleContextResponse_Route `protobuf:"bytes,5,rep,name=routes,proto3" json:"routes,omitempty"` } func (x *GetModuleContextResponse) Reset() { @@ -380,6 +381,13 @@ func (x *GetModuleContextResponse) GetDatabases() []*GetModuleContextResponse_DS return nil } +func (x *GetModuleContextResponse) GetRoutes() []*GetModuleContextResponse_Route { + if x != nil { + return x.Routes + } + return nil +} + type GetModuleContextResponse_Ref struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -494,6 +502,59 @@ func (x *GetModuleContextResponse_DSN) GetDsn() string { return "" } +type GetModuleContextResponse_Route struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` + Uri string `protobuf:"bytes,2,opt,name=uri,proto3" json:"uri,omitempty"` +} + +func (x *GetModuleContextResponse_Route) Reset() { + *x = GetModuleContextResponse_Route{} + mi := &file_xyz_block_ftl_v1_module_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetModuleContextResponse_Route) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetModuleContextResponse_Route) ProtoMessage() {} + +func (x *GetModuleContextResponse_Route) ProtoReflect() protoreflect.Message { + mi := &file_xyz_block_ftl_v1_module_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetModuleContextResponse_Route.ProtoReflect.Descriptor instead. +func (*GetModuleContextResponse_Route) Descriptor() ([]byte, []int) { + return file_xyz_block_ftl_v1_module_proto_rawDescGZIP(), []int{5, 2} +} + +func (x *GetModuleContextResponse_Route) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *GetModuleContextResponse_Route) GetUri() string { + if x != nil { + return x.Uri + } + return "" +} + var File_xyz_block_ftl_v1_module_proto protoreflect.FileDescriptor var file_xyz_block_ftl_v1_module_proto_rawDesc = []byte{ @@ -526,7 +587,7 @@ var file_xyz_block_ftl_v1_module_proto_rawDesc = []byte{ 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x31, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x22, 0xa1, 0x05, 0x0a, 0x18, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x22, 0x9e, 0x06, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, @@ -545,60 +606,68 @@ var file_xyz_block_ftl_v1_module_proto_rawDesc = []byte{ 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x44, 0x53, 0x4e, 0x52, 0x09, 0x64, 0x61, 0x74, 0x61, 0x62, - 0x61, 0x73, 0x65, 0x73, 0x1a, 0x41, 0x0a, 0x03, 0x52, 0x65, 0x66, 0x12, 0x1b, 0x0a, 0x06, 0x6d, - 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x6d, - 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x88, 0x01, 0x01, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x09, 0x0a, 0x07, - 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x1a, 0x72, 0x0a, 0x03, 0x44, 0x53, 0x4e, 0x12, 0x12, - 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x12, 0x45, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x31, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, - 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x44, 0x62, 0x54, - 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x73, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x64, 0x73, 0x6e, 0x1a, 0x3a, 0x0a, 0x0c, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3a, 0x0a, 0x0c, 0x53, 0x65, 0x63, 0x72, 0x65, - 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x22, 0x4a, 0x0a, 0x06, 0x44, 0x62, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, - 0x13, 0x44, 0x42, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, - 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x44, 0x42, 0x5f, 0x54, 0x59, 0x50, - 0x45, 0x5f, 0x50, 0x4f, 0x53, 0x54, 0x47, 0x52, 0x45, 0x53, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, - 0x44, 0x42, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x59, 0x53, 0x51, 0x4c, 0x10, 0x02, 0x32, - 0x8a, 0x03, 0x0a, 0x0d, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x4a, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x1d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, - 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, - 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x6b, 0x0a, - 0x10, 0x47, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, - 0x74, 0x12, 0x29, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, - 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, - 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x78, - 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x61, 0x0a, 0x0c, 0x41, 0x63, - 0x71, 0x75, 0x69, 0x72, 0x65, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x12, 0x25, 0x2e, 0x78, 0x79, 0x7a, - 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, - 0x71, 0x75, 0x69, 0x72, 0x65, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x61, 0x73, 0x65, 0x73, 0x12, 0x48, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x05, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, + 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x1a, 0x41, + 0x0a, 0x03, 0x52, 0x65, 0x66, 0x12, 0x1b, 0x0a, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x88, + 0x01, 0x01, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, + 0x65, 0x1a, 0x72, 0x0a, 0x03, 0x44, 0x53, 0x4e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x45, 0x0a, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x31, 0x2e, 0x78, 0x79, 0x7a, + 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x44, 0x62, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x73, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x64, 0x73, 0x6e, 0x1a, 0x31, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x1a, 0x3a, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3a, 0x0a, 0x0c, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x22, 0x4a, 0x0a, 0x06, 0x44, 0x62, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x44, 0x42, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, + 0x44, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x44, 0x42, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, + 0x4f, 0x53, 0x54, 0x47, 0x52, 0x45, 0x53, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x44, 0x42, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x59, 0x53, 0x51, 0x4c, 0x10, 0x02, 0x32, 0x8a, 0x03, 0x0a, + 0x0d, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4a, + 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x1d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x6b, 0x0a, 0x10, 0x47, 0x65, + 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x29, + 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, + 0x78, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x78, 0x79, 0x7a, 0x2e, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x61, 0x0a, 0x0c, 0x41, 0x63, 0x71, 0x75, 0x69, + 0x72, 0x65, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x12, 0x25, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x71, 0x75, 0x69, + 0x72, 0x65, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, + 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, + 0x31, 0x2e, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x12, 0x5d, 0x0a, 0x0c, 0x50, 0x75, + 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x2e, 0x78, 0x79, 0x7a, + 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, + 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, - 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x4c, 0x65, 0x61, 0x73, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x12, 0x5d, 0x0a, - 0x0c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x2e, - 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, - 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x44, 0x50, 0x01, - 0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x42, 0x44, - 0x35, 0x34, 0x35, 0x36, 0x36, 0x39, 0x37, 0x35, 0x2f, 0x66, 0x74, 0x6c, 0x2f, 0x62, 0x61, 0x63, - 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x78, 0x79, 0x7a, 0x2f, - 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2f, 0x66, 0x74, 0x6c, 0x2f, 0x76, 0x31, 0x3b, 0x66, 0x74, 0x6c, - 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x44, 0x50, 0x01, 0x5a, 0x40, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x42, 0x44, 0x35, 0x34, 0x35, + 0x36, 0x36, 0x39, 0x37, 0x35, 0x2f, 0x66, 0x74, 0x6c, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, + 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x78, 0x79, 0x7a, 0x2f, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x2f, 0x66, 0x74, 0x6c, 0x2f, 0x76, 0x31, 0x3b, 0x66, 0x74, 0x6c, 0x76, 0x31, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -614,44 +683,46 @@ func file_xyz_block_ftl_v1_module_proto_rawDescGZIP() []byte { } var file_xyz_block_ftl_v1_module_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_xyz_block_ftl_v1_module_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_xyz_block_ftl_v1_module_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_xyz_block_ftl_v1_module_proto_goTypes = []any{ - (GetModuleContextResponse_DbType)(0), // 0: xyz.block.ftl.v1.GetModuleContextResponse.DbType - (*AcquireLeaseRequest)(nil), // 1: xyz.block.ftl.v1.AcquireLeaseRequest - (*AcquireLeaseResponse)(nil), // 2: xyz.block.ftl.v1.AcquireLeaseResponse - (*PublishEventRequest)(nil), // 3: xyz.block.ftl.v1.PublishEventRequest - (*PublishEventResponse)(nil), // 4: xyz.block.ftl.v1.PublishEventResponse - (*GetModuleContextRequest)(nil), // 5: xyz.block.ftl.v1.GetModuleContextRequest - (*GetModuleContextResponse)(nil), // 6: xyz.block.ftl.v1.GetModuleContextResponse - (*GetModuleContextResponse_Ref)(nil), // 7: xyz.block.ftl.v1.GetModuleContextResponse.Ref - (*GetModuleContextResponse_DSN)(nil), // 8: xyz.block.ftl.v1.GetModuleContextResponse.DSN - nil, // 9: xyz.block.ftl.v1.GetModuleContextResponse.ConfigsEntry - nil, // 10: xyz.block.ftl.v1.GetModuleContextResponse.SecretsEntry - (*durationpb.Duration)(nil), // 11: google.protobuf.Duration - (*v1.Ref)(nil), // 12: xyz.block.ftl.schema.v1.Ref - (*PingRequest)(nil), // 13: xyz.block.ftl.v1.PingRequest - (*PingResponse)(nil), // 14: xyz.block.ftl.v1.PingResponse + (GetModuleContextResponse_DbType)(0), // 0: xyz.block.ftl.v1.GetModuleContextResponse.DbType + (*AcquireLeaseRequest)(nil), // 1: xyz.block.ftl.v1.AcquireLeaseRequest + (*AcquireLeaseResponse)(nil), // 2: xyz.block.ftl.v1.AcquireLeaseResponse + (*PublishEventRequest)(nil), // 3: xyz.block.ftl.v1.PublishEventRequest + (*PublishEventResponse)(nil), // 4: xyz.block.ftl.v1.PublishEventResponse + (*GetModuleContextRequest)(nil), // 5: xyz.block.ftl.v1.GetModuleContextRequest + (*GetModuleContextResponse)(nil), // 6: xyz.block.ftl.v1.GetModuleContextResponse + (*GetModuleContextResponse_Ref)(nil), // 7: xyz.block.ftl.v1.GetModuleContextResponse.Ref + (*GetModuleContextResponse_DSN)(nil), // 8: xyz.block.ftl.v1.GetModuleContextResponse.DSN + (*GetModuleContextResponse_Route)(nil), // 9: xyz.block.ftl.v1.GetModuleContextResponse.Route + nil, // 10: xyz.block.ftl.v1.GetModuleContextResponse.ConfigsEntry + nil, // 11: xyz.block.ftl.v1.GetModuleContextResponse.SecretsEntry + (*durationpb.Duration)(nil), // 12: google.protobuf.Duration + (*v1.Ref)(nil), // 13: xyz.block.ftl.schema.v1.Ref + (*PingRequest)(nil), // 14: xyz.block.ftl.v1.PingRequest + (*PingResponse)(nil), // 15: xyz.block.ftl.v1.PingResponse } var file_xyz_block_ftl_v1_module_proto_depIdxs = []int32{ - 11, // 0: xyz.block.ftl.v1.AcquireLeaseRequest.ttl:type_name -> google.protobuf.Duration - 12, // 1: xyz.block.ftl.v1.PublishEventRequest.topic:type_name -> xyz.block.ftl.schema.v1.Ref - 9, // 2: xyz.block.ftl.v1.GetModuleContextResponse.configs:type_name -> xyz.block.ftl.v1.GetModuleContextResponse.ConfigsEntry - 10, // 3: xyz.block.ftl.v1.GetModuleContextResponse.secrets:type_name -> xyz.block.ftl.v1.GetModuleContextResponse.SecretsEntry + 12, // 0: xyz.block.ftl.v1.AcquireLeaseRequest.ttl:type_name -> google.protobuf.Duration + 13, // 1: xyz.block.ftl.v1.PublishEventRequest.topic:type_name -> xyz.block.ftl.schema.v1.Ref + 10, // 2: xyz.block.ftl.v1.GetModuleContextResponse.configs:type_name -> xyz.block.ftl.v1.GetModuleContextResponse.ConfigsEntry + 11, // 3: xyz.block.ftl.v1.GetModuleContextResponse.secrets:type_name -> xyz.block.ftl.v1.GetModuleContextResponse.SecretsEntry 8, // 4: xyz.block.ftl.v1.GetModuleContextResponse.databases:type_name -> xyz.block.ftl.v1.GetModuleContextResponse.DSN - 0, // 5: xyz.block.ftl.v1.GetModuleContextResponse.DSN.type:type_name -> xyz.block.ftl.v1.GetModuleContextResponse.DbType - 13, // 6: xyz.block.ftl.v1.ModuleService.Ping:input_type -> xyz.block.ftl.v1.PingRequest - 5, // 7: xyz.block.ftl.v1.ModuleService.GetModuleContext:input_type -> xyz.block.ftl.v1.GetModuleContextRequest - 1, // 8: xyz.block.ftl.v1.ModuleService.AcquireLease:input_type -> xyz.block.ftl.v1.AcquireLeaseRequest - 3, // 9: xyz.block.ftl.v1.ModuleService.PublishEvent:input_type -> xyz.block.ftl.v1.PublishEventRequest - 14, // 10: xyz.block.ftl.v1.ModuleService.Ping:output_type -> xyz.block.ftl.v1.PingResponse - 6, // 11: xyz.block.ftl.v1.ModuleService.GetModuleContext:output_type -> xyz.block.ftl.v1.GetModuleContextResponse - 2, // 12: xyz.block.ftl.v1.ModuleService.AcquireLease:output_type -> xyz.block.ftl.v1.AcquireLeaseResponse - 4, // 13: xyz.block.ftl.v1.ModuleService.PublishEvent:output_type -> xyz.block.ftl.v1.PublishEventResponse - 10, // [10:14] is the sub-list for method output_type - 6, // [6:10] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name + 9, // 5: xyz.block.ftl.v1.GetModuleContextResponse.routes:type_name -> xyz.block.ftl.v1.GetModuleContextResponse.Route + 0, // 6: xyz.block.ftl.v1.GetModuleContextResponse.DSN.type:type_name -> xyz.block.ftl.v1.GetModuleContextResponse.DbType + 14, // 7: xyz.block.ftl.v1.ModuleService.Ping:input_type -> xyz.block.ftl.v1.PingRequest + 5, // 8: xyz.block.ftl.v1.ModuleService.GetModuleContext:input_type -> xyz.block.ftl.v1.GetModuleContextRequest + 1, // 9: xyz.block.ftl.v1.ModuleService.AcquireLease:input_type -> xyz.block.ftl.v1.AcquireLeaseRequest + 3, // 10: xyz.block.ftl.v1.ModuleService.PublishEvent:input_type -> xyz.block.ftl.v1.PublishEventRequest + 15, // 11: xyz.block.ftl.v1.ModuleService.Ping:output_type -> xyz.block.ftl.v1.PingResponse + 6, // 12: xyz.block.ftl.v1.ModuleService.GetModuleContext:output_type -> xyz.block.ftl.v1.GetModuleContextResponse + 2, // 13: xyz.block.ftl.v1.ModuleService.AcquireLease:output_type -> xyz.block.ftl.v1.AcquireLeaseResponse + 4, // 14: xyz.block.ftl.v1.ModuleService.PublishEvent:output_type -> xyz.block.ftl.v1.PublishEventResponse + 11, // [11:15] is the sub-list for method output_type + 7, // [7:11] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name } func init() { file_xyz_block_ftl_v1_module_proto_init() } @@ -667,7 +738,7 @@ func file_xyz_block_ftl_v1_module_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_xyz_block_ftl_v1_module_proto_rawDesc, NumEnums: 1, - NumMessages: 10, + NumMessages: 11, NumExtensions: 0, NumServices: 1, }, diff --git a/backend/protos/xyz/block/ftl/v1/module.proto b/backend/protos/xyz/block/ftl/v1/module.proto index 8db4036a08..7d9ba4926e 100644 --- a/backend/protos/xyz/block/ftl/v1/module.proto +++ b/backend/protos/xyz/block/ftl/v1/module.proto @@ -48,10 +48,16 @@ message GetModuleContextResponse { string dsn = 3; } + message Route { + string module = 1; + string uri = 2; + } + string module = 1; map configs = 2; map secrets = 3; repeated DSN databases = 4; + repeated Route routes = 5; } // ModuleService is the service that modules use to interact with the Controller. diff --git a/backend/runner/proxy/proxy.go b/backend/runner/proxy/proxy.go index d10639ca32..57b6bf7316 100644 --- a/backend/runner/proxy/proxy.go +++ b/backend/runner/proxy/proxy.go @@ -8,6 +8,8 @@ import ( ftlv1 "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1" "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/ftlv1connect" + "github.com/TBD54566975/ftl/internal/log" + "github.com/TBD54566975/ftl/internal/rpc" "github.com/TBD54566975/ftl/internal/rpc/headers" ) @@ -15,14 +17,14 @@ var _ ftlv1connect.VerbServiceHandler = &Service{} var _ ftlv1connect.ModuleServiceHandler = &Service{} type Service struct { - controllerVerbService ftlv1connect.VerbServiceClient + moduleVerbService map[string]ftlv1connect.VerbServiceClient controllerModuleService ftlv1connect.ModuleServiceClient } -func New(controllerVerbService ftlv1connect.VerbServiceClient, controllerModuleService ftlv1connect.ModuleServiceClient) *Service { +func New(controllerModuleService ftlv1connect.ModuleServiceClient) *Service { proxy := &Service{ - controllerVerbService: controllerVerbService, controllerModuleService: controllerModuleService, + moduleVerbService: map[string]ftlv1connect.VerbServiceClient{}, } return proxy } @@ -34,7 +36,17 @@ func (r *Service) GetModuleContext(ctx context.Context, c *connect.Request[ftlv1 } for { rcv := moduleContext.Receive() + if rcv { + newRouteTable := map[string]ftlv1connect.VerbServiceClient{} + for _, route := range moduleContext.Msg().Routes { + if client, ok := r.moduleVerbService[route.Module]; ok { + newRouteTable[route.Module] = client + } else { + newRouteTable[route.Module] = rpc.Dial(ftlv1connect.NewVerbServiceClient, route.Uri, log.Error) + } + } + r.moduleVerbService = newRouteTable err := c2.Send(moduleContext.Msg()) if err != nil { return fmt.Errorf("failed to send message: %w", err) @@ -83,7 +95,12 @@ func (r *Service) Ping(ctx context.Context, c *connect.Request[ftlv1.PingRequest func (r *Service) Call(ctx context.Context, c *connect.Request[ftlv1.CallRequest]) (*connect.Response[ftlv1.CallResponse], error) { - call, err := r.controllerVerbService.Call(ctx, headers.CopyRequestForForwarding(c)) + client, ok := r.moduleVerbService[c.Msg.Verb.Module] + if !ok { + return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("module not found: %s", c.Msg.Verb.Module)) + } + + call, err := client.Call(ctx, headers.CopyRequestForForwarding(c)) if err != nil { return nil, fmt.Errorf("failed to proxy verb: %w", err) } diff --git a/backend/runner/runner.go b/backend/runner/runner.go index d17b7ddb57..279c6c8404 100644 --- a/backend/runner/runner.go +++ b/backend/runner/runner.go @@ -410,8 +410,7 @@ func (s *Service) deploy(ctx context.Context, key model.DeploymentKey, module *s s.pubSub = pubSub moduleServiceClient := rpc.Dial(ftlv1connect.NewModuleServiceClient, s.config.ControllerEndpoint.String(), log.Error) - verbServiceClient := rpc.Dial(ftlv1connect.NewVerbServiceClient, s.config.ControllerEndpoint.String(), log.Error) - s.proxy = proxy.New(verbServiceClient, moduleServiceClient) + s.proxy = proxy.New(moduleServiceClient) parse, err := url.Parse("http://127.0.0.1:0") if err != nil { diff --git a/frontend/console/src/protos/xyz/block/ftl/v1/module_pb.ts b/frontend/console/src/protos/xyz/block/ftl/v1/module_pb.ts index fcdef6289e..e81c875344 100644 --- a/frontend/console/src/protos/xyz/block/ftl/v1/module_pb.ts +++ b/frontend/console/src/protos/xyz/block/ftl/v1/module_pb.ts @@ -230,6 +230,11 @@ export class GetModuleContextResponse extends Message */ databases: GetModuleContextResponse_DSN[] = []; + /** + * @generated from field: repeated xyz.block.ftl.v1.GetModuleContextResponse.Route routes = 5; + */ + routes: GetModuleContextResponse_Route[] = []; + constructor(data?: PartialMessage) { super(); proto3.util.initPartial(data, this); @@ -242,6 +247,7 @@ export class GetModuleContextResponse extends Message { no: 2, name: "configs", kind: "map", K: 9 /* ScalarType.STRING */, V: {kind: "scalar", T: 12 /* ScalarType.BYTES */} }, { no: 3, name: "secrets", kind: "map", K: 9 /* ScalarType.STRING */, V: {kind: "scalar", T: 12 /* ScalarType.BYTES */} }, { no: 4, name: "databases", kind: "message", T: GetModuleContextResponse_DSN, repeated: true }, + { no: 5, name: "routes", kind: "message", T: GetModuleContextResponse_Route, repeated: true }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): GetModuleContextResponse { @@ -379,3 +385,46 @@ export class GetModuleContextResponse_DSN extends Message { + /** + * @generated from field: string module = 1; + */ + module = ""; + + /** + * @generated from field: string uri = 2; + */ + uri = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "xyz.block.ftl.v1.GetModuleContextResponse.Route"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "module", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "uri", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): GetModuleContextResponse_Route { + return new GetModuleContextResponse_Route().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): GetModuleContextResponse_Route { + return new GetModuleContextResponse_Route().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): GetModuleContextResponse_Route { + return new GetModuleContextResponse_Route().fromJsonString(jsonString, options); + } + + static equals(a: GetModuleContextResponse_Route | PlainMessage | undefined, b: GetModuleContextResponse_Route | PlainMessage | undefined): boolean { + return proto3.util.equals(GetModuleContextResponse_Route, a, b); + } +} + diff --git a/internal/modulecontext/module_context.go b/internal/modulecontext/module_context.go index 6cae4057fb..b1e6e25a24 100644 --- a/internal/modulecontext/module_context.go +++ b/internal/modulecontext/module_context.go @@ -37,6 +37,7 @@ type ModuleContext struct { module string configs map[string][]byte secrets map[string][]byte + routes map[string]string databases map[string]Database isTesting bool @@ -68,6 +69,7 @@ func NewBuilder(module string) *Builder { secrets: map[string][]byte{}, databases: map[string]Database{}, mockVerbs: map[schema.RefKey]Verb{}, + routes: map[string]string{}, } } @@ -101,6 +103,14 @@ func (b *Builder) AddSecrets(secrets map[string][]byte) *Builder { return b } +// AddRoutes adds configuration values (as bytes) to the builder +func (b *Builder) AddRoutes(routes map[string]string) *Builder { + for name, data := range routes { + b.routes[name] = data + } + return b +} + // AddDatabases adds databases to the builder func (b *Builder) AddDatabases(databases map[string]Database) *Builder { for name, db := range databases { diff --git a/internal/modulecontext/to_proto.go b/internal/modulecontext/to_proto.go index ad3af8b4cc..0e3da8fb27 100644 --- a/internal/modulecontext/to_proto.go +++ b/internal/modulecontext/to_proto.go @@ -17,10 +17,18 @@ func (m ModuleContext) ToProto() *ftlv1.GetModuleContextResponse { Dsn: entry.DSN, }) } + routes := make([]*ftlv1.GetModuleContextResponse_Route, 0, len(m.routes)) + for name, entry := range m.routes { + routes = append(routes, &ftlv1.GetModuleContextResponse_Route{ + Module: name, + Uri: entry, + }) + } return &ftlv1.GetModuleContextResponse{ Module: m.module, Configs: m.configs, Secrets: m.secrets, + Routes: routes, Databases: databases, } } diff --git a/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/v1/module_pb2.py b/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/v1/module_pb2.py index 723cf037a3..13bd0ad38e 100644 --- a/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/v1/module_pb2.py +++ b/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/v1/module_pb2.py @@ -27,7 +27,7 @@ from xyz.block.ftl.v1 import ftl_pb2 as xyz_dot_block_dot_ftl_dot_v1_dot_ftl__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1dxyz/block/ftl/v1/module.proto\x12\x10xyz.block.ftl.v1\x1a\x1egoogle/protobuf/duration.proto\x1a$xyz/block/ftl/schema/v1/schema.proto\x1a\x1axyz/block/ftl/v1/ftl.proto\"l\n\x13\x41\x63quireLeaseRequest\x12\x16\n\x06module\x18\x01 \x01(\tR\x06module\x12\x10\n\x03key\x18\x02 \x03(\tR\x03key\x12+\n\x03ttl\x18\x03 \x01(\x0b\x32\x19.google.protobuf.DurationR\x03ttl\"\x16\n\x14\x41\x63quireLeaseResponse\"u\n\x13PublishEventRequest\x12\x32\n\x05topic\x18\x01 \x01(\x0b\x32\x1c.xyz.block.ftl.schema.v1.RefR\x05topic\x12\x12\n\x04\x62ody\x18\x02 \x01(\x0cR\x04\x62ody\x12\x16\n\x06\x63\x61ller\x18\x03 \x01(\tR\x06\x63\x61ller\"\x16\n\x14PublishEventResponse\"1\n\x17GetModuleContextRequest\x12\x16\n\x06module\x18\x01 \x01(\tR\x06module\"\xa1\x05\n\x18GetModuleContextResponse\x12\x16\n\x06module\x18\x01 \x01(\tR\x06module\x12Q\n\x07\x63onfigs\x18\x02 \x03(\x0b\x32\x37.xyz.block.ftl.v1.GetModuleContextResponse.ConfigsEntryR\x07\x63onfigs\x12Q\n\x07secrets\x18\x03 \x03(\x0b\x32\x37.xyz.block.ftl.v1.GetModuleContextResponse.SecretsEntryR\x07secrets\x12L\n\tdatabases\x18\x04 \x03(\x0b\x32..xyz.block.ftl.v1.GetModuleContextResponse.DSNR\tdatabases\x1a\x41\n\x03Ref\x12\x1b\n\x06module\x18\x01 \x01(\tH\x00R\x06module\x88\x01\x01\x12\x12\n\x04name\x18\x02 \x01(\tR\x04nameB\t\n\x07_module\x1ar\n\x03\x44SN\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x45\n\x04type\x18\x02 \x01(\x0e\x32\x31.xyz.block.ftl.v1.GetModuleContextResponse.DbTypeR\x04type\x12\x10\n\x03\x64sn\x18\x03 \x01(\tR\x03\x64sn\x1a:\n\x0c\x43onfigsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\x0cR\x05value:\x02\x38\x01\x1a:\n\x0cSecretsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\x0cR\x05value:\x02\x38\x01\"J\n\x06\x44\x62Type\x12\x17\n\x13\x44\x42_TYPE_UNSPECIFIED\x10\x00\x12\x14\n\x10\x44\x42_TYPE_POSTGRES\x10\x01\x12\x11\n\rDB_TYPE_MYSQL\x10\x02\x32\x8a\x03\n\rModuleService\x12J\n\x04Ping\x12\x1d.xyz.block.ftl.v1.PingRequest\x1a\x1e.xyz.block.ftl.v1.PingResponse\"\x03\x90\x02\x01\x12k\n\x10GetModuleContext\x12).xyz.block.ftl.v1.GetModuleContextRequest\x1a*.xyz.block.ftl.v1.GetModuleContextResponse0\x01\x12\x61\n\x0c\x41\x63quireLease\x12%.xyz.block.ftl.v1.AcquireLeaseRequest\x1a&.xyz.block.ftl.v1.AcquireLeaseResponse(\x01\x30\x01\x12]\n\x0cPublishEvent\x12%.xyz.block.ftl.v1.PublishEventRequest\x1a&.xyz.block.ftl.v1.PublishEventResponseBDP\x01Z@github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1;ftlv1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1dxyz/block/ftl/v1/module.proto\x12\x10xyz.block.ftl.v1\x1a\x1egoogle/protobuf/duration.proto\x1a$xyz/block/ftl/schema/v1/schema.proto\x1a\x1axyz/block/ftl/v1/ftl.proto\"l\n\x13\x41\x63quireLeaseRequest\x12\x16\n\x06module\x18\x01 \x01(\tR\x06module\x12\x10\n\x03key\x18\x02 \x03(\tR\x03key\x12+\n\x03ttl\x18\x03 \x01(\x0b\x32\x19.google.protobuf.DurationR\x03ttl\"\x16\n\x14\x41\x63quireLeaseResponse\"u\n\x13PublishEventRequest\x12\x32\n\x05topic\x18\x01 \x01(\x0b\x32\x1c.xyz.block.ftl.schema.v1.RefR\x05topic\x12\x12\n\x04\x62ody\x18\x02 \x01(\x0cR\x04\x62ody\x12\x16\n\x06\x63\x61ller\x18\x03 \x01(\tR\x06\x63\x61ller\"\x16\n\x14PublishEventResponse\"1\n\x17GetModuleContextRequest\x12\x16\n\x06module\x18\x01 \x01(\tR\x06module\"\x9e\x06\n\x18GetModuleContextResponse\x12\x16\n\x06module\x18\x01 \x01(\tR\x06module\x12Q\n\x07\x63onfigs\x18\x02 \x03(\x0b\x32\x37.xyz.block.ftl.v1.GetModuleContextResponse.ConfigsEntryR\x07\x63onfigs\x12Q\n\x07secrets\x18\x03 \x03(\x0b\x32\x37.xyz.block.ftl.v1.GetModuleContextResponse.SecretsEntryR\x07secrets\x12L\n\tdatabases\x18\x04 \x03(\x0b\x32..xyz.block.ftl.v1.GetModuleContextResponse.DSNR\tdatabases\x12H\n\x06routes\x18\x05 \x03(\x0b\x32\x30.xyz.block.ftl.v1.GetModuleContextResponse.RouteR\x06routes\x1a\x41\n\x03Ref\x12\x1b\n\x06module\x18\x01 \x01(\tH\x00R\x06module\x88\x01\x01\x12\x12\n\x04name\x18\x02 \x01(\tR\x04nameB\t\n\x07_module\x1ar\n\x03\x44SN\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x45\n\x04type\x18\x02 \x01(\x0e\x32\x31.xyz.block.ftl.v1.GetModuleContextResponse.DbTypeR\x04type\x12\x10\n\x03\x64sn\x18\x03 \x01(\tR\x03\x64sn\x1a\x31\n\x05Route\x12\x16\n\x06module\x18\x01 \x01(\tR\x06module\x12\x10\n\x03uri\x18\x02 \x01(\tR\x03uri\x1a:\n\x0c\x43onfigsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\x0cR\x05value:\x02\x38\x01\x1a:\n\x0cSecretsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\x0cR\x05value:\x02\x38\x01\"J\n\x06\x44\x62Type\x12\x17\n\x13\x44\x42_TYPE_UNSPECIFIED\x10\x00\x12\x14\n\x10\x44\x42_TYPE_POSTGRES\x10\x01\x12\x11\n\rDB_TYPE_MYSQL\x10\x02\x32\x8a\x03\n\rModuleService\x12J\n\x04Ping\x12\x1d.xyz.block.ftl.v1.PingRequest\x1a\x1e.xyz.block.ftl.v1.PingResponse\"\x03\x90\x02\x01\x12k\n\x10GetModuleContext\x12).xyz.block.ftl.v1.GetModuleContextRequest\x1a*.xyz.block.ftl.v1.GetModuleContextResponse0\x01\x12\x61\n\x0c\x41\x63quireLease\x12%.xyz.block.ftl.v1.AcquireLeaseRequest\x1a&.xyz.block.ftl.v1.AcquireLeaseResponse(\x01\x30\x01\x12]\n\x0cPublishEvent\x12%.xyz.block.ftl.v1.PublishEventRequest\x1a&.xyz.block.ftl.v1.PublishEventResponseBDP\x01Z@github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1;ftlv1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -52,17 +52,19 @@ _globals['_GETMODULECONTEXTREQUEST']._serialized_start=426 _globals['_GETMODULECONTEXTREQUEST']._serialized_end=475 _globals['_GETMODULECONTEXTRESPONSE']._serialized_start=478 - _globals['_GETMODULECONTEXTRESPONSE']._serialized_end=1151 - _globals['_GETMODULECONTEXTRESPONSE_REF']._serialized_start=774 - _globals['_GETMODULECONTEXTRESPONSE_REF']._serialized_end=839 - _globals['_GETMODULECONTEXTRESPONSE_DSN']._serialized_start=841 - _globals['_GETMODULECONTEXTRESPONSE_DSN']._serialized_end=955 - _globals['_GETMODULECONTEXTRESPONSE_CONFIGSENTRY']._serialized_start=957 - _globals['_GETMODULECONTEXTRESPONSE_CONFIGSENTRY']._serialized_end=1015 - _globals['_GETMODULECONTEXTRESPONSE_SECRETSENTRY']._serialized_start=1017 - _globals['_GETMODULECONTEXTRESPONSE_SECRETSENTRY']._serialized_end=1075 - _globals['_GETMODULECONTEXTRESPONSE_DBTYPE']._serialized_start=1077 - _globals['_GETMODULECONTEXTRESPONSE_DBTYPE']._serialized_end=1151 - _globals['_MODULESERVICE']._serialized_start=1154 - _globals['_MODULESERVICE']._serialized_end=1548 + _globals['_GETMODULECONTEXTRESPONSE']._serialized_end=1276 + _globals['_GETMODULECONTEXTRESPONSE_REF']._serialized_start=848 + _globals['_GETMODULECONTEXTRESPONSE_REF']._serialized_end=913 + _globals['_GETMODULECONTEXTRESPONSE_DSN']._serialized_start=915 + _globals['_GETMODULECONTEXTRESPONSE_DSN']._serialized_end=1029 + _globals['_GETMODULECONTEXTRESPONSE_ROUTE']._serialized_start=1031 + _globals['_GETMODULECONTEXTRESPONSE_ROUTE']._serialized_end=1080 + _globals['_GETMODULECONTEXTRESPONSE_CONFIGSENTRY']._serialized_start=1082 + _globals['_GETMODULECONTEXTRESPONSE_CONFIGSENTRY']._serialized_end=1140 + _globals['_GETMODULECONTEXTRESPONSE_SECRETSENTRY']._serialized_start=1142 + _globals['_GETMODULECONTEXTRESPONSE_SECRETSENTRY']._serialized_end=1200 + _globals['_GETMODULECONTEXTRESPONSE_DBTYPE']._serialized_start=1202 + _globals['_GETMODULECONTEXTRESPONSE_DBTYPE']._serialized_end=1276 + _globals['_MODULESERVICE']._serialized_start=1279 + _globals['_MODULESERVICE']._serialized_end=1673 # @@protoc_insertion_point(module_scope) diff --git a/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/v1/module_pb2.pyi b/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/v1/module_pb2.pyi index 8ac880343e..a4a5a632f6 100644 --- a/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/v1/module_pb2.pyi +++ b/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/v1/module_pb2.pyi @@ -44,7 +44,7 @@ class GetModuleContextRequest(_message.Message): def __init__(self, module: _Optional[str] = ...) -> None: ... class GetModuleContextResponse(_message.Message): - __slots__ = ("module", "configs", "secrets", "databases") + __slots__ = ("module", "configs", "secrets", "databases", "routes") class DbType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): __slots__ = () DB_TYPE_UNSPECIFIED: _ClassVar[GetModuleContextResponse.DbType] @@ -69,6 +69,13 @@ class GetModuleContextResponse(_message.Message): type: GetModuleContextResponse.DbType dsn: str def __init__(self, name: _Optional[str] = ..., type: _Optional[_Union[GetModuleContextResponse.DbType, str]] = ..., dsn: _Optional[str] = ...) -> None: ... + class Route(_message.Message): + __slots__ = ("module", "uri") + MODULE_FIELD_NUMBER: _ClassVar[int] + URI_FIELD_NUMBER: _ClassVar[int] + module: str + uri: str + def __init__(self, module: _Optional[str] = ..., uri: _Optional[str] = ...) -> None: ... class ConfigsEntry(_message.Message): __slots__ = ("key", "value") KEY_FIELD_NUMBER: _ClassVar[int] @@ -87,8 +94,10 @@ class GetModuleContextResponse(_message.Message): CONFIGS_FIELD_NUMBER: _ClassVar[int] SECRETS_FIELD_NUMBER: _ClassVar[int] DATABASES_FIELD_NUMBER: _ClassVar[int] + ROUTES_FIELD_NUMBER: _ClassVar[int] module: str configs: _containers.ScalarMap[str, bytes] secrets: _containers.ScalarMap[str, bytes] databases: _containers.RepeatedCompositeFieldContainer[GetModuleContextResponse.DSN] - def __init__(self, module: _Optional[str] = ..., configs: _Optional[_Mapping[str, bytes]] = ..., secrets: _Optional[_Mapping[str, bytes]] = ..., databases: _Optional[_Iterable[_Union[GetModuleContextResponse.DSN, _Mapping]]] = ...) -> None: ... + routes: _containers.RepeatedCompositeFieldContainer[GetModuleContextResponse.Route] + def __init__(self, module: _Optional[str] = ..., configs: _Optional[_Mapping[str, bytes]] = ..., secrets: _Optional[_Mapping[str, bytes]] = ..., databases: _Optional[_Iterable[_Union[GetModuleContextResponse.DSN, _Mapping]]] = ..., routes: _Optional[_Iterable[_Union[GetModuleContextResponse.Route, _Mapping]]] = ...) -> None: ...