diff --git a/Makefile b/Makefile index 8a482924..00ba10be 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ .PHONY: license build: - go build -v -o bin/function-stream ./cmd + go build -v -o bin/fs ./cmd build-example: tinygo build -o bin/example_basic.wasm -target=wasi ./examples/basic diff --git a/apiclient/apiclient.go b/apiclient/apiclient.go new file mode 100644 index 00000000..fcd191f9 --- /dev/null +++ b/apiclient/apiclient.go @@ -0,0 +1,539 @@ +package apiclient + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "encoding/xml" + "fmt" + "github.com/functionstream/function-stream/fs/model" + "github.com/pkg/errors" + "io" + "mime/multipart" + "net" + "net/http" + "net/http/httputil" + "net/url" + "os" + "path/filepath" + "reflect" + "regexp" + "strings" +) + +type Configuration struct { + Host string `json:"host,omitempty"` + Scheme string `json:"scheme,omitempty"` + DefaultHeader map[string]string `json:"defaultHeader,omitempty"` + UserAgent string `json:"userAgent,omitempty"` + Debug bool `json:"debug,omitempty"` + HTTPClient *http.Client +} + +func NewConfiguration() *Configuration { + cfg := &Configuration{ + DefaultHeader: make(map[string]string), + UserAgent: "Function-Stream-API-Client", // TODO: Add version support + Debug: false, + Scheme: "http", + Host: "localhost:7300", + } + return cfg +} + +type APIClient struct { + cfg *Configuration +} + +func NewAPIClient(cfg *Configuration) *APIClient { + if cfg.HTTPClient == nil { + cfg.HTTPClient = http.DefaultClient + } + + return &APIClient{ + cfg: cfg, + } +} + +type formFile struct { + fileBytes []byte + fileName string + formFileName string +} + +// detectContentType method is used to figure out `Request.Body` content type for request header +func detectContentType(body interface{}) string { + contentType := "text/plain; charset=utf-8" + kind := reflect.TypeOf(body).Kind() + + switch kind { + case reflect.Struct, reflect.Map, reflect.Ptr: + contentType = "application/json; charset=utf-8" + case reflect.String: + contentType = "text/plain; charset=utf-8" + default: + if b, ok := body.([]byte); ok { + contentType = http.DetectContentType(b) + } else if kind == reflect.Slice { + contentType = "application/json; charset=utf-8" + } + } + + return contentType +} + +var ( + JsonCheck = regexp.MustCompile(`(?i:(?:application|text)/(?:[^;]+\+)?json)`) + XmlCheck = regexp.MustCompile(`(?i:(?:application|text)/(?:[^;]+\+)?xml)`) + queryParamSplit = regexp.MustCompile(`(^|&)([^&]+)`) + queryDescape = strings.NewReplacer("%5B", "[", "%5D", "]") +) + +// Set request body from an interface{} +func setBody(body interface{}, contentType string) (bodyBuf *bytes.Buffer, err error) { + if bodyBuf == nil { + bodyBuf = &bytes.Buffer{} + } + + if reader, ok := body.(io.Reader); ok { + _, err = bodyBuf.ReadFrom(reader) + } else if fp, ok := body.(*os.File); ok { + _, err = bodyBuf.ReadFrom(fp) + } else if b, ok := body.([]byte); ok { + _, err = bodyBuf.Write(b) + } else if s, ok := body.(string); ok { + _, err = bodyBuf.WriteString(s) + } else if s, ok := body.(*string); ok { + _, err = bodyBuf.WriteString(*s) + } else if JsonCheck.MatchString(contentType) { + err = json.NewEncoder(bodyBuf).Encode(body) + } else if XmlCheck.MatchString(contentType) { + var bs []byte + bs, err = xml.Marshal(body) + if err == nil { + bodyBuf.Write(bs) + } + } + + if err != nil { + return nil, err + } + + if bodyBuf.Len() == 0 { + err = fmt.Errorf("invalid body type %s\n", contentType) + return nil, err + } + return bodyBuf, nil +} + +// Add a file to the multipart request +func addFile(w *multipart.Writer, fieldName, path string) error { + file, err := os.Open(filepath.Clean(path)) + if err != nil { + return err + } + err = file.Close() + if err != nil { + return err + } + + part, err := w.CreateFormFile(fieldName, filepath.Base(path)) + if err != nil { + return err + } + _, err = io.Copy(part, file) + + return err +} + +// prepareRequest build the request +func (c *APIClient) prepareRequest( + ctx context.Context, + path string, method string, + postBody interface{}, + headerParams map[string]string, + queryParams url.Values, + formParams url.Values, + formFiles []formFile) (localVarRequest *http.Request, err error) { + + var body *bytes.Buffer + + if headerParams == nil { + headerParams = make(map[string]string) + } + + // Detect postBody type and post. + if postBody != nil { + contentType := headerParams["Content-Type"] + if contentType == "" { + contentType = detectContentType(postBody) + headerParams["Content-Type"] = contentType + } + + body, err = setBody(postBody, contentType) + if err != nil { + return nil, err + } + } + + // add form parameters and file if available. + if strings.HasPrefix(headerParams["Content-Type"], "multipart/form-data") && len(formParams) > 0 || (len(formFiles) > 0) { + if body != nil { + return nil, errors.New("Cannot specify postBody and multipart form at the same time.") + } + body = &bytes.Buffer{} + w := multipart.NewWriter(body) + + for k, v := range formParams { + for _, iv := range v { + if strings.HasPrefix(k, "@") { // file + err = addFile(w, k[1:], iv) + if err != nil { + return nil, err + } + } else { // form value + w.WriteField(k, iv) + } + } + } + for _, formFile := range formFiles { + if len(formFile.fileBytes) > 0 && formFile.fileName != "" { + w.Boundary() + part, err := w.CreateFormFile(formFile.formFileName, filepath.Base(formFile.fileName)) + if err != nil { + return nil, err + } + _, err = part.Write(formFile.fileBytes) + if err != nil { + return nil, err + } + } + } + + // Set the Boundary in the Content-Type + headerParams["Content-Type"] = w.FormDataContentType() + + // Set Content-Length + headerParams["Content-Length"] = fmt.Sprintf("%d", body.Len()) + w.Close() + } + + if strings.HasPrefix(headerParams["Content-Type"], "application/x-www-form-urlencoded") && len(formParams) > 0 { + if body != nil { + return nil, errors.New("Cannot specify postBody and x-www-form-urlencoded form at the same time.") + } + body = &bytes.Buffer{} + body.WriteString(formParams.Encode()) + // Set Content-Length + headerParams["Content-Length"] = fmt.Sprintf("%d", body.Len()) + } + + // Setup path and query parameters + url, err := url.Parse(path) + if err != nil { + return nil, err + } + + // Override request host, if applicable + if c.cfg.Host != "" { + url.Host = c.cfg.Host + } + + // Override request scheme, if applicable + if c.cfg.Scheme != "" { + url.Scheme = c.cfg.Scheme + } + + // Adding Query Param + query := url.Query() + for k, v := range queryParams { + for _, iv := range v { + query.Add(k, iv) + } + } + + // Encode the parameters. + url.RawQuery = queryParamSplit.ReplaceAllStringFunc(query.Encode(), func(s string) string { + pieces := strings.Split(s, "=") + pieces[0] = queryDescape.Replace(pieces[0]) + return strings.Join(pieces, "=") + }) + + // Generate a new request + if body != nil { + localVarRequest, err = http.NewRequest(method, url.String(), body) + } else { + localVarRequest, err = http.NewRequest(method, url.String(), nil) + } + if err != nil { + return nil, err + } + + // add header parameters, if any + if len(headerParams) > 0 { + headers := http.Header{} + for h, v := range headerParams { + headers[h] = []string{v} + } + localVarRequest.Header = headers + } + + // Add the user agent to the request. + localVarRequest.Header.Add("User-Agent", c.cfg.UserAgent) + + if ctx != nil { + // add context to the request + localVarRequest = localVarRequest.WithContext(ctx) + + // Walk through any authentication. + + } + + for header, value := range c.cfg.DefaultHeader { + localVarRequest.Header.Add(header, value) + } + return localVarRequest, nil +} + +// callAPI do the request. +func (c *APIClient) callAPI(request *http.Request) (*http.Response, error) { + if c.cfg.Debug { + dump, err := httputil.DumpRequestOut(request, true) + if err != nil { + return nil, err + } + fmt.Printf("\n%s\n", string(dump)) + } + + resp, err := c.cfg.HTTPClient.Do(request) + if err != nil { + return resp, err + } + + if c.cfg.Debug { + dump, err := httputil.DumpResponse(resp, true) + if err != nil { + return resp, err + } + fmt.Printf("\n%s\n", string(dump)) + } + return resp, err +} + +func (c *APIClient) HandleResponse(resp *http.Response, e error) ([]byte, error) { + bodyString := "" + var bodyBytes []byte + if resp != nil { + var err error + bodyBytes, err = io.ReadAll(resp.Body) + if err == nil { + bodyString = string(bodyBytes) + } else { + return nil, fmt.Errorf("failed to read response body: %v", err) + } + } + + if e != nil { + var netErr *net.OpError + if errors.As(e, &netErr) { + var sysErr *os.SyscallError + if errors.As(e, &sysErr) { + return nil, fmt.Errorf("failed to connect to the server: %s", netErr.Addr) + } + return nil, fmt.Errorf("request error: [%v] %s", netErr, bodyString) + } + return nil, fmt.Errorf("request error: [%v] %s", e, bodyString) + } + if resp == nil { + return nil, fmt.Errorf("empty response") + } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, bodyString) + } + + return bodyBytes, nil +} + +type Response struct { + HttpResponse *http.Response +} + +func (c *APIClient) FunctionService() *ResourceService[model.Function] { + return &ResourceService[model.Function]{ + Client: c, + BasePath: "/apis/v1/function", + } +} + +func (c *APIClient) PackageService() *ResourceService[model.Package] { + return &ResourceService[model.Package]{ + Client: c, + BasePath: "/apis/v1/package", + } +} + +func (c *APIClient) GenericService(resourceName string) *ResourceService[map[string]any] { + return &ResourceService[map[string]any]{ + Client: c, + BasePath: "/apis/v1/" + resourceName, + } +} + +type ResourceService[T any] struct { + BasePath string + Client *APIClient +} + +func (rs *ResourceService[T]) Create(ctx context.Context, resource *T) error { + req, err := rs.Client.prepareRequest(ctx, rs.BasePath, http.MethodPost, resource, nil, nil, nil, nil) + if err != nil { + return err + } + _, err = rs.Client.HandleResponse(rs.Client.callAPI(req)) + return err +} + +func (rs *ResourceService[T]) List(ctx context.Context) ([]*T, error) { + req, err := rs.Client.prepareRequest(ctx, rs.BasePath, http.MethodGet, nil, nil, nil, nil, nil) + if err != nil { + return nil, err + } + resp, err := rs.Client.HandleResponse(rs.Client.callAPI(req)) + if err != nil { + return nil, err + } + var resources []*T + err = json.Unmarshal(resp, &resources) + if err != nil { + return nil, err + } + return resources, nil +} + +func (rs *ResourceService[T]) Read(ctx context.Context, name string) (*T, error) { + req, err := rs.Client.prepareRequest(ctx, rs.BasePath+"/"+name, http.MethodGet, nil, nil, nil, nil, nil) + if err != nil { + return nil, err + } + resp, err := rs.Client.HandleResponse(rs.Client.callAPI(req)) + if err != nil { + return nil, err + } + var resource T + err = json.Unmarshal(resp, &resource) + if err != nil { + return nil, err + } + return &resource, nil +} + +func (rs *ResourceService[T]) Upsert(ctx context.Context, resource *T) error { + req, err := rs.Client.prepareRequest(ctx, rs.BasePath, http.MethodPut, resource, nil, nil, nil, nil) + if err != nil { + return err + } + _, err = rs.Client.HandleResponse(rs.Client.callAPI(req)) + return err +} + +func (rs *ResourceService[T]) Delete(ctx context.Context, name string) error { + req, err := rs.Client.prepareRequest(ctx, rs.BasePath+"/"+name, http.MethodDelete, nil, nil, nil, nil, nil) + if err != nil { + return err + } + _, err = rs.Client.HandleResponse(rs.Client.callAPI(req)) + return err +} + +type EventsService struct { + client *APIClient +} + +func (c *APIClient) EventsService() *EventsService { + return &EventsService{ + client: c, + } +} + +type MappedNullable interface { + ToMap() (map[string]interface{}, error) +} + +func parameterValueToString(obj interface{}, key string) string { + if reflect.TypeOf(obj).Kind() != reflect.Ptr { + return fmt.Sprintf("%v", obj) + } + var param, ok = obj.(MappedNullable) + if !ok { + return "" + } + dataMap, err := param.ToMap() + if err != nil { + return "" + } + return fmt.Sprintf("%v", dataMap[key]) +} + +func (es *EventsService) Consume(ctx context.Context, topic string) (<-chan string, <-chan error) { + localVarPath := "/api/v1/events/consume/{name}" + localVarPath = strings.Replace(localVarPath, "{"+"name"+"}", url.PathEscape(parameterValueToString(topic, "name")), -1) + + dataCh := make(chan string) + errCh := make(chan error, 1000) + + go func() { + defer close(dataCh) + defer close(errCh) + + req, err := es.client.prepareRequest(ctx, localVarPath, http.MethodGet, nil, nil, nil, nil, nil) + if err != nil { + errCh <- err + return + } + + if es.client.cfg.Debug { + dump, err := httputil.DumpRequestOut(req, true) + if err != nil { + errCh <- err + return + } + fmt.Printf("\n%s\n", string(dump)) + } + + resp, err := es.client.cfg.HTTPClient.Do(req) + if err != nil { + errCh <- err + return + } + defer resp.Body.Close() + + scanner := bufio.NewScanner(resp.Body) + for scanner.Scan() { + select { + case <-ctx.Done(): + errCh <- ctx.Err() + return + case dataCh <- scanner.Text(): + } + } + + if err := scanner.Err(); err != nil { + errCh <- err + } + }() + + return dataCh, errCh +} + +func (es *EventsService) Produce(ctx context.Context, topic string, event any) error { + localVarPath := "/api/v1/events/produce/{name}" + localVarPath = strings.Replace(localVarPath, "{"+"name"+"}", url.PathEscape(parameterValueToString(topic, "name")), -1) + + req, err := es.client.prepareRequest(ctx, localVarPath, http.MethodPost, event, nil, nil, nil, nil) + if err != nil { + return err + } + _, err = es.client.HandleResponse(es.client.callAPI(req)) + return err +} diff --git a/benchmark/bench_test.go b/benchmark/bench_test.go index ed4fd491..22e79305 100644 --- a/benchmark/bench_test.go +++ b/benchmark/bench_test.go @@ -27,21 +27,21 @@ import ( "github.com/functionstream/function-stream/common/config" - "github.com/functionstream/function-stream/fs/api" - "github.com/functionstream/function-stream/fs/runtime/wazero" + "github.com/functionstream/function-stream/fsold/api" + "github.com/functionstream/function-stream/fsold/runtime/wazero" "github.com/apache/pulsar-client-go/pulsaradmin" "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" adminclient "github.com/functionstream/function-stream/admin/client" adminutils "github.com/functionstream/function-stream/admin/utils" "github.com/functionstream/function-stream/common" - "github.com/functionstream/function-stream/fs/contube" + "github.com/functionstream/function-stream/fsold/contube" "github.com/functionstream/function-stream/perf" - "github.com/functionstream/function-stream/server" + "github.com/functionstream/function-stream/serverold" ) func BenchmarkStressForBasicFunc(b *testing.B) { - s, err := server.NewDefaultServer() + s, err := serverold.NewDefaultServer() if err != nil { b.Fatal(err) } @@ -117,12 +117,12 @@ func BenchmarkStressForBasicFunc(b *testing.B) { func BenchmarkStressForBasicFuncWithMemoryQueue(b *testing.B) { memoryQueueFactory := contube.NewMemoryQueueFactory(context.Background()) - s, err := server.NewServer( - server.WithRuntimeFactoryBuilder(common.WASMRuntime, + s, err := serverold.NewServer( + serverold.WithRuntimeFactoryBuilder(common.WASMRuntime, func(configMap config.ConfigMap) (api.FunctionRuntimeFactory, error) { return wazero.NewWazeroFunctionRuntimeFactory(), nil }), - server.WithTubeFactoryBuilder(common.MemoryTubeType, + serverold.WithTubeFactoryBuilder(common.MemoryTubeType, func(configMap config.ConfigMap) (contube.TubeFactory, error) { return memoryQueueFactory, nil }), diff --git a/clients/gofs/api/api.go b/clients/gofs/api/api.go new file mode 100644 index 00000000..d10a33d9 --- /dev/null +++ b/clients/gofs/api/api.go @@ -0,0 +1,144 @@ +package api + +import ( + "context" +) + +const DefaultModule = "default" + +type Event[T any] interface { + Data() *T + Commit(ctx context.Context) error +} + +type FunctionContext interface { + context.Context + GetState(ctx context.Context, key string) ([]byte, error) + PutState(ctx context.Context, key string, value []byte) error + Write(ctx context.Context, rawEvent Event[[]byte]) error + Read(ctx context.Context) (<-chan Event[[]byte], error) + GetConfig() map[string]string +} + +type BaseModule interface { + Init(ctx FunctionContext) error +} + +type Function[I any, O any] interface { + BaseModule + Handle(ctx FunctionContext, event Event[I]) (Event[O], error) +} + +type Source[O any] interface { + BaseModule + Handle(ctx FunctionContext, emit func(context.Context, Event[O]) error) error +} + +type Sink[I any] interface { + BaseModule + Handle(ctx FunctionContext, event Event[I]) error +} + +type Custom interface { + BaseModule + Handle(ctx FunctionContext) error +} + +type eventImpl[T any] struct { + data *T + commitFunc func(context.Context) error +} + +func NewEvent[T any](data *T) Event[T] { + return NewEventWithCommit(data, nil) +} + +func NewEventWithCommit[T any](data *T, ack func(ctx context.Context) error) Event[T] { + return &eventImpl[T]{ + data: data, + commitFunc: ack, + } +} + +func (e *eventImpl[T]) Data() *T { + return e.data +} + +func (e *eventImpl[T]) Commit(ctx context.Context) error { + if e.commitFunc != nil { + return e.commitFunc(ctx) + } + return nil +} + +type simpleFunction[I any, O any] struct { + handle func(ctx FunctionContext, event Event[I]) (Event[O], error) +} + +func NewSimpleFunction[I any, O any](handle func(ctx FunctionContext, event Event[I]) (Event[O], error)) Function[I, O] { + return &simpleFunction[I, O]{ + handle: handle, + } +} + +func (f *simpleFunction[I, O]) Init(_ FunctionContext) error { + return nil +} + +func (f *simpleFunction[I, O]) Handle(ctx FunctionContext, event Event[I]) (Event[O], error) { + return f.handle(ctx, event) +} + +type simpleSource[O any] struct { + handle func(ctx FunctionContext, emit func(context.Context, Event[O]) error) error +} + +func NewSimpleSource[O any](handle func(ctx FunctionContext, emit func(context.Context, Event[O]) error) error) Source[O] { + return &simpleSource[O]{ + handle: handle, + } +} + +func (s *simpleSource[O]) Init(_ FunctionContext) error { + return nil +} + +func (s *simpleSource[O]) Handle(ctx FunctionContext, emit func(context.Context, Event[O]) error) error { + return s.handle(ctx, emit) +} + +type simpleSink[I any] struct { + handle func(ctx FunctionContext, event Event[I]) error +} + +func NewSimpleSink[I any](handle func(ctx FunctionContext, event Event[I]) error) Sink[I] { + return &simpleSink[I]{ + handle: handle, + } +} + +func (s *simpleSink[I]) Init(_ FunctionContext) error { + return nil +} + +func (s *simpleSink[I]) Handle(ctx FunctionContext, event Event[I]) error { + return s.handle(ctx, event) +} + +type simpleCustom struct { + handle func(ctx FunctionContext) error +} + +func NewSimpleCustom(handle func(ctx FunctionContext) error) Custom { + return &simpleCustom{ + handle: handle, + } +} + +func (c *simpleCustom) Init(_ FunctionContext) error { + return nil +} + +func (c *simpleCustom) Handle(ctx FunctionContext) error { + return c.handle(ctx) +} diff --git a/clients/gofs/client.go b/clients/gofs/client.go new file mode 100644 index 00000000..479332d1 --- /dev/null +++ b/clients/gofs/client.go @@ -0,0 +1,320 @@ +package gofs + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "github.com/functionstream/function-stream/clients/gofs/api" + "github.com/functionstream/function-stream/clients/gofs/rpc" + "github.com/wirelessr/avroschema" + "go.uber.org/zap" + "log/slog" + "sync" +) + +const ( + StateInit int32 = iota + StateRunning +) + +var ( + ErrRegisterModuleDuringRunning = fmt.Errorf("cannot register module during running") + ErrAlreadyRunning = fmt.Errorf("already running") +) + +type FSClient interface { + error + Register(module string, wrapperFact InstanceWrapperFactory) FSClient + Run(ctx context.Context) error +} + +type fsClient struct { + rpc rpc.FSRPCClient + modules map[string]InstanceWrapperFactory + state int32 + registerMu sync.Mutex + err error + + instances map[string]*InstanceWrapper +} + +func (c *fsClient) Error() string { + return c.err.Error() +} + +func NewFSClient() FSClient { + return &fsClient{ + modules: make(map[string]InstanceWrapperFactory), + state: StateInit, + instances: make(map[string]*InstanceWrapper), + } +} + +type InstanceWrapper struct { + *fsClient + ctx *functionContextImpl + rpcCtx *rpc.RPCContext + executeFunc func(api.FunctionContext) error + initFunc func(api.FunctionContext) error + registerErr error +} + +func (m *InstanceWrapper) AddInitFunc(initFunc func(api.FunctionContext) error) *InstanceWrapper { + parentInit := m.initFunc + if parentInit != nil { + m.initFunc = func(ctx api.FunctionContext) error { + err := parentInit(ctx) + if err != nil { + return err + } + return initFunc(ctx) + } + } else { + m.initFunc = initFunc + } + return m +} + +type InstanceWrapperFactory func() *InstanceWrapper + +func (c *fsClient) Register(module string, wrapperFact InstanceWrapperFactory) FSClient { + if c.err != nil { + return c + } + c.registerMu.Lock() + defer c.registerMu.Unlock() + if c.state == StateRunning { + c.err = ErrRegisterModuleDuringRunning + return c + } + c.modules[module] = wrapperFact + return c +} + +func WithFunction[I any, O any](functionFactory func() api.Function[I, O]) InstanceWrapperFactory { + return func() *InstanceWrapper { + m := &InstanceWrapper{} + function := functionFactory() + m.initFunc = func(ctx api.FunctionContext) error { + outputSchema, err := avroschema.Reflect(new(O)) + if err != nil { + return err + } + err = m.rpc.RegisterSchema(ctx, outputSchema) + if err != nil { + return fmt.Errorf("failed to register schema: %w", err) + } + return function.Init(ctx) + } + m.executeFunc = func(ctx api.FunctionContext) error { + return m.rpc.OnReceiveEvent(m.rpcCtx, func(event api.Event[[]byte]) error { + input := new(I) + err := json.Unmarshal(*event.Data(), input) + if err != nil { + return fmt.Errorf("failed to parse JSON: %w", err) + } + output, err := function.Handle(ctx, api.NewEventWithCommit(input, event.Commit)) + if err != nil { + return err + } + outputPayload, err := json.Marshal(output.Data()) + if err != nil { + return fmt.Errorf("failed to marshal JSON: %w", err) + } + return ctx.Write(ctx, api.NewEventWithCommit(&outputPayload, func(ctx context.Context) error { + return errors.Join(event.Commit(ctx), output.Commit(ctx)) + })) + }) + } + return m + } +} + +func WithSource[O any](sourceFactory func() api.Source[O]) InstanceWrapperFactory { + return func() *InstanceWrapper { + m := &InstanceWrapper{} + source := sourceFactory() + emit := func(ctx context.Context, event api.Event[O]) error { + outputPayload, _ := json.Marshal(event.Data()) + return m.ctx.Write(ctx, api.NewEventWithCommit(&outputPayload, event.Commit)) + } + m.initFunc = func(ctx api.FunctionContext) error { + outputSchema, err := avroschema.Reflect(new(O)) + if err != nil { + return err + } + err = m.rpc.RegisterSchema(ctx, outputSchema) + if err != nil { + return fmt.Errorf("failed to register schema: %w", err) + } + return source.Init(ctx) + } + m.executeFunc = func(ctx api.FunctionContext) error { + return source.Handle(ctx, emit) + } + return m + } +} + +func WithSink[I any](sinkFactory func() api.Sink[I]) InstanceWrapperFactory { + return func() *InstanceWrapper { + m := &InstanceWrapper{} + sink := sinkFactory() + m.initFunc = func(ctx api.FunctionContext) error { + inputSchema, err := avroschema.Reflect(new(I)) + if err != nil { + return err + } + err = m.rpc.RegisterSchema(ctx, inputSchema) + if err != nil { + return fmt.Errorf("failed to register schema: %w", err) + } + return sink.Init(ctx) + } + m.executeFunc = func(ctx api.FunctionContext) error { + return m.rpc.OnReceiveEvent(m.rpcCtx, func(event api.Event[[]byte]) error { + input := new(I) + err := json.Unmarshal(*event.Data(), input) + if err != nil { + return fmt.Errorf("failed to parse JSON: %w", err) + } + return sink.Handle(ctx, api.NewEventWithCommit(input, event.Commit)) + }) + } + return m + } +} + +func WithCustom(customFactory func() api.Custom) InstanceWrapperFactory { + return func() *InstanceWrapper { + custom := customFactory() + return &InstanceWrapper{ + initFunc: func(ctx api.FunctionContext) error { + return custom.Init(ctx) + }, + executeFunc: func(ctx api.FunctionContext) error { + return custom.Handle(ctx) + }, + } + } +} + +type functionContextImpl struct { + context.Context + cancelFunc context.CancelFunc + + c *fsClient + name string + config map[string]string +} + +func (c *functionContextImpl) warpContext(parent context.Context) *rpc.RPCContext { + return &rpc.RPCContext{ + Context: parent, + FunctionName: c.name, + } +} + +func (c *functionContextImpl) GetState(ctx context.Context, key string) ([]byte, error) { + return c.c.rpc.GetState(c.warpContext(ctx), key) +} + +func (c *functionContextImpl) PutState(ctx context.Context, key string, value []byte) error { + return c.c.rpc.PutState(c.warpContext(ctx), key, value) +} + +func (c *functionContextImpl) Write(ctx context.Context, rawEvent api.Event[[]byte]) error { + return c.c.rpc.Write(c.warpContext(ctx), rawEvent) +} + +func (c *functionContextImpl) Read(ctx context.Context) (<-chan api.Event[[]byte], error) { + return c.c.rpc.Read(c.warpContext(ctx)) +} + +func (c *functionContextImpl) GetConfig() map[string]string { + return c.config +} + +func (c *functionContextImpl) Close() { + c.cancelFunc() +} + +func (c *fsClient) Run(ctx context.Context) error { + if c.err != nil { + return c.err + } + c.registerMu.Lock() + if c.state == StateRunning { + c.registerMu.Unlock() + return ErrAlreadyRunning + } + c.state = StateRunning + c.registerMu.Unlock() + + rpcCli, err := rpc.NewRPCClient() + if err != nil { + return err + } + c.rpc = rpcCli + + modList := make([]string, 0, len(c.modules)) + for k := range c.modules { + modList = append(modList, k) + } + + eventChan, err := rpcCli.OnEvent(ctx, "", modList) + if err != nil { + return err + } + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case e, ok := <-eventChan: + if !ok { + return nil + } + switch e.Type { + case rpc.FunctionEventType_DEPLOY: + mf, ok := c.modules[e.Function.Module] + if !ok { + return fmt.Errorf("module %s not found", e.Function.Module) + } + m := mf() + fc, cancel := context.WithCancel(ctx) + funcCtx := &functionContextImpl{ + Context: fc, + cancelFunc: cancel, + c: c, + name: e.Function.Name, + } + m.fsClient = c + m.ctx = funcCtx + m.rpcCtx = funcCtx.warpContext(funcCtx) + c.instances[e.Function.Name] = m + go func() { + err := m.initFunc(funcCtx) + if err != nil { + slog.Error("failed to init function", zap.Error(err)) // TODO: We need to figure out how to print log in the function + return + } + err = m.executeFunc(funcCtx) + if err != nil && !errors.Is(err, context.Canceled) { + slog.Error("failed to execute function", zap.Error(err)) // TODO: We need to figure out how to print log in the function + return + } + }() + case rpc.FunctionEventType_DELETE: + m, ok := c.instances[*e.FunctionName] + if !ok { + slog.Warn("delete failed. function not found", slog.String("name", *e.FunctionName)) + } else { + m.ctx.Close() + delete(c.instances, *e.FunctionName) + } + } + } + } +} diff --git a/clients/gofs/rpc/rpc.go b/clients/gofs/rpc/rpc.go new file mode 100644 index 00000000..88056236 --- /dev/null +++ b/clients/gofs/rpc/rpc.go @@ -0,0 +1,52 @@ +package rpc + +import ( + "context" + "github.com/functionstream/function-stream/clients/gofs/api" +) + +type RPCContext struct { + context.Context + FunctionName string +} + +func (r *RPCContext) warp(ctx context.Context) *RPCContext { + return &RPCContext{ + Context: ctx, + FunctionName: r.FunctionName, + } +} + +type FunctionEventType int32 + +const ( + FunctionEventType_DEPLOY FunctionEventType = 0 + FunctionEventType_DELETE FunctionEventType = 1 +) + +type Function struct { + Name string + Package string + Module string + Config map[string]string +} + +type FunctionEvent struct { + Type FunctionEventType + + // Payload: + Function *Function + FunctionName *string +} + +type FSRPCClient interface { + Read(ctx *RPCContext) (<-chan api.Event[[]byte], error) + OnReceiveEvent(ctx *RPCContext, cb func(api.Event[[]byte]) error) error + Write(ctx *RPCContext, e api.Event[[]byte]) error + Commit(ctx *RPCContext, id string) error + OnEvent(ctx context.Context, serviceId string, modules []string) (<-chan *FunctionEvent, error) + RegisterSchema(ctx context.Context, schema string) error + GetState(ctx *RPCContext, key string) ([]byte, error) + PutState(ctx *RPCContext, key string, value []byte) error + ListStates(ctx *RPCContext, path string) ([]string, error) +} diff --git a/clients/gofs/rpc/socket.go b/clients/gofs/rpc/socket.go new file mode 100644 index 00000000..ee100052 --- /dev/null +++ b/clients/gofs/rpc/socket.go @@ -0,0 +1,174 @@ +package rpc + +import ( + "context" + "fmt" + "github.com/functionstream/function-stream/clients/gofs/api" + proto "github.com/functionstream/function-stream/fs/runtime/external/proto" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "io" + "os" +) + +const ( + FSTarget = "FS_TARGET" + DefaultModule = "default" +) + +type grpcRPCClient struct { + runtimeCli proto.RuntimeServiceClient + funcCli proto.FunctionServiceClient +} + +func NewRPCClient() (FSRPCClient, error) { + target := os.Getenv(FSTarget) // TODO: Support TCP port + if target == "" { + return nil, fmt.Errorf("%s is not set", FSTarget) + } + + serviceConfig := `{ + "methodConfig": [{ + "name": [{"service": "*"}], + "retryPolicy": { + "maxAttempts": 30, + "initialBackoff": "0.1s", + "maxBackoff": "30s", + "backoffMultiplier": 2, + "retryableStatusCodes": ["UNAVAILABLE"] + } + }] + }` + conn, err := grpc.NewClient( + target, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithDefaultServiceConfig(serviceConfig), + ) + if err != nil { + return nil, err + } + return &grpcRPCClient{ + runtimeCli: proto.NewRuntimeServiceClient(conn), + funcCli: proto.NewFunctionServiceClient(conn), + }, nil +} + +func (g *grpcRPCClient) OnReceiveEvent(ctx *RPCContext, cb func(api.Event[[]byte]) error) error { + ch, err := g.Read(ctx) + if err != nil { + return err + } + for e := range ch { + if err := cb(e); err != nil { + return err + } + } + return nil +} + +func (g *grpcRPCClient) Read(ctx *RPCContext) (<-chan api.Event[[]byte], error) { + c, err := g.funcCli.Read(ctx, &proto.ReadRequest{ + Context: &proto.Context{ + FunctionName: ctx.FunctionName, + }, + }) + if err != nil { + return nil, err + } + ch := make(chan api.Event[[]byte]) + go func() { + defer close(ch) + for { + e, err := c.Recv() + if err == io.EOF { + return + } + if err != nil { + return + } + ch <- api.NewEventWithCommit(&e.Payload, func(ctx2 context.Context) error { + return g.Commit(ctx.warp(ctx2), e.Id) + }) + } + }() + return ch, nil +} + +func (g *grpcRPCClient) Write(ctx *RPCContext, e api.Event[[]byte]) error { + _, err := g.funcCli.Write(ctx, &proto.WriteRequest{ + Context: &proto.Context{ + FunctionName: ctx.FunctionName, + }, + Payload: *e.Data(), + }) + return err +} + +func (g *grpcRPCClient) Commit(ctx *RPCContext, id string) error { + _, err := g.funcCli.Commit(ctx, &proto.CommitRequest{ + Context: &proto.Context{ + FunctionName: ctx.FunctionName, + }, + EventId: id, + }) + return err +} + +func (g *grpcRPCClient) OnEvent(ctx context.Context, serviceId string, modules []string) (<-chan *FunctionEvent, error) { + c, err := g.runtimeCli.OnEvent(ctx, &proto.OnDeployRequest{ + ServiceId: serviceId, + Modules: modules, + }) + if err != nil { + return nil, err + } + ch := make(chan *FunctionEvent) + go func() { + defer close(ch) + for { + e, err := c.Recv() + if err == io.EOF { + return + } + if err != nil { + return + } + fe := &FunctionEvent{} + switch e.GetType() { + case proto.FunctionEventType_DEPLOY: + fe.Type = FunctionEventType_DEPLOY + f := e.GetPayload().(*proto.FunctionEvent_Function).Function + fe.Function = &Function{ + Name: f.Name, + Package: f.Package, + Module: f.Module, + Config: f.Config, + } + case proto.FunctionEventType_DELETE: + fe.Type = FunctionEventType_DELETE + fe.FunctionName = &e.GetPayload().(*proto.FunctionEvent_FunctionName).FunctionName + } + ch <- fe + } + }() + return ch, nil +} + +func (g *grpcRPCClient) RegisterSchema(ctx context.Context, schema string) error { + return nil +} + +func (g *grpcRPCClient) GetState(ctx *RPCContext, key string) ([]byte, error) { + //TODO implement me + panic("implement me") +} + +func (g *grpcRPCClient) PutState(ctx *RPCContext, key string, value []byte) error { + //TODO implement me + panic("implement me") +} + +func (g *grpcRPCClient) ListStates(ctx *RPCContext, path string) ([]string, error) { + //TODO implement me + panic("implement me") +} diff --git a/clients/gofs/api.go b/clients/gofsold/api.go similarity index 99% rename from clients/gofs/api.go rename to clients/gofsold/api.go index 6ced2b8b..5d0c5856 100644 --- a/clients/gofs/api.go +++ b/clients/gofsold/api.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package gofs +package gofsold import "context" diff --git a/clients/gofs/gofs.go b/clients/gofsold/gofs.go similarity index 99% rename from clients/gofs/gofs.go rename to clients/gofsold/gofs.go index b17279d7..3d4eff53 100644 --- a/clients/gofs/gofs.go +++ b/clients/gofsold/gofs.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package gofs +package gofsold import ( "context" diff --git a/clients/gofs/gofs_socket.go b/clients/gofsold/gofs_socket.go similarity index 97% rename from clients/gofs/gofs_socket.go rename to clients/gofsold/gofs_socket.go index a474e6a2..1c413994 100644 --- a/clients/gofs/gofs_socket.go +++ b/clients/gofsold/gofs_socket.go @@ -17,7 +17,7 @@ * limitations under the License. */ -package gofs +package gofsold import ( "context" @@ -27,7 +27,7 @@ import ( "google.golang.org/grpc/metadata" - "github.com/functionstream/function-stream/fs/runtime/external/model" + "github.com/functionstream/function-stream/fsold/runtime/external/model" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) diff --git a/clients/gofs/gofs_wasmfs.go b/clients/gofsold/gofs_wasmfs.go similarity index 99% rename from clients/gofs/gofs_wasmfs.go rename to clients/gofsold/gofs_wasmfs.go index 7404b8a9..0361430a 100644 --- a/clients/gofs/gofs_wasmfs.go +++ b/clients/gofsold/gofs_wasmfs.go @@ -17,7 +17,7 @@ * limitations under the License. */ -package gofs +package gofsold import "C" import ( diff --git a/cmd/client/apply.go b/cmd/client/apply.go new file mode 100644 index 00000000..dc433bc6 --- /dev/null +++ b/cmd/client/apply.go @@ -0,0 +1,50 @@ +package client + +import ( + "fmt" + "github.com/functionstream/function-stream/cmd/client/common" + "github.com/spf13/cobra" + "os" +) + +func NewApplyCmd() *cobra.Command { + filePath := "" + cmd := &cobra.Command{ + Use: "apply", + Short: "apply resources", + RunE: func(cmd *cobra.Command, args []string) error { + if filePath == "" { + return fmt.Errorf("file is required") + } + data, err := os.ReadFile(filePath) + if err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + cmd.SilenceUsage = true + resources, err := DecodeResource(data) + if err != nil { + return fmt.Errorf("failed to decode resources: %w", err) + } + + // TODO: support dry run + + apiCLi := common.GetApiClient() + + hasErr := false + for _, r := range resources { + if err := apiCLi.GenericService(r.Kind).Upsert(cmd.Context(), &r.Spec); err != nil { + fmt.Printf("Failed to apply resource %s: %v\n", r.Metadata.Name, err) + hasErr = true + continue + } + fmt.Printf("Resource %s/%s applied\n", r.Kind, r.Metadata.Name) + } + if hasErr { + return fmt.Errorf("some resources failed to apply") + } + return nil + }, + } + cmd.Flags().StringVarP(&filePath, "file", "f", "", "The path to the resources") + return cmd +} diff --git a/cmd/client/cmd.go b/cmd/client/cmd.go index 222b09b4..ec7ed16a 100644 --- a/cmd/client/cmd.go +++ b/cmd/client/cmd.go @@ -18,12 +18,6 @@ package client import ( c "github.com/functionstream/function-stream/cmd/client/common" - "github.com/functionstream/function-stream/cmd/client/consume" - "github.com/functionstream/function-stream/cmd/client/create" - del "github.com/functionstream/function-stream/cmd/client/delete" - "github.com/functionstream/function-stream/cmd/client/list" - "github.com/functionstream/function-stream/cmd/client/produce" - "github.com/functionstream/function-stream/cmd/client/reload" "github.com/spf13/cobra" ) @@ -35,14 +29,18 @@ var ( } ) -func init() { - Cmd.PersistentFlags().StringVarP(&c.Config.ServiceAddr, "service-address", "s", - "http://localhost:7300", "Service address") - - Cmd.AddCommand(create.Cmd) - Cmd.AddCommand(list.Cmd) - Cmd.AddCommand(del.Cmd) - Cmd.AddCommand(produce.Cmd) - Cmd.AddCommand(consume.Cmd) - Cmd.AddCommand(reload.Cmd) +func NewClientCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "client", + Short: "Function Stream Client Tool", + Long: `Operations to manage functions in a function stream server`, + } + cmd.PersistentFlags().StringVarP(&c.Config.ServiceAddr, "service-address", "s", + "localhost:7300", "Service address") + cmd.AddCommand(NewCreateCmd()) + cmd.AddCommand(NewGetCommand()) + cmd.AddCommand(NewListCommand()) + cmd.AddCommand(NewDeleteCommand()) + cmd.AddCommand(NewApplyCmd()) + return cmd } diff --git a/cmd/client/common/config.go b/cmd/client/common/config.go index 5488fd32..33090b53 100644 --- a/cmd/client/common/config.go +++ b/cmd/client/common/config.go @@ -16,8 +16,17 @@ package common +import "github.com/functionstream/function-stream/apiclient" + type ClientConfig struct { ServiceAddr string } var Config ClientConfig + +func GetApiClient() *apiclient.APIClient { + apiCliCfg := apiclient.NewConfiguration() + apiCliCfg.Debug = true // TODO: support configuring debug + apiCliCfg.Host = Config.ServiceAddr + return apiclient.NewAPIClient(apiCliCfg) +} diff --git a/cmd/client/create.go b/cmd/client/create.go new file mode 100644 index 00000000..ef25f1e0 --- /dev/null +++ b/cmd/client/create.go @@ -0,0 +1,50 @@ +package client + +import ( + "fmt" + "github.com/functionstream/function-stream/cmd/client/common" + "github.com/spf13/cobra" + "os" +) + +func NewCreateCmd() *cobra.Command { + filePath := "" + cmd := &cobra.Command{ + Use: "create", + Short: "Create resources", + RunE: func(cmd *cobra.Command, args []string) error { + if filePath == "" { + return fmt.Errorf("file is required") + } + data, err := os.ReadFile(filePath) + if err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + cmd.SilenceUsage = true + resources, err := DecodeResource(data) + if err != nil { + return fmt.Errorf("failed to decode resources: %w", err) + } + + // TODO: support dry run + + apiCLi := common.GetApiClient() + + hasErr := false + for _, r := range resources { + if err := apiCLi.GenericService(r.Kind).Create(cmd.Context(), &r.Spec); err != nil { + fmt.Printf("Failed to create resource %s: %v\n", r.Metadata.Name, err) + hasErr = true + continue + } + fmt.Printf("Resource %s/%s created\n", r.Kind, r.Metadata.Name) + } + if hasErr { + return fmt.Errorf("some resources failed to create") + } + return nil + }, + } + cmd.Flags().StringVarP(&filePath, "file", "f", "", "The path to the resources") + return cmd +} diff --git a/cmd/client/delete.go b/cmd/client/delete.go new file mode 100644 index 00000000..39a33d22 --- /dev/null +++ b/cmd/client/delete.go @@ -0,0 +1,28 @@ +package client + +import ( + "fmt" + "github.com/functionstream/function-stream/cmd/client/common" + "github.com/spf13/cobra" +) + +func NewDeleteCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "delete", + Short: "delete resources", + RunE: func(cmd *cobra.Command, args []string) error { + res := args[0] + name := args[1] + apiCLi := common.GetApiClient() + + err := apiCLi.GenericService(res).Delete(cmd.Context(), name) + if err != nil { + return err + } + fmt.Printf("Resource %s/%s deleted\n", res, name) + return nil + }, + } + cmd.Args = cobra.ExactArgs(2) + return cmd +} diff --git a/cmd/client/get.go b/cmd/client/get.go new file mode 100644 index 00000000..a9a2b07d --- /dev/null +++ b/cmd/client/get.go @@ -0,0 +1,35 @@ +package client + +import ( + "encoding/json" + "fmt" + "github.com/functionstream/function-stream/cmd/client/common" + "github.com/spf13/cobra" +) + +func NewGetCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "get", + Short: "get resources", + RunE: func(cmd *cobra.Command, args []string) error { + res := args[0] + name := args[1] + apiCLi := common.GetApiClient() + + t, err := apiCLi.GenericService(res).Read(cmd.Context(), name) + if err != nil { + return err + } + + data, err := json.MarshalIndent(t, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal data: %v", err) + } + + fmt.Println(string(data)) + return nil + }, + } + cmd.Args = cobra.ExactArgs(2) + return cmd +} diff --git a/cmd/client/list.go b/cmd/client/list.go new file mode 100644 index 00000000..4012e259 --- /dev/null +++ b/cmd/client/list.go @@ -0,0 +1,34 @@ +package client + +import ( + "encoding/json" + "fmt" + "github.com/functionstream/function-stream/cmd/client/common" + "github.com/spf13/cobra" +) + +func NewListCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "list resources", + RunE: func(cmd *cobra.Command, args []string) error { + res := args[0] + apiCLi := common.GetApiClient() + + resources, err := apiCLi.GenericService(res).List(cmd.Context()) + if err != nil { + return err + } + + data, err := json.MarshalIndent(resources, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal data: %v", err) + } + + fmt.Println(string(data)) + return nil + }, + } + cmd.Args = cobra.ExactArgs(1) + return cmd +} diff --git a/cmd/client/resource.go b/cmd/client/resource.go new file mode 100644 index 00000000..a17ccc78 --- /dev/null +++ b/cmd/client/resource.go @@ -0,0 +1,68 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/functionstream/function-stream/fs/model" + "gopkg.in/yaml.v3" + "strings" +) + +type Metadata struct { + Name string `yaml:"name"` +} + +type Resource struct { + Kind string `yaml:"kind"` + Metadata Metadata `yaml:"metadata"` + Spec map[string]any `yaml:"spec"` +} + +func decodeSpec(spec any, out any) error { + data, err := json.Marshal(spec) + if err != nil { + return err + } + return json.Unmarshal(data, out) +} + +func DecodeResource(data []byte) ([]*Resource, error) { + decoder := yaml.NewDecoder(bytes.NewReader(data)) + resources := make([]*Resource, 0) + + for { + resource := new(Resource) + if err := decoder.Decode(&resource); err != nil { + if err.Error() == "EOF" { + break + } + return nil, fmt.Errorf("Invalid yaml format: %w", err) + } + + resource.Kind = strings.ToLower(resource.Kind) + resource.Spec["name"] = resource.Metadata.Name + + // TODO: Validate the resources + switch resource.Kind { + case "package": + pkg := &model.Package{} + if err := decodeSpec(resource.Spec, &pkg); err != nil { + return nil, fmt.Errorf("Invalid PackageMetadata: %w", err) + } + + pkg.Name = resource.Metadata.Name + resources = append(resources, resource) + case "function": + function := &model.Function{} + if err := decodeSpec(resource.Spec, &function); err != nil { + return nil, fmt.Errorf("Invalid Function: %w", err) + } + function.Name = resource.Metadata.Name + resources = append(resources, resource) + default: + return nil, fmt.Errorf("Invalid resource kind: %s", resource.Kind) + } + } + return resources, nil +} diff --git a/cmd/main.go b/cmd/main.go index 7f56fdfe..7ef706f2 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -18,29 +18,21 @@ package main import ( "fmt" - "os" - "github.com/functionstream/function-stream/cmd/client" - "github.com/functionstream/function-stream/cmd/perf" "github.com/functionstream/function-stream/cmd/server" + "os" + "github.com/spf13/cobra" ) -var ( - rootCmd = &cobra.Command{ +func main() { + rootCmd := &cobra.Command{ Use: "function-stream", - Short: "function-stream root command", - Long: `function-stream root command`, + Short: "Manage Function Stream", } -) - -func init() { - rootCmd.AddCommand(server.Cmd) - rootCmd.AddCommand(client.Cmd) - rootCmd.AddCommand(perf.Cmd) -} - -func main() { + rootCmd.AddCommand(server.NewServerCmd()) + rootCmd.AddCommand(client.NewClientCmd()) + rootCmd.SilenceErrors = true if err := rootCmd.Execute(); err != nil { _, _ = fmt.Fprintln(os.Stderr, err) os.Exit(1) diff --git a/cmd/perf/cmd.go b/cmd/perf/cmd.go index 84c055ea..38548d3f 100644 --- a/cmd/perf/cmd.go +++ b/cmd/perf/cmd.go @@ -18,9 +18,9 @@ package perf import ( "context" + "github.com/functionstream/function-stream/pkg/process" "io" - "github.com/functionstream/function-stream/common" "github.com/functionstream/function-stream/perf" "github.com/spf13/cobra" ) @@ -42,7 +42,7 @@ func init() { } func exec(*cobra.Command, []string) { - common.RunProcess(runPerf) + process.RunProcess(runPerf) } type closer struct { diff --git a/cmd/server/cmd.go b/cmd/server/cmd.go deleted file mode 100644 index d8d69df2..00000000 --- a/cmd/server/cmd.go +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2024 Function Stream Org. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package server - -import ( - "context" - "io" - - "github.com/functionstream/function-stream/common" - "github.com/functionstream/function-stream/server" - "github.com/spf13/cobra" -) - -var ( - Cmd = &cobra.Command{ - Use: "server", - Short: "Start a server", - Long: `Start a server`, - Run: exec, - } -) - -type flags struct { - configFile string - loadConfigFromEnv bool -} - -var ( - config = flags{} -) - -func init() { - Cmd.Flags().StringVarP(&config.configFile, "config-file", "c", "conf/function-stream.yaml", - "path to the config file (default is conf/function-stream.yaml)") - Cmd.Flags().BoolVarP(&config.loadConfigFromEnv, "load-config-from-env", "e", false, - "load config from env (default is false)") -} - -func exec(*cobra.Command, []string) { - common.RunProcess(func() (io.Closer, error) { - var c *server.Config - var err error - if config.loadConfigFromEnv { - c, err = server.LoadConfigFromEnv() - if err != nil { - return nil, err - } - } else { - c, err = server.LoadConfigFromFile(config.configFile) - if err != nil { - return nil, err - } - } - s, err := server.NewServer( - server.WithTubeFactoryBuilders(server.GetBuiltinTubeFactoryBuilder()), - server.WithRuntimeFactoryBuilders(server.GetBuiltinRuntimeFactoryBuilder()), - server.WithConfig(c)) - if err != nil { - return nil, err - } - go s.Run(context.Background()) - return s, nil - }) -} diff --git a/cmd/server/server.go b/cmd/server/server.go new file mode 100644 index 00000000..0dbe72c0 --- /dev/null +++ b/cmd/server/server.go @@ -0,0 +1,71 @@ +/* + * Copyright 2024 Function Stream Org. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package server + +import ( + "context" + "fmt" + "github.com/functionstream/function-stream/pkg/cmdutil" + "github.com/functionstream/function-stream/pkg/process" + "github.com/functionstream/function-stream/server" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "io" + "net/http" +) + +type ServerCloser struct { + cancel context.CancelFunc +} + +func (s *ServerCloser) Close() error { + s.cancel() + return nil +} + +func NewServerCmd() *cobra.Command { + configFile := "" + cmd := &cobra.Command{ + Use: "server", + RunE: func(cmd *cobra.Command, args []string) error { + if configFile == "" { + return fmt.Errorf("config file is required") + } + process.RunProcess(func() (io.Closer, error) { + c, err := server.LoadConfigFromFile(configFile) + cmdutil.CheckErr(err) + svr, err := server.NewServer(cmd.Context(), c, &server.ServerOption{ + Logger: cmdutil.GetLogger(), + }) + cmdutil.CheckErr(err) + svrCtx, cancel := context.WithCancel(cmd.Context()) + err = svr.Run(svrCtx) + if err != nil && !errors.Is(err, http.ErrServerClosed) { + cmdutil.CheckErr(err) + } + // TODO: Add health check + return &ServerCloser{ + cancel: cancel, + }, nil + }) + return nil + }, + } + cmd.Flags().StringVarP(&configFile, "config-file", "c", "conf/function-stream.yaml", + "path to the config file (default is conf/function-stream.yaml)") + return cmd +} diff --git a/common/model/function.go b/common/model/function.go index 95d8d019..67aa5af1 100644 --- a/common/model/function.go +++ b/common/model/function.go @@ -21,7 +21,7 @@ import ( "github.com/functionstream/function-stream/common/config" - "github.com/functionstream/function-stream/fs/contube" + "github.com/functionstream/function-stream/fsold/contube" "github.com/pkg/errors" ) diff --git a/conf/function-stream.yaml b/conf/function-stream.yaml index 77a073c8..8fa3dd38 100644 --- a/conf/function-stream.yaml +++ b/conf/function-stream.yaml @@ -12,15 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -listen_addr: ":7300" -queue: - type: "pulsar" - config: - pulsar_url: "pulsar://localhost:6650" -tube-config: - pulsar: - pulsar_url: "pulsar://localhost:6650" -runtime-config: - external: - socket-path: /tmp/fs.sock -function-store: ./functions \ No newline at end of file +listen-address: ":7300" +package-loader: + type: memory +event-storage: + type: memory +state-store: + type: memory +runtimes: + - type: external + config: + address: ":7400" \ No newline at end of file diff --git a/examples/basic/main.go b/examples/basic/main.go index 38755536..f22d0392 100644 --- a/examples/basic/main.go +++ b/examples/basic/main.go @@ -17,14 +17,14 @@ package main import ( - "github.com/functionstream/function-stream/clients/gofs" + "github.com/functionstream/function-stream/clients/gofsold" "log/slog" ) func main() { slog.Info("Hello from Go function!") - err := gofs.NewFSClient(). - Register(gofs.DefaultModule, gofs.WithFunction(gofs.NewSimpleFunction(myProcess))). + err := gofsold.NewFSClient(). + Register(gofsold.DefaultModule, gofsold.WithFunction(gofsold.NewSimpleFunction(myProcess))). Run() if err != nil { slog.Error(err.Error()) @@ -37,8 +37,8 @@ type Person struct { Expected int `json:"expected"` } -func myProcess(ctx gofs.FunctionContext, e gofs.Event[Person]) (gofs.Event[Person], error) { +func myProcess(ctx gofsold.FunctionContext, e gofsold.Event[Person]) (gofsold.Event[Person], error) { person := e.Data() person.Money += 1 - return gofs.NewEvent(person), nil + return gofsold.NewEvent(person), nil } diff --git a/examples/example.yaml b/examples/example.yaml new file mode 100644 index 00000000..49ea71a9 --- /dev/null +++ b/examples/example.yaml @@ -0,0 +1,5 @@ +kind: Package +metadata: + name: test-pkg +spec: + type: "external" diff --git a/fs/api/api.go b/fs/api/api.go new file mode 100644 index 00000000..434b1c4a --- /dev/null +++ b/fs/api/api.go @@ -0,0 +1,57 @@ +package api + +import ( + "context" + "github.com/functionstream/function-stream/fs/model" + "io" +) + +type Event interface { + ID() string + SchemaID() int64 + Payload() io.Reader + Properties() map[string]string + Commit(ctx context.Context) error +} + +type Manager interface { + ResourceProvider[model.Function] +} + +type PackageStorage interface { + ResourceProvider[model.Package] +} + +type ResourceProvider[T any] interface { + Create(ctx context.Context, r *T) error + Read(ctx context.Context, name string) (*T, error) + Upsert(ctx context.Context, r *T) error + Delete(ctx context.Context, name string) error + List(ctx context.Context) ([]*T, error) +} + +type EventStorage interface { + Read(ctx context.Context, topics []model.TopicConfig) (<-chan Event, error) + Write(ctx context.Context, event Event, topic model.TopicConfig) error + Commit(ctx context.Context, eventId string) error +} + +type Instance interface { + Context() context.Context + Function() *model.Function + Package() *model.Package + EventStorage() EventStorage + StateStore() StateStore +} + +type RuntimeAdapter interface { + DeployFunction(ctx context.Context, instance Instance) error + DeleteFunction(ctx context.Context, name string) error +} + +type StateStore interface { + Get(ctx context.Context, key string) ([]byte, error) + Put(ctx context.Context, key string, value []byte) error + List(ctx context.Context, startInclusive string, endExclusive string) ([]string, error) + Delete(ctx context.Context, key string) error +} diff --git a/fs/api/errors.go b/fs/api/errors.go new file mode 100644 index 00000000..3f9fc8b1 --- /dev/null +++ b/fs/api/errors.go @@ -0,0 +1,8 @@ +package api + +import "fmt" + +var ErrStateNotFound = fmt.Errorf("state not found") + +var ErrResourceAlreadyExists = fmt.Errorf("resource already exists") +var ErrResourceNotFound = fmt.Errorf("resource not found") diff --git a/fs/event/event.go b/fs/event/event.go new file mode 100644 index 00000000..d9ba2ca3 --- /dev/null +++ b/fs/event/event.go @@ -0,0 +1,56 @@ +package event + +import ( + "bytes" + "context" + "encoding/json" + "github.com/functionstream/function-stream/fs/api" + "io" +) + +type RawEvent struct { + id string + payload io.Reader + commitCh chan struct{} +} + +func NewRawEvent(id string, payload io.Reader) *RawEvent { + return &RawEvent{ + id: id, + payload: payload, + commitCh: make(chan struct{}, 2), // Don't block the sender + } +} + +func (e *RawEvent) ID() string { + return e.id +} + +func (e *RawEvent) SchemaID() int64 { + return -1 +} + +func (e *RawEvent) Payload() io.Reader { + return e.payload +} + +func (e *RawEvent) Properties() map[string]string { + return nil +} + +func (e *RawEvent) Commit(ctx context.Context) error { + e.commitCh <- struct{}{} + return nil +} + +func (e *RawEvent) OnCommit() <-chan struct{} { + return e.commitCh +} + +func NewStructEvent(id string, payload any) (api.Event, error) { + data, err := json.Marshal(payload) + if err != nil { + return nil, err + } + return NewRawEvent(id, bytes.NewReader(data)), nil +} diff --git a/fs/eventstorage/memory/memeventstorage.go b/fs/eventstorage/memory/memeventstorage.go new file mode 100644 index 00000000..b7d71afc --- /dev/null +++ b/fs/eventstorage/memory/memeventstorage.go @@ -0,0 +1,109 @@ +package memory + +import ( + "context" + "github.com/functionstream/function-stream/fs/api" + "github.com/functionstream/function-stream/fs/model" + "go.uber.org/zap" +) + +type MemES struct { + eventLoop chan op + readCh map[string]chan api.Event + + log *zap.Logger +} + +type op struct { + registerRead *struct { + topic string + c chan api.Event + } + unregisterRead *struct { + topic string + } + write *struct { + topic string + event api.Event + } +} + +func (m *MemES) Read(ctx context.Context, topics []model.TopicConfig) (<-chan api.Event, error) { + m.log.Debug("Reading events", zap.Any("topics", topics)) + c := make(chan api.Event, 10) + for _, t := range topics { + m.eventLoop <- op{ + registerRead: &struct { + topic string + c chan api.Event + }{topic: t.Name, c: c}, + } + } + go func() { + <-ctx.Done() + for _, t := range topics { + m.eventLoop <- op{ + unregisterRead: &struct { + topic string + }{topic: t.Name}, + } + } + }() + return c, nil +} + +func (m *MemES) Write(ctx context.Context, event api.Event, topic model.TopicConfig) error { + m.log.Debug("Writing event", zap.Any("eventId", event.ID()), zap.Any("topic", topic.Name)) + select { + case <-ctx.Done(): + return ctx.Err() + case m.eventLoop <- op{ + write: &struct { + topic string + event api.Event + }{topic: topic.Name, event: event}, + }: + } + return nil +} + +func (m *MemES) Commit(_ context.Context, eventId string) error { + m.log.Debug("Committing event", zap.Any("eventId", eventId)) + return nil +} + +func NewMemEs(ctx context.Context, log *zap.Logger) api.EventStorage { + m := &MemES{ + eventLoop: make(chan op, 1000), + readCh: make(map[string]chan api.Event), + log: log, + } + go func() { + for { + select { + case <-ctx.Done(): + for _, c := range m.readCh { + close(c) + } + return + case op := <-m.eventLoop: + if op.registerRead != nil { + m.readCh[op.registerRead.topic] = op.registerRead.c + } + if op.unregisterRead != nil { + c := m.readCh[op.unregisterRead.topic] + delete(m.readCh, op.unregisterRead.topic) + close(c) + } + if op.write != nil { + c := m.readCh[op.write.topic] + if c != nil { + c <- op.write.event + } + _ = op.write.event.Commit(ctx) + } + } + } + }() + return m +} diff --git a/fs/eventstorage/memory/memeventstorage_test.go b/fs/eventstorage/memory/memeventstorage_test.go new file mode 100644 index 00000000..a02bbf47 --- /dev/null +++ b/fs/eventstorage/memory/memeventstorage_test.go @@ -0,0 +1,75 @@ +package memory + +import ( + "bytes" + "context" + "fmt" + "github.com/functionstream/function-stream/fs/event" + "github.com/functionstream/function-stream/fs/model" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "golang.org/x/exp/maps" + "io" + "strconv" + "strings" + "testing" +) + +func TestMemoryEventStorage(t *testing.T) { + config := zap.NewDevelopmentConfig() + config.Level = zap.NewAtomicLevelAt(zap.DebugLevel) + log, err := config.Build() + + ctx, cancel := context.WithCancel(context.Background()) + es := NewMemEs(ctx, log) + + topics := []model.TopicConfig{ + { + Name: "topic1", + }, + { + Name: "topic2", + }, + { + Name: "topic3", + }, + } + source, err := es.Read(ctx, topics) + require.NoError(t, err) + if err != nil { + t.Fatal(err) + } + + numMsgsPerTopic := 10 + totalMsgs := numMsgsPerTopic * len(topics) + + go func() { + for _, v := range topics { + for i := 0; i < numMsgsPerTopic; i++ { + require.NoError(t, es.Write(ctx, event.NewRawEvent(strconv.Itoa(i), bytes.NewReader([]byte(fmt.Sprintf("%s-%d", v.Name, i)))), v)) + } + } + }() + events := make(map[string]int) + + recvMsgs := 0 + + for e := range source { + data, err := io.ReadAll(e.Payload()) + require.NoError(t, err) + str := string(data) + parts := strings.Split(str, "-") + l := events[parts[0]] + i, err := strconv.Atoi(parts[1]) + require.NoError(t, err) + require.Equal(t, l, i) + events[parts[0]]++ + recvMsgs++ + if recvMsgs == totalMsgs { + break + } + } + + require.ElementsMatch(t, maps.Keys(events), []string{"topic1", "topic2", "topic3"}) + cancel() +} diff --git a/fs/manager.go b/fs/manager.go index 4a7bc956..968a9a92 100644 --- a/fs/manager.go +++ b/fs/manager.go @@ -1,366 +1,206 @@ -/* - * Copyright 2024 Function Stream Org. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package fs import ( "context" "fmt" - "math/rand" - "strconv" - "sync" - - "github.com/functionstream/function-stream/fs/statestore" - - "github.com/functionstream/function-stream/common/config" - _package "github.com/functionstream/function-stream/fs/package" - - "github.com/functionstream/function-stream/common" - "github.com/functionstream/function-stream/common/model" "github.com/functionstream/function-stream/fs/api" - "github.com/functionstream/function-stream/fs/contube" - "github.com/go-logr/logr" - "github.com/pkg/errors" + "github.com/functionstream/function-stream/fs/model" + "github.com/go-playground/validator/v10" + "go.uber.org/zap" + "regexp" + "sync" ) -type FunctionManager interface { - StartFunction(f *model.Function) error - DeleteFunction(namespace, name string) error - ListFunctions() []string - ProduceEvent(name string, event contube.Record) error - ConsumeEvent(name string) (contube.Record, error) - GetStateStore() (api.StateStore, error) - Close() error +type ManagerImpl struct { + runtimeMap map[string]api.RuntimeAdapter + mu sync.Mutex + instanceMap map[string]api.Instance + pkgLoader api.PackageStorage + es api.EventStorage + stateStore api.StateStore + validate *validator.Validate + log *zap.Logger } -type functionManagerImpl struct { - options *managerOptions - functions map[common.NamespacedName][]api.FunctionInstance //TODO: Use sync.map - functionsLock sync.Mutex - log *common.Logger +func (m *ManagerImpl) Create(ctx context.Context, r *model.Function) error { + if err := m.Deploy(ctx, r); err != nil { + return err + } + m.log.Info("created function", zap.String("name", r.Name)) + return nil } -type managerOptions struct { - tubeFactoryMap map[string]contube.TubeFactory - runtimeFactoryMap map[string]api.FunctionRuntimeFactory - instanceFactory api.FunctionInstanceFactory - stateStoreFactory api.StateStoreFactory - queueFactory contube.TubeFactory - packageLoader api.PackageLoader // TODO: Need to set it - log *logr.Logger +func (m *ManagerImpl) Read(ctx context.Context, name string) (*model.Function, error) { + m.mu.Lock() + defer m.mu.Unlock() + ins, ok := m.instanceMap[name] + if !ok { + return nil, api.ErrResourceNotFound + } + return ins.Function(), nil } -type ManagerOption interface { - apply(option *managerOptions) (*managerOptions, error) +func (m *ManagerImpl) Upsert(ctx context.Context, r *model.Function) error { + m.mu.Lock() + defer m.mu.Unlock() + _, ok := m.instanceMap[r.Name] + if ok { + if err := m.Delete(ctx, r.Name); err != nil { + return fmt.Errorf("failed to restart function %s: %w", r.Name, err) + } + } + m.log.Info("updated function", zap.String("name", r.Name)) + return m.Deploy(ctx, r) } -type managerOptionFunc func(*managerOptions) (*managerOptions, error) - -func (f managerOptionFunc) apply(c *managerOptions) (*managerOptions, error) { - return f(c) +func (m *ManagerImpl) List(ctx context.Context) ([]*model.Function, error) { + m.mu.Lock() + defer m.mu.Unlock() + var functions []*model.Function + for _, ins := range m.instanceMap { + functions = append(functions, ins.Function()) + } + return functions, nil } -func WithTubeFactory(name string, factory contube.TubeFactory) ManagerOption { - return managerOptionFunc(func(c *managerOptions) (*managerOptions, error) { - c.tubeFactoryMap[name] = factory - return c, nil - }) +type ManagerConfig struct { + RuntimeMap map[string]api.RuntimeAdapter + PackageLoader api.PackageStorage + EventStorage api.EventStorage + StateStore api.StateStore } -func WithQueueFactory(factory contube.TubeFactory) ManagerOption { - return managerOptionFunc(func(c *managerOptions) (*managerOptions, error) { - c.queueFactory = factory - return c, nil + +func NewManager(cfg ManagerConfig, log *zap.Logger) (api.Manager, error) { + validate := validator.New() + err := validate.RegisterValidation("alphanumdash", func(fl validator.FieldLevel) bool { + value := fl.Field().String() + // Allow alphanumeric, dash, dot, asterisk, and slash + matched, _ := regexp.MatchString(`^[a-zA-Z0-9\-.*/]+$`, value) + return matched }) + if err != nil { + return nil, err + } + return &ManagerImpl{ + runtimeMap: cfg.RuntimeMap, + pkgLoader: cfg.PackageLoader, + es: cfg.EventStorage, + stateStore: cfg.StateStore, + instanceMap: make(map[string]api.Instance), + validate: validate, + log: log, + }, nil } -func WithRuntimeFactory(name string, factory api.FunctionRuntimeFactory) ManagerOption { - return managerOptionFunc(func(c *managerOptions) (*managerOptions, error) { - c.runtimeFactoryMap[name] = factory - return c, nil - }) +func (m *ManagerImpl) validateFunctionModel(f *model.Function) error { + if err := m.validate.Struct(f); err != nil { + var errMessages []string + for _, err := range err.(validator.ValidationErrors) { + errMessages = append(errMessages, fmt.Sprintf("%s: %s", err.Field(), err.Tag())) + } + return fmt.Errorf("validation errors: %s", errMessages) + } + return nil } -func WithInstanceFactory(factory api.FunctionInstanceFactory) ManagerOption { - return managerOptionFunc(func(c *managerOptions) (*managerOptions, error) { - c.instanceFactory = factory - return c, nil - }) +func validateFunctionPackage(f *model.Function, p *model.Package) error { + if p.Name != f.Package { + return fmt.Errorf("package name %s does not match function package name %s", p.Name, f.Package) + } + if _, ok := p.Modules[f.Module]; !ok { + return fmt.Errorf("module %s not found in package %s", f.Module, f.Package) + } + return nil } -func WithStateStoreFactory(storeFactory api.StateStoreFactory) ManagerOption { - return managerOptionFunc(func(c *managerOptions) (*managerOptions, error) { - c.stateStoreFactory = storeFactory - return c, nil - }) +type instance struct { + ctx context.Context + f *model.Function + p *model.Package + es api.EventStorage + ss api.StateStore } -func WithLogger(log *logr.Logger) ManagerOption { - return managerOptionFunc(func(c *managerOptions) (*managerOptions, error) { - c.log = log - return c, nil - }) +func (i *instance) EventStorage() api.EventStorage { + return i.es } -func WithPackageLoader(loader api.PackageLoader) ManagerOption { - return managerOptionFunc(func(c *managerOptions) (*managerOptions, error) { - c.packageLoader = loader - return c, nil - }) +func (i *instance) Context() context.Context { + return i.ctx } -func NewFunctionManager(opts ...ManagerOption) (FunctionManager, error) { - options := &managerOptions{ - tubeFactoryMap: make(map[string]contube.TubeFactory), - runtimeFactoryMap: make(map[string]api.FunctionRuntimeFactory), - } - options.instanceFactory = NewDefaultInstanceFactory() - for _, o := range opts { - _, err := o.apply(options) - if err != nil { - return nil, err - } - } - var log *common.Logger - if options.log == nil { - log = common.NewDefaultLogger() - } else { - log = common.NewLogger(options.log) - } - loadedRuntimeFact := make([]string, 0, len(options.runtimeFactoryMap)) - for k := range options.runtimeFactoryMap { - loadedRuntimeFact = append(loadedRuntimeFact, k) - } - loadedTubeFact := make([]string, 0, len(options.tubeFactoryMap)) - for k := range options.tubeFactoryMap { - loadedTubeFact = append(loadedTubeFact, k) - } - if options.packageLoader == nil { - options.packageLoader = _package.NewDefaultPackageLoader() - } - if options.stateStoreFactory == nil { - if fact, err := statestore.NewDefaultPebbleStateStoreFactory(); err != nil { - return nil, err - } else { - options.stateStoreFactory = fact - } - } - log.Info("Function manager created", "runtime-factories", loadedRuntimeFact, - "tube-factories", loadedTubeFact) - return &functionManagerImpl{ - options: options, - functions: make(map[common.NamespacedName][]api.FunctionInstance), - log: log, - }, nil +func (i *instance) Function() *model.Function { + return i.f } -func (fm *functionManagerImpl) getTubeFactory(tubeConfig *model.TubeConfig) (contube.TubeFactory, error) { - factory, exist := fm.options.tubeFactoryMap[tubeConfig.Type] - if !exist { - return nil, fmt.Errorf("failed to get tube factory: %w, type: %s", common.ErrorTubeFactoryNotFound, tubeConfig.Type) - } - return factory, nil +func (i *instance) Package() *model.Package { + return i.p } -func (fm *functionManagerImpl) getRuntimeFactory(t string) (api.FunctionRuntimeFactory, error) { - factory, exist := fm.options.runtimeFactoryMap[t] - if !exist { - return nil, fmt.Errorf("failed to get runtime factory: %w, type: %s", common.ErrorRuntimeFactoryNotFound, t) - } - return factory, nil +func (i *instance) StateStore() api.StateStore { + return i.ss } -func generateRuntimeConfig(ctx context.Context, p api.Package, f *model.Function) (*model.RuntimeConfig, error) { - log := common.GetLogger(ctx) - rc := &model.RuntimeConfig{} - if p == _package.EmptyPackage { - return &f.Runtime, nil - } - supportedRuntimeConf := p.GetSupportedRuntimeConfig() - rcMap := map[string]*model.RuntimeConfig{} - for k, v := range supportedRuntimeConf { - if v.Type == "" { - log.Warn("Package supported runtime type is empty. Ignore it.", "index", k, "package", f.Package) - continue - } - vCopy := v - rcMap[v.Type] = &vCopy - } - if len(rcMap) == 0 { - return nil, common.ErrorPackageNoSupportedRuntime - } - defaultRC := &supportedRuntimeConf[0] - if f.Runtime.Type == "" { - rc.Type = defaultRC.Type - } else { - if r, exist := rcMap[f.Runtime.Type]; exist { - defaultRC = r - } else { - return nil, fmt.Errorf("runtime type '%s' is not supported by package '%s'", f.Runtime.Type, f.Package) - } - rc.Type = f.Runtime.Type +func (m *ManagerImpl) Deploy(ctx context.Context, f *model.Function) error { + m.mu.Lock() + _, ok := m.instanceMap[f.Name] + if ok { + m.mu.Unlock() + return api.ErrResourceAlreadyExists } - rc.Config = config.MergeConfig(defaultRC.Config, f.Runtime.Config) - return rc, nil -} -func (fm *functionManagerImpl) StartFunction(f *model.Function) error { // TODO: Shouldn't use pointer here - if err := f.Validate(); err != nil { + err := m.validateFunctionModel(f) + if err != nil { + m.mu.Unlock() return err } - fm.functionsLock.Lock() - if _, exist := fm.functions[common.GetNamespacedName(f.Namespace, f.Name)]; exist { - fm.functionsLock.Unlock() - return common.ErrorFunctionExists + p, err := m.pkgLoader.Read(ctx, f.Package) + if err != nil { + m.mu.Unlock() + return err } - fm.functions[common.GetNamespacedName(f.Namespace, f.Name)] = make([]api.FunctionInstance, f.Replicas) - fm.functionsLock.Unlock() - - for i := int32(0); i < f.Replicas; i++ { - p, err := fm.options.packageLoader.Load(f.Package) - if err != nil { - return err - } - runtimeConfig, err := generateRuntimeConfig(context.Background(), p, f) - if err != nil { - return fmt.Errorf("failed to generate runtime config: %v", err) - } - store, err := fm.options.stateStoreFactory.NewStateStore(f) - if err != nil { - return fmt.Errorf("failed to create state store: %w", err) - } - - funcCtx := newFuncCtxImpl(f, store) - instanceLogger := fm.log.SubLogger("functionName", f.Name, "instanceIndex", int(i), "runtimeType", runtimeConfig.Type) - instance := fm.options.instanceFactory.NewFunctionInstance(f, funcCtx, i, instanceLogger) - fm.functionsLock.Lock() - fm.functions[common.GetNamespacedName(f.Namespace, f.Name)][i] = instance - fm.functionsLock.Unlock() - runtimeFactory, err := fm.getRuntimeFactory(runtimeConfig.Type) - if err != nil { - return err - } - var sources []<-chan contube.Record - for _, t := range f.Sources { - sourceFactory, err := fm.getTubeFactory(&t) - if err != nil { - return err - } - sourceChan, err := sourceFactory.NewSourceTube(instance.Context(), t.Config) - if err != nil { - return fmt.Errorf("failed to create source event queue: %w", err) - } - sources = append(sources, sourceChan) - } - sinkFactory, err := fm.getTubeFactory(&f.Sink) - if err != nil { - return err - } - sink, err := sinkFactory.NewSinkTube(instance.Context(), f.Sink.Config) - if err != nil { - return fmt.Errorf("failed to create sink event queue: %w", err) - } - funcCtx.setSink(sink) - - runtime, err := runtimeFactory.NewFunctionRuntime(instance, runtimeConfig) - if err != nil { - return fmt.Errorf("failed to create runtime: %w", err) - } - fm.log.Info("Starting function instance", "function", f) - - go instance.Run(runtime, sources, sink) + err = validateFunctionPackage(f, p) + if err != nil { + m.mu.Unlock() + return err } - return nil -} -func (fm *functionManagerImpl) DeleteFunction(namespace, name string) error { - fm.functionsLock.Lock() - defer fm.functionsLock.Unlock() - instances, exist := fm.functions[common.GetNamespacedName(namespace, name)] - if !exist { - return common.ErrorFunctionNotFound - } - delete(fm.functions, common.GetNamespacedName(namespace, name)) - for _, instance := range instances { - instance.Stop() + runtime, ok := m.runtimeMap[p.Type] + if !ok { + return fmt.Errorf("runtime %s not found", p.Type) } - return nil -} -func (fm *functionManagerImpl) ListFunctions() (result []string) { - fm.functionsLock.Lock() - defer fm.functionsLock.Unlock() - result = make([]string, len(fm.functions)) - i := 0 - for k := range fm.functions { - result[i] = k.String() - i++ + ins := &instance{ + ctx: ctx, + f: f, + p: p, + es: m.es, + ss: m.stateStore, } - return + m.instanceMap[f.Name] = ins + m.mu.Unlock() + + return runtime.DeployFunction(ctx, ins) } -func (fm *functionManagerImpl) ProduceEvent(name string, event contube.Record) error { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - factory, ok := fm.options.tubeFactoryMap[common.MemoryTubeType] +func (m *ManagerImpl) Delete(ctx context.Context, name string) error { + m.mu.Lock() + defer m.mu.Unlock() + ins, ok := m.instanceMap[name] if !ok { - return errors.New("memory tube factory not found") + return api.ErrResourceNotFound } - c, err := factory.NewSinkTube(ctx, (&contube.SinkQueueConfig{Topic: name}).ToConfigMap()) - if err != nil { - return err - } - c <- event - return nil -} -func (fm *functionManagerImpl) ConsumeEvent(name string) (contube.Record, error) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - factory, ok := fm.options.tubeFactoryMap[common.MemoryTubeType] + runtime, ok := m.runtimeMap[ins.Package().Type] if !ok { - return nil, errors.New("memory tube factory not found") + return fmt.Errorf("runtime %s not found", ins.Function().Package) } - c, err := factory.NewSourceTube(ctx, (&contube.SourceQueueConfig{ - Topics: []string{name}, SubName: "consume-" + strconv.Itoa(rand.Int())}).ToConfigMap()) - if err != nil { - return nil, err - } - return <-c, nil -} - -// GetStateStore returns the state store used by the function manager -// Return nil if no state store is configured -func (fm *functionManagerImpl) GetStateStore() (api.StateStore, error) { - return fm.options.stateStoreFactory.NewStateStore(nil) -} -func (fm *functionManagerImpl) Close() error { - fm.functionsLock.Lock() - defer fm.functionsLock.Unlock() - log := common.NewDefaultLogger() - for _, instances := range fm.functions { - for _, instance := range instances { - instance.Stop() - } - } - if fm.options.stateStoreFactory != nil { - if err := fm.options.stateStoreFactory.Close(); err != nil { - log.Error(err, "failed to close state store") - } + if err := runtime.DeleteFunction(ctx, name); err != nil { + return err } + + delete(m.instanceMap, name) return nil } diff --git a/fs/manager_test.go b/fs/manager_test.go index 6f0619d6..c42eec3f 100644 --- a/fs/manager_test.go +++ b/fs/manager_test.go @@ -1,97 +1,97 @@ -/* - * Copyright 2024 Function Stream Org. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package fs import ( "context" + "fmt" + "github.com/functionstream/function-stream/fs/api" + "github.com/functionstream/function-stream/fs/model" + memory2 "github.com/functionstream/function-stream/fs/packagestorage/memory" + "github.com/functionstream/function-stream/fs/statestore/memory" + "github.com/functionstream/function-stream/pkg/testutil" + "github.com/stretchr/testify/require" "testing" - - "github.com/functionstream/function-stream/common" - "github.com/functionstream/function-stream/common/model" - "github.com/stretchr/testify/assert" ) -// Mock implementations of the interfaces and structs -type MockPackage struct { - runtimeConfigs []model.RuntimeConfig +type MockRuntimeAdapter struct { + instances map[string]api.Instance } -func (m *MockPackage) GetSupportedRuntimeConfig() []model.RuntimeConfig { - return m.runtimeConfigs +func NewMockRuntimeAdapter() *MockRuntimeAdapter { + return &MockRuntimeAdapter{ + instances: make(map[string]api.Instance), + } } -func TestGenerateRuntimeConfig_EmptySupportedRuntimeConfig(t *testing.T) { - ctx := context.Background() - p := &MockPackage{runtimeConfigs: []model.RuntimeConfig{}} - f := &model.Function{} - - _, err := generateRuntimeConfig(ctx, p, f) - assert.NotNil(t, err) - assert.Equal(t, common.ErrorPackageNoSupportedRuntime, err) +func (m *MockRuntimeAdapter) DeployFunction(ctx context.Context, instance api.Instance) error { + m.instances[instance.Function().Name] = instance + return nil } -func TestGenerateRuntimeConfig_EmptyFunctionRuntimeType(t *testing.T) { - ctx := context.Background() - p := &MockPackage{ - runtimeConfigs: []model.RuntimeConfig{ - {Type: "runtime1", Config: map[string]interface{}{"key1": "value1"}}, - }, +func (m *MockRuntimeAdapter) DeleteFunction(ctx context.Context, name string) error { + if _, ok := m.instances[name]; !ok { + return fmt.Errorf("function %s not found", name) } - f := &model.Function{ - Runtime: model.RuntimeConfig{}, - } - - rc, err := generateRuntimeConfig(ctx, p, f) - assert.Nil(t, err) - assert.Equal(t, "runtime1", rc.Type) - assert.Equal(t, "value1", rc.Config["key1"]) + delete(m.instances, name) + return nil } -func TestGenerateRuntimeConfig_UnsupportedFunctionRuntimeType(t *testing.T) { - ctx := context.Background() - p := &MockPackage{ - runtimeConfigs: []model.RuntimeConfig{ - {Type: "runtime1", Config: map[string]interface{}{"key1": "value1"}}, +func TestManagerImpl(t *testing.T) { + pkgStorage := memory2.NewMemoryPackageStorage(testutil.GetTestLogger(t)) + err := pkgStorage.Create(context.Background(), &model.Package{ + Name: "test-pkg", + Type: "test-runtime", + Modules: map[string]model.ModuleConfig{ + "test-module": { + "test-config": { + Type: "string", + Required: "true", + }, + }, }, - } - f := &model.Function{ - Runtime: model.RuntimeConfig{Type: "unsupported_runtime"}, - } + }) + require.NoError(t, err) - _, err := generateRuntimeConfig(ctx, p, f) - assert.NotNil(t, err) - assert.Equal(t, "runtime type 'unsupported_runtime' is not supported by package ''", err.Error()) -} + mockRuntimeAdapter := NewMockRuntimeAdapter() -func TestGenerateRuntimeConfig_SupportedFunctionRuntimeType(t *testing.T) { - ctx := context.Background() - p := &MockPackage{ - runtimeConfigs: []model.RuntimeConfig{ - {Type: "runtime1", Config: map[string]interface{}{"key1": "value1"}}, - {Type: "runtime2", Config: map[string]interface{}{"key2": "value2"}}, + m, err := NewManager(ManagerConfig{ + RuntimeMap: map[string]api.RuntimeAdapter{ + "test-runtime": mockRuntimeAdapter, }, - } + PackageLoader: pkgStorage, + StateStore: memory.NewMemoryStateStore(), + }, testutil.GetTestLogger(t)) + + require.NoError(t, err) + f := &model.Function{ - Runtime: model.RuntimeConfig{Type: "runtime2", Config: map[string]interface{}{"key3": "value3"}}, + Name: "test-func", + Package: "test-pkg", + Module: "test-module", + Config: map[string]string{ + "test-config": "test-value", + }, } - rc, err := generateRuntimeConfig(ctx, p, f) - assert.Nil(t, err) - assert.Equal(t, "runtime2", rc.Type) - assert.Equal(t, "value2", rc.Config["key2"]) - assert.Equal(t, "value3", rc.Config["key3"]) + t.Run("Deploy", func(t *testing.T) { + err = m.Deploy(context.Background(), f) + require.NoError(t, err) + ins, ok := mockRuntimeAdapter.instances[f.Name] + require.True(t, ok) + insF := ins.Function() + require.Equal(t, f, insF) + ss := ins.StateStore() + require.IsType(t, &memory.Store{}, ss) + }) + + t.Run("Delete", func(t *testing.T) { + err = m.Delete(context.Background(), f.Name) + require.NoError(t, err) + _, ok := mockRuntimeAdapter.instances[f.Name] + require.False(t, ok) + }) + + t.Run("Deploy with invalid function", func(t *testing.T) { + err = m.Deploy(context.Background(), &model.Function{}) + require.Error(t, err) + }) } diff --git a/fs/model/function.go b/fs/model/function.go new file mode 100644 index 00000000..4e43ddb0 --- /dev/null +++ b/fs/model/function.go @@ -0,0 +1,31 @@ +package model + +type ConfigMap map[string]string +type GenericConfigMap map[string]interface{} + +type TopicConfig struct { + Name string `json:"name"` + Config ConfigMap `json:"config"` +} + +type Function struct { + Name string `json:"name" validate:"required,alphanumdash"` + Package string `json:"package" validate:"required,alphanumdash"` + Module string `json:"module" validate:"required,alphanumdash"` + Sources []TopicConfig `json:"sources"` + Sink TopicConfig `json:"sink"` + Config ConfigMap `json:"config"` +} + +type ModuleConfigItem struct { + Type string `json:"type"` + Required string `json:"required"` +} + +type ModuleConfig map[string]ModuleConfigItem + +type Package struct { + Name string `json:"name"` + Type string `json:"type"` + Modules map[string]ModuleConfig `json:"modules"` +} diff --git a/fs/packagestorage/memory/memorypackagestorage.go b/fs/packagestorage/memory/memorypackagestorage.go new file mode 100644 index 00000000..2e245b79 --- /dev/null +++ b/fs/packagestorage/memory/memorypackagestorage.go @@ -0,0 +1,73 @@ +package memory + +import ( + "context" + "github.com/functionstream/function-stream/fs/api" + "github.com/functionstream/function-stream/fs/model" + "go.uber.org/zap" + "sync" +) + +type MemoryPackageStorage struct { + mu sync.RWMutex + m map[string]*model.Package + log *zap.Logger +} + +func (m *MemoryPackageStorage) Create(_ context.Context, pkg *model.Package) error { + m.mu.Lock() + defer m.mu.Unlock() + if _, ok := m.m[pkg.Name]; ok { + return api.ErrResourceAlreadyExists + } + m.m[pkg.Name] = pkg + m.log.Info("created package", zap.String("name", pkg.Name)) + return nil +} + +func (m *MemoryPackageStorage) Read(ctx context.Context, name string) (*model.Package, error) { + m.mu.RLock() + defer m.mu.RUnlock() + if pkg, ok := m.m[name]; ok { + return pkg, nil + } + return nil, api.ErrResourceNotFound +} + +func (m *MemoryPackageStorage) List(ctx context.Context) ([]*model.Package, error) { + m.mu.RLock() + defer m.mu.RUnlock() + pkgs := make([]*model.Package, 0, len(m.m)) + for _, pkg := range m.m { + pkgs = append(pkgs, pkg) + } + return pkgs, nil +} + +func (m *MemoryPackageStorage) Upsert(ctx context.Context, pkg *model.Package) error { + m.mu.Lock() + defer m.mu.Unlock() + if _, ok := m.m[pkg.Name]; !ok { + return api.ErrResourceNotFound + } + m.m[pkg.Name] = pkg + m.log.Info("updated package", zap.String("name", pkg.Name)) + return nil +} + +func (m *MemoryPackageStorage) Delete(ctx context.Context, name string) error { + m.mu.Lock() + defer m.mu.Unlock() + if _, ok := m.m[name]; !ok { + return api.ErrResourceNotFound + } + delete(m.m, name) + return nil +} + +func NewMemoryPackageStorage(log *zap.Logger) api.PackageStorage { + return &MemoryPackageStorage{ + m: make(map[string]*model.Package), + log: log, + } +} diff --git a/fs/runtime/external/adapter.go b/fs/runtime/external/adapter.go new file mode 100644 index 00000000..c7952fa1 --- /dev/null +++ b/fs/runtime/external/adapter.go @@ -0,0 +1,254 @@ +package external + +import ( + "bytes" + "context" + "fmt" + "github.com/functionstream/function-stream/fs/api" + "github.com/functionstream/function-stream/fs/event" + proto "github.com/functionstream/function-stream/fs/runtime/external/proto" + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "io" + "net" + "sync" +) + +//go:generate protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. proto/fs.proto + +type Config struct { + Listener net.Listener + Logger *zap.Logger +} + +type adapterImpl struct { + proto.RuntimeServiceServer + proto.FunctionServiceServer + log *zap.Logger + + instances map[string]api.Instance + + servicesMu sync.Mutex + services map[string]service // module -> service +} + +type service struct { + eventCh chan *proto.FunctionEvent +} + +func (a *adapterImpl) OnEvent(request *proto.OnDeployRequest, g grpc.ServerStreamingServer[proto.FunctionEvent]) error { + svc := service{ + eventCh: make(chan *proto.FunctionEvent), + } + log := a.log.Named(request.ServiceId) + log.Info("Setting up function service") + a.servicesMu.Lock() + for _, m := range request.Modules { + if _, exist := a.services[m]; exist { + log.Error("module already exists", zap.String("module", m)) + return status.Error(codes.AlreadyExists, fmt.Sprintf("module %s already exists", m)) + } + } + for _, m := range request.Modules { + a.services[m] = svc + } + a.servicesMu.Unlock() + log.Info("Function service setup complete") + for { + select { + case <-g.Context().Done(): + a.servicesMu.Lock() + for _, m := range request.Modules { + delete(a.services, m) + } + a.servicesMu.Unlock() + close(svc.eventCh) + log.Info("Function service stopped") + return nil + case e := <-svc.eventCh: + log.Info("Sending function event", zap.String("type", e.Type.String())) + err := g.Send(e) + if err != nil { + log.Error("failed to send function event", zap.Error(err)) + return status.Error(codes.Internal, "failed to send function event") + } + } + } +} + +func (a *adapterImpl) DeployFunction(ctx context.Context, instance api.Instance) error { + // TODO: Support interceptor to process the package + e := &proto.FunctionEvent{ + Type: proto.FunctionEventType_DEPLOY, + Payload: &proto.FunctionEvent_Function{ + Function: &proto.Function{ + Name: instance.Function().Name, + Package: instance.Function().Package, + Module: instance.Function().Module, + Config: instance.Function().Config, + }, + }, + } + a.log.Info("Deploying function", zap.String("name", instance.Function().Name)) + a.servicesMu.Lock() + a.instances[instance.Function().Name] = instance + svc, ok := a.services[instance.Function().Module] + if !ok { + return fmt.Errorf("module %s not found", instance.Function().Module) + } + a.servicesMu.Unlock() + select { + case <-ctx.Done(): + return ctx.Err() + case svc.eventCh <- e: + return nil + } +} + +func (a *adapterImpl) DeleteFunction(ctx context.Context, name string) error { + e := &proto.FunctionEvent{ + Type: proto.FunctionEventType_DELETE, + Payload: &proto.FunctionEvent_FunctionName{ + FunctionName: name, + }, + } + a.log.Info("Deleting function", zap.String("name", name)) + for _, svc := range a.services { + select { + case <-ctx.Done(): + return ctx.Err() + case svc.eventCh <- e: + } + } + return nil +} + +func (a *adapterImpl) getInstance(name string) (api.Instance, error) { + ins, ok := a.instances[name] + if !ok { + msg := fmt.Sprintf("function runtime %s not found", name) + return nil, status.Error(codes.Unavailable, msg) + } + return ins, nil +} + +func (a *adapterImpl) Read(request *proto.ReadRequest, g grpc.ServerStreamingServer[proto.Event]) error { + ins, err := a.getInstance(request.GetContext().GetFunctionName()) + if err != nil { + return err + } + readCh, err := ins.EventStorage().Read(g.Context(), ins.Function().Sources) + if err != nil { + return err + } + l := a.log.With(zap.String("function", ins.Function().Name)) + for { + select { + case <-g.Context().Done(): + return nil + case e, ok := <-readCh: + if !ok { + return nil + } + p, err := io.ReadAll(e.Payload()) + if err != nil { + return fmt.Errorf("failed to read event payload: %w", err) + } + protoE := &proto.Event{ + Id: e.ID(), + SchemaId: e.SchemaID(), + Payload: p, + Properties: e.Properties(), + } + if l.Core().Enabled(zap.DebugLevel) { + l.Debug("Sending event", zap.Any("event", protoE)) + } + err = g.Send(protoE) + if err != nil { + return err + } + } + } +} + +func (a *adapterImpl) Write(ctx context.Context, request *proto.WriteRequest) (*proto.WriteResponse, error) { + ins, err := a.getInstance(request.GetContext().GetFunctionName()) + if err != nil { + return nil, err + } + e := event.NewRawEvent("", bytes.NewReader(request.GetPayload())) + err = ins.EventStorage().Write(ctx, e, ins.Function().Sink) + if err != nil { + return nil, err + } + l := a.log.With(zap.String("function", ins.Function().Name)) + if l.Core().Enabled(zap.DebugLevel) { + l.Debug("Write event to storage", zap.String("event_id", e.ID())) + } + select { + case <-e.OnCommit(): + return &proto.WriteResponse{}, nil + case <-ctx.Done(): + return nil, ctx.Err() + } +} + +func (a *adapterImpl) Commit(ctx context.Context, request *proto.CommitRequest) (*proto.CommitResponse, error) { + ins, err := a.getInstance(request.GetContext().GetFunctionName()) + if err != nil { + return nil, err + } + err = ins.EventStorage().Commit(ctx, request.GetEventId()) + if err != nil { + return nil, err + } + return &proto.CommitResponse{}, nil +} + +func (a *adapterImpl) PutState(ctx context.Context, request *proto.PutStateRequest) (*proto.PutStateResponse, error) { + //TODO implement me + panic("implement me") +} + +func (a *adapterImpl) GetState(ctx context.Context, request *proto.GetStateRequest) (*proto.GetStateResponse, error) { + //TODO implement me + panic("implement me") +} + +func (a *adapterImpl) ListStates(ctx context.Context, request *proto.ListStatesRequest) (*proto.ListStatesResponse, error) { + //TODO implement me + panic("implement me") +} + +func (a *adapterImpl) DeleteState(ctx context.Context, request *proto.DeleteStateRequest) (*proto.DeleteStateResponse, error) { + //TODO implement me + panic("implement me") +} + +func NewExternalAdapter(ctx context.Context, name string, c *Config) (api.RuntimeAdapter, error) { + l := c.Logger.Named("external-adapter").Named(name) + s := grpc.NewServer() + a := &adapterImpl{ + log: l, + instances: make(map[string]api.Instance), + services: make(map[string]service), + } + proto.RegisterRuntimeServiceServer(s, a) + proto.RegisterFunctionServiceServer(s, a) + // TODO: Setup health service + go func() { + l.Info("starting external adapter") + if err := s.Serve(c.Listener); err != nil { + l.Error("failed to start external adapter", zap.Error(err)) + panic(err) + } + }() + go func() { + <-ctx.Done() + l.Info("stopping external adapter") + s.Stop() + }() + return a, nil +} diff --git a/fs/runtime/external/adapter_test.go b/fs/runtime/external/adapter_test.go new file mode 100644 index 00000000..cd14f485 --- /dev/null +++ b/fs/runtime/external/adapter_test.go @@ -0,0 +1,177 @@ +package external + +import ( + "context" + "encoding/json" + "fmt" + api "github.com/functionstream/function-stream/fs/api" + "github.com/functionstream/function-stream/fs/event" + "github.com/functionstream/function-stream/fs/model" + "github.com/functionstream/function-stream/pkg/testfunctions" + "github.com/functionstream/function-stream/pkg/testutil" + "github.com/stretchr/testify/require" + "net" + "os" + "testing" + "time" +) + +type MockInstance struct { + ctx context.Context + f *model.Function + es api.EventStorage + p *model.Package +} + +func (m *MockInstance) Context() context.Context { + return m.ctx +} + +func (m *MockInstance) Function() *model.Function { + return m.f +} + +func (m *MockInstance) Package() *model.Package { + return m.p +} + +func (m *MockInstance) EventStorage() api.EventStorage { + return m.es +} + +func (m *MockInstance) StateStore() api.StateStore { + return nil +} + +func NewMockInstance(ctx context.Context, f *model.Function, es api.EventStorage, p *model.Package) api.Instance { + return &MockInstance{ + ctx: ctx, + f: f, + es: es, + p: p, + } +} + +type MockEventStorage struct { + ReadCh chan api.Event + WriteCh chan api.Event + CommitCh chan string +} + +func (m *MockEventStorage) Read(ctx context.Context, topics []model.TopicConfig) (<-chan api.Event, error) { + return m.ReadCh, nil +} + +func (m *MockEventStorage) Write(ctx context.Context, event api.Event, topic model.TopicConfig) error { + m.WriteCh <- event + return nil +} + +func (m *MockEventStorage) Commit(ctx context.Context, eventId string) error { + m.CommitCh <- eventId + return nil +} + +func NewMockEventStorage() *MockEventStorage { + return &MockEventStorage{ + ReadCh: make(chan api.Event), + WriteCh: make(chan api.Event), + CommitCh: make(chan string, 1000), + } +} + +func TestExternalRuntime(t *testing.T) { + testSocketPath := fmt.Sprintf("/tmp/%s.sock", t.Name()) + require.NoError(t, os.RemoveAll(testSocketPath)) + testfunctions.SetupFSTarget(t, "unix:"+testSocketPath) + lis, err := net.Listen("unix", testSocketPath) + require.NoError(t, err) + log := testutil.GetTestLogger(t) + adapter, err := NewExternalAdapter(context.Background(), "test", &Config{ + Listener: lis, + Logger: log, + }) + require.NoError(t, err) + testMods := testfunctions.NewTestModules() + go testMods.Run(context.Background()) + time.Sleep(1 * time.Second) + + t.Run("Default Module", func(t *testing.T) { + es := NewMockEventStorage() + instance := NewMockInstance(context.Background(), &model.Function{ + Name: "test", + Package: "test", + Module: "default", + }, es, nil) + require.NoError(t, adapter.DeployFunction(context.Background(), instance)) + in, err := event.NewStructEvent("1", &testfunctions.Person{ + Name: "test", + Money: 1, + }) + require.NoError(t, err) + es.ReadCh <- in + out := <-es.WriteCh + require.Equal(t, "", out.ID()) + p := &testfunctions.Person{} + require.NoError(t, json.NewDecoder(out.Payload()).Decode(p)) + require.Equal(t, 2, p.Money) + }) + + t.Run("Counter Module", func(t *testing.T) { + es := NewMockEventStorage() + instance := NewMockInstance(context.Background(), &model.Function{ + Name: "test", + Package: "test", + Module: "counter", + }, es, nil) + require.NoError(t, adapter.DeployFunction(context.Background(), instance)) + in, err := event.NewStructEvent("1", &testfunctions.Counter{ + Count: 1, + }) + require.NoError(t, err) + es.ReadCh <- in + out := <-es.WriteCh + require.Equal(t, "", out.ID()) + c := &testfunctions.Counter{} + require.NoError(t, json.NewDecoder(out.Payload()).Decode(c)) + require.Equal(t, 2, c.Count) + }) + + t.Run("Source Module", func(t *testing.T) { + es := NewMockEventStorage() + instance := NewMockInstance(context.Background(), &model.Function{ + Name: "test", + Package: "test", + Module: "test-source", + }, es, nil) + require.NoError(t, adapter.DeployFunction(context.Background(), instance)) + for i := 0; i < 10; i++ { + out := <-es.WriteCh + require.Equal(t, "", out.ID()) + r := &testfunctions.TestRecord{} + require.NoError(t, json.NewDecoder(out.Payload()).Decode(r)) + require.NoError(t, out.Commit(context.Background())) + require.Equal(t, i, r.ID) + require.Equal(t, "test", r.Name) + } + }) + + t.Run("Sink Module", func(t *testing.T) { + es := NewMockEventStorage() + instance := NewMockInstance(context.Background(), &model.Function{ + Name: "test", + Package: "test", + Module: "test-sink", + }, es, nil) + require.NoError(t, adapter.DeployFunction(context.Background(), instance)) + for i := 0; i < 10; i++ { + e, err := event.NewStructEvent("", &testfunctions.Counter{ + Count: i, + }) + require.NoError(t, err) + es.ReadCh <- e + output := <-testMods.TestSink.SinkCh + require.Equal(t, i, output.Count) + } + }) +} diff --git a/fs/runtime/external/proto/fs.pb.go b/fs/runtime/external/proto/fs.pb.go new file mode 100644 index 00000000..df42e20e --- /dev/null +++ b/fs/runtime/external/proto/fs.pb.go @@ -0,0 +1,1652 @@ +// +// Copyright 2024 Function Stream Org. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v4.25.2 +// source: fs/runtime/external/proto/fs.proto + +package model + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type FunctionEventType int32 + +const ( + FunctionEventType_DEPLOY FunctionEventType = 0 + FunctionEventType_DELETE FunctionEventType = 1 +) + +// Enum value maps for FunctionEventType. +var ( + FunctionEventType_name = map[int32]string{ + 0: "DEPLOY", + 1: "DELETE", + } + FunctionEventType_value = map[string]int32{ + "DEPLOY": 0, + "DELETE": 1, + } +) + +func (x FunctionEventType) Enum() *FunctionEventType { + p := new(FunctionEventType) + *p = x + return p +} + +func (x FunctionEventType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (FunctionEventType) Descriptor() protoreflect.EnumDescriptor { + return file_fs_runtime_external_proto_fs_proto_enumTypes[0].Descriptor() +} + +func (FunctionEventType) Type() protoreflect.EnumType { + return &file_fs_runtime_external_proto_fs_proto_enumTypes[0] +} + +func (x FunctionEventType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use FunctionEventType.Descriptor instead. +func (FunctionEventType) EnumDescriptor() ([]byte, []int) { + return file_fs_runtime_external_proto_fs_proto_rawDescGZIP(), []int{0} +} + +type Event struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + SchemaId int64 `protobuf:"varint,2,opt,name=schemaId,proto3" json:"schemaId,omitempty"` + Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"` + Properties map[string]string `protobuf:"bytes,4,rep,name=properties,proto3" json:"properties,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *Event) Reset() { + *x = Event{} + if protoimpl.UnsafeEnabled { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Event) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Event) ProtoMessage() {} + +func (x *Event) ProtoReflect() protoreflect.Message { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Event.ProtoReflect.Descriptor instead. +func (*Event) Descriptor() ([]byte, []int) { + return file_fs_runtime_external_proto_fs_proto_rawDescGZIP(), []int{0} +} + +func (x *Event) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Event) GetSchemaId() int64 { + if x != nil { + return x.SchemaId + } + return 0 +} + +func (x *Event) GetPayload() []byte { + if x != nil { + return x.Payload + } + return nil +} + +func (x *Event) GetProperties() map[string]string { + if x != nil { + return x.Properties + } + return nil +} + +type Context struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + FunctionName string `protobuf:"bytes,1,opt,name=functionName,proto3" json:"functionName,omitempty"` +} + +func (x *Context) Reset() { + *x = Context{} + if protoimpl.UnsafeEnabled { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Context) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Context) ProtoMessage() {} + +func (x *Context) ProtoReflect() protoreflect.Message { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Context.ProtoReflect.Descriptor instead. +func (*Context) Descriptor() ([]byte, []int) { + return file_fs_runtime_external_proto_fs_proto_rawDescGZIP(), []int{1} +} + +func (x *Context) GetFunctionName() string { + if x != nil { + return x.FunctionName + } + return "" +} + +type ReadRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Context *Context `protobuf:"bytes,1,opt,name=context,proto3" json:"context,omitempty"` +} + +func (x *ReadRequest) Reset() { + *x = ReadRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReadRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReadRequest) ProtoMessage() {} + +func (x *ReadRequest) ProtoReflect() protoreflect.Message { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReadRequest.ProtoReflect.Descriptor instead. +func (*ReadRequest) Descriptor() ([]byte, []int) { + return file_fs_runtime_external_proto_fs_proto_rawDescGZIP(), []int{2} +} + +func (x *ReadRequest) GetContext() *Context { + if x != nil { + return x.Context + } + return nil +} + +type ReadResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Event *Event `protobuf:"bytes,1,opt,name=event,proto3" json:"event,omitempty"` +} + +func (x *ReadResponse) Reset() { + *x = ReadResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReadResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReadResponse) ProtoMessage() {} + +func (x *ReadResponse) ProtoReflect() protoreflect.Message { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReadResponse.ProtoReflect.Descriptor instead. +func (*ReadResponse) Descriptor() ([]byte, []int) { + return file_fs_runtime_external_proto_fs_proto_rawDescGZIP(), []int{3} +} + +func (x *ReadResponse) GetEvent() *Event { + if x != nil { + return x.Event + } + return nil +} + +type WriteRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Context *Context `protobuf:"bytes,1,opt,name=context,proto3" json:"context,omitempty"` + Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` +} + +func (x *WriteRequest) Reset() { + *x = WriteRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WriteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WriteRequest) ProtoMessage() {} + +func (x *WriteRequest) ProtoReflect() protoreflect.Message { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WriteRequest.ProtoReflect.Descriptor instead. +func (*WriteRequest) Descriptor() ([]byte, []int) { + return file_fs_runtime_external_proto_fs_proto_rawDescGZIP(), []int{4} +} + +func (x *WriteRequest) GetContext() *Context { + if x != nil { + return x.Context + } + return nil +} + +func (x *WriteRequest) GetPayload() []byte { + if x != nil { + return x.Payload + } + return nil +} + +type WriteResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *WriteResponse) Reset() { + *x = WriteResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WriteResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WriteResponse) ProtoMessage() {} + +func (x *WriteResponse) ProtoReflect() protoreflect.Message { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WriteResponse.ProtoReflect.Descriptor instead. +func (*WriteResponse) Descriptor() ([]byte, []int) { + return file_fs_runtime_external_proto_fs_proto_rawDescGZIP(), []int{5} +} + +type CommitRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Context *Context `protobuf:"bytes,1,opt,name=context,proto3" json:"context,omitempty"` + EventId string `protobuf:"bytes,2,opt,name=eventId,proto3" json:"eventId,omitempty"` +} + +func (x *CommitRequest) Reset() { + *x = CommitRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CommitRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CommitRequest) ProtoMessage() {} + +func (x *CommitRequest) ProtoReflect() protoreflect.Message { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CommitRequest.ProtoReflect.Descriptor instead. +func (*CommitRequest) Descriptor() ([]byte, []int) { + return file_fs_runtime_external_proto_fs_proto_rawDescGZIP(), []int{6} +} + +func (x *CommitRequest) GetContext() *Context { + if x != nil { + return x.Context + } + return nil +} + +func (x *CommitRequest) GetEventId() string { + if x != nil { + return x.EventId + } + return "" +} + +type CommitResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *CommitResponse) Reset() { + *x = CommitResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CommitResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CommitResponse) ProtoMessage() {} + +func (x *CommitResponse) ProtoReflect() protoreflect.Message { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CommitResponse.ProtoReflect.Descriptor instead. +func (*CommitResponse) Descriptor() ([]byte, []int) { + return file_fs_runtime_external_proto_fs_proto_rawDescGZIP(), []int{7} +} + +type StateContext struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Context *Context `protobuf:"bytes,1,opt,name=context,proto3" json:"context,omitempty"` +} + +func (x *StateContext) Reset() { + *x = StateContext{} + if protoimpl.UnsafeEnabled { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StateContext) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StateContext) ProtoMessage() {} + +func (x *StateContext) ProtoReflect() protoreflect.Message { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StateContext.ProtoReflect.Descriptor instead. +func (*StateContext) Descriptor() ([]byte, []int) { + return file_fs_runtime_external_proto_fs_proto_rawDescGZIP(), []int{8} +} + +func (x *StateContext) GetContext() *Context { + if x != nil { + return x.Context + } + return nil +} + +type GetStateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Context *StateContext `protobuf:"bytes,1,opt,name=context,proto3" json:"context,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` +} + +func (x *GetStateRequest) Reset() { + *x = GetStateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetStateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetStateRequest) ProtoMessage() {} + +func (x *GetStateRequest) ProtoReflect() protoreflect.Message { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetStateRequest.ProtoReflect.Descriptor instead. +func (*GetStateRequest) Descriptor() ([]byte, []int) { + return file_fs_runtime_external_proto_fs_proto_rawDescGZIP(), []int{9} +} + +func (x *GetStateRequest) GetContext() *StateContext { + if x != nil { + return x.Context + } + return nil +} + +func (x *GetStateRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +type GetStateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *GetStateResponse) Reset() { + *x = GetStateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetStateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetStateResponse) ProtoMessage() {} + +func (x *GetStateResponse) ProtoReflect() protoreflect.Message { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetStateResponse.ProtoReflect.Descriptor instead. +func (*GetStateResponse) Descriptor() ([]byte, []int) { + return file_fs_runtime_external_proto_fs_proto_rawDescGZIP(), []int{10} +} + +func (x *GetStateResponse) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +type PutStateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Context *StateContext `protobuf:"bytes,1,opt,name=context,proto3" json:"context,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + Value []byte `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *PutStateRequest) Reset() { + *x = PutStateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PutStateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PutStateRequest) ProtoMessage() {} + +func (x *PutStateRequest) ProtoReflect() protoreflect.Message { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PutStateRequest.ProtoReflect.Descriptor instead. +func (*PutStateRequest) Descriptor() ([]byte, []int) { + return file_fs_runtime_external_proto_fs_proto_rawDescGZIP(), []int{11} +} + +func (x *PutStateRequest) GetContext() *StateContext { + if x != nil { + return x.Context + } + return nil +} + +func (x *PutStateRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *PutStateRequest) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +type PutStateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *PutStateResponse) Reset() { + *x = PutStateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PutStateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PutStateResponse) ProtoMessage() {} + +func (x *PutStateResponse) ProtoReflect() protoreflect.Message { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PutStateResponse.ProtoReflect.Descriptor instead. +func (*PutStateResponse) Descriptor() ([]byte, []int) { + return file_fs_runtime_external_proto_fs_proto_rawDescGZIP(), []int{12} +} + +type ListStatesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Context *StateContext `protobuf:"bytes,1,opt,name=context,proto3" json:"context,omitempty"` + StartInclusive string `protobuf:"bytes,2,opt,name=start_inclusive,json=startInclusive,proto3" json:"start_inclusive,omitempty"` + EndExclusive string `protobuf:"bytes,3,opt,name=end_exclusive,json=endExclusive,proto3" json:"end_exclusive,omitempty"` +} + +func (x *ListStatesRequest) Reset() { + *x = ListStatesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListStatesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListStatesRequest) ProtoMessage() {} + +func (x *ListStatesRequest) ProtoReflect() protoreflect.Message { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListStatesRequest.ProtoReflect.Descriptor instead. +func (*ListStatesRequest) Descriptor() ([]byte, []int) { + return file_fs_runtime_external_proto_fs_proto_rawDescGZIP(), []int{13} +} + +func (x *ListStatesRequest) GetContext() *StateContext { + if x != nil { + return x.Context + } + return nil +} + +func (x *ListStatesRequest) GetStartInclusive() string { + if x != nil { + return x.StartInclusive + } + return "" +} + +func (x *ListStatesRequest) GetEndExclusive() string { + if x != nil { + return x.EndExclusive + } + return "" +} + +type ListStatesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Keys []string `protobuf:"bytes,1,rep,name=keys,proto3" json:"keys,omitempty"` +} + +func (x *ListStatesResponse) Reset() { + *x = ListStatesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListStatesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListStatesResponse) ProtoMessage() {} + +func (x *ListStatesResponse) ProtoReflect() protoreflect.Message { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListStatesResponse.ProtoReflect.Descriptor instead. +func (*ListStatesResponse) Descriptor() ([]byte, []int) { + return file_fs_runtime_external_proto_fs_proto_rawDescGZIP(), []int{14} +} + +func (x *ListStatesResponse) GetKeys() []string { + if x != nil { + return x.Keys + } + return nil +} + +type DeleteStateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Context *StateContext `protobuf:"bytes,1,opt,name=context,proto3" json:"context,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` +} + +func (x *DeleteStateRequest) Reset() { + *x = DeleteStateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteStateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteStateRequest) ProtoMessage() {} + +func (x *DeleteStateRequest) ProtoReflect() protoreflect.Message { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteStateRequest.ProtoReflect.Descriptor instead. +func (*DeleteStateRequest) Descriptor() ([]byte, []int) { + return file_fs_runtime_external_proto_fs_proto_rawDescGZIP(), []int{15} +} + +func (x *DeleteStateRequest) GetContext() *StateContext { + if x != nil { + return x.Context + } + return nil +} + +func (x *DeleteStateRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +type DeleteStateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DeleteStateResponse) Reset() { + *x = DeleteStateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteStateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteStateResponse) ProtoMessage() {} + +func (x *DeleteStateResponse) ProtoReflect() protoreflect.Message { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteStateResponse.ProtoReflect.Descriptor instead. +func (*DeleteStateResponse) Descriptor() ([]byte, []int) { + return file_fs_runtime_external_proto_fs_proto_rawDescGZIP(), []int{16} +} + +type FunctionEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type FunctionEventType `protobuf:"varint,1,opt,name=type,proto3,enum=fs_external.FunctionEventType" json:"type,omitempty"` + // Types that are assignable to Payload: + // + // *FunctionEvent_Function + // *FunctionEvent_FunctionName + Payload isFunctionEvent_Payload `protobuf_oneof:"payload"` +} + +func (x *FunctionEvent) Reset() { + *x = FunctionEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FunctionEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FunctionEvent) ProtoMessage() {} + +func (x *FunctionEvent) ProtoReflect() protoreflect.Message { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FunctionEvent.ProtoReflect.Descriptor instead. +func (*FunctionEvent) Descriptor() ([]byte, []int) { + return file_fs_runtime_external_proto_fs_proto_rawDescGZIP(), []int{17} +} + +func (x *FunctionEvent) GetType() FunctionEventType { + if x != nil { + return x.Type + } + return FunctionEventType_DEPLOY +} + +func (m *FunctionEvent) GetPayload() isFunctionEvent_Payload { + if m != nil { + return m.Payload + } + return nil +} + +func (x *FunctionEvent) GetFunction() *Function { + if x, ok := x.GetPayload().(*FunctionEvent_Function); ok { + return x.Function + } + return nil +} + +func (x *FunctionEvent) GetFunctionName() string { + if x, ok := x.GetPayload().(*FunctionEvent_FunctionName); ok { + return x.FunctionName + } + return "" +} + +type isFunctionEvent_Payload interface { + isFunctionEvent_Payload() +} + +type FunctionEvent_Function struct { + Function *Function `protobuf:"bytes,2,opt,name=function,proto3,oneof"` +} + +type FunctionEvent_FunctionName struct { + FunctionName string `protobuf:"bytes,3,opt,name=functionName,proto3,oneof"` +} + +func (*FunctionEvent_Function) isFunctionEvent_Payload() {} + +func (*FunctionEvent_FunctionName) isFunctionEvent_Payload() {} + +type Function struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Package string `protobuf:"bytes,2,opt,name=package,proto3" json:"package,omitempty"` + Module string `protobuf:"bytes,3,opt,name=module,proto3" json:"module,omitempty"` + Config map[string]string `protobuf:"bytes,4,rep,name=config,proto3" json:"config,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *Function) Reset() { + *x = Function{} + if protoimpl.UnsafeEnabled { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Function) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Function) ProtoMessage() {} + +func (x *Function) ProtoReflect() protoreflect.Message { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Function.ProtoReflect.Descriptor instead. +func (*Function) Descriptor() ([]byte, []int) { + return file_fs_runtime_external_proto_fs_proto_rawDescGZIP(), []int{18} +} + +func (x *Function) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Function) GetPackage() string { + if x != nil { + return x.Package + } + return "" +} + +func (x *Function) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *Function) GetConfig() map[string]string { + if x != nil { + return x.Config + } + return nil +} + +type OnDeployRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ServiceId string `protobuf:"bytes,1,opt,name=serviceId,proto3" json:"serviceId,omitempty"` + Modules []string `protobuf:"bytes,2,rep,name=modules,proto3" json:"modules,omitempty"` +} + +func (x *OnDeployRequest) Reset() { + *x = OnDeployRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OnDeployRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OnDeployRequest) ProtoMessage() {} + +func (x *OnDeployRequest) ProtoReflect() protoreflect.Message { + mi := &file_fs_runtime_external_proto_fs_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OnDeployRequest.ProtoReflect.Descriptor instead. +func (*OnDeployRequest) Descriptor() ([]byte, []int) { + return file_fs_runtime_external_proto_fs_proto_rawDescGZIP(), []int{19} +} + +func (x *OnDeployRequest) GetServiceId() string { + if x != nil { + return x.ServiceId + } + return "" +} + +func (x *OnDeployRequest) GetModules() []string { + if x != nil { + return x.Modules + } + return nil +} + +var File_fs_runtime_external_proto_fs_proto protoreflect.FileDescriptor + +var file_fs_runtime_external_proto_fs_proto_rawDesc = []byte{ + 0x0a, 0x22, 0x66, 0x73, 0x2f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2f, 0x65, 0x78, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x66, 0x73, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x22, 0xd0, 0x01, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x73, + 0x63, 0x68, 0x65, 0x6d, 0x61, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, + 0x63, 0x68, 0x65, 0x6d, 0x61, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, + 0x61, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, + 0x64, 0x12, 0x42, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, + 0x74, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x65, + 0x72, 0x74, 0x69, 0x65, 0x73, 0x1a, 0x3d, 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, + 0x69, 0x65, 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, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x22, 0x2d, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, + 0x22, 0x0a, 0x0c, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, + 0x61, 0x6d, 0x65, 0x22, 0x3d, 0x0a, 0x0b, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x78, 0x74, 0x22, 0x38, 0x0a, 0x0c, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x12, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x58, 0x0a, 0x0c, + 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, + 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x43, 0x6f, 0x6e, 0x74, + 0x65, 0x78, 0x74, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x18, 0x0a, 0x07, + 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x0f, 0x0a, 0x0d, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x59, 0x0a, 0x0d, 0x43, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x66, 0x73, 0x5f, 0x65, + 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, + 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x49, 0x64, 0x22, 0x10, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3e, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, + 0x74, 0x65, 0x78, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x07, 0x63, 0x6f, 0x6e, + 0x74, 0x65, 0x78, 0x74, 0x22, 0x58, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, + 0x65, 0x78, 0x74, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x28, + 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x6e, 0x0a, 0x0f, 0x50, 0x75, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x07, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x66, + 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x50, 0x75, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x96, 0x01, 0x0a, + 0x11, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x33, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x07, + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, + 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x6e, 0x64, 0x5f, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x6e, 0x64, 0x45, 0x78, 0x63, 0x6c, + 0x75, 0x73, 0x69, 0x76, 0x65, 0x22, 0x28, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6b, + 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x22, + 0x5b, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, + 0x74, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x15, 0x0a, 0x13, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0xa9, 0x01, 0x0a, 0x0d, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x2e, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, + 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x33, 0x0a, 0x08, 0x66, 0x75, 0x6e, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x66, 0x73, + 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x08, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x24, + 0x0a, 0x0c, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x4e, 0x61, 0x6d, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, + 0xc6, 0x01, 0x0a, 0x08, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x6f, + 0x64, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x6f, 0x64, 0x75, + 0x6c, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2e, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x39, 0x0a, + 0x0b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 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, 0x09, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x49, 0x0a, 0x0f, 0x4f, 0x6e, 0x44, 0x65, + 0x70, 0x6c, 0x6f, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x6f, 0x64, + 0x75, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75, + 0x6c, 0x65, 0x73, 0x2a, 0x2b, 0x0a, 0x11, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x45, 0x50, 0x4c, + 0x4f, 0x59, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x01, + 0x32, 0xff, 0x03, 0x0a, 0x0f, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x18, 0x2e, 0x66, + 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x3e, 0x0a, 0x05, + 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1a, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x57, + 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x06, + 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x1a, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x47, 0x0a, 0x08, 0x50, 0x75, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x66, 0x73, + 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x50, 0x75, 0x74, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x66, 0x73, 0x5f, 0x65, + 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x50, 0x75, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x4d, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73, 0x12, + 0x1e, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1f, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x50, 0x0a, 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, + 0x1f, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x20, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x32, 0x57, 0x0a, 0x0e, 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x07, 0x4f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, + 0x1c, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x4f, 0x6e, + 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, + 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x46, 0x75, 0x6e, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x42, 0x1b, 0x5a, 0x19, 0x66, + 0x73, 0x2f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_fs_runtime_external_proto_fs_proto_rawDescOnce sync.Once + file_fs_runtime_external_proto_fs_proto_rawDescData = file_fs_runtime_external_proto_fs_proto_rawDesc +) + +func file_fs_runtime_external_proto_fs_proto_rawDescGZIP() []byte { + file_fs_runtime_external_proto_fs_proto_rawDescOnce.Do(func() { + file_fs_runtime_external_proto_fs_proto_rawDescData = protoimpl.X.CompressGZIP(file_fs_runtime_external_proto_fs_proto_rawDescData) + }) + return file_fs_runtime_external_proto_fs_proto_rawDescData +} + +var file_fs_runtime_external_proto_fs_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_fs_runtime_external_proto_fs_proto_msgTypes = make([]protoimpl.MessageInfo, 22) +var file_fs_runtime_external_proto_fs_proto_goTypes = []any{ + (FunctionEventType)(0), // 0: fs_external.FunctionEventType + (*Event)(nil), // 1: fs_external.Event + (*Context)(nil), // 2: fs_external.Context + (*ReadRequest)(nil), // 3: fs_external.ReadRequest + (*ReadResponse)(nil), // 4: fs_external.ReadResponse + (*WriteRequest)(nil), // 5: fs_external.WriteRequest + (*WriteResponse)(nil), // 6: fs_external.WriteResponse + (*CommitRequest)(nil), // 7: fs_external.CommitRequest + (*CommitResponse)(nil), // 8: fs_external.CommitResponse + (*StateContext)(nil), // 9: fs_external.StateContext + (*GetStateRequest)(nil), // 10: fs_external.GetStateRequest + (*GetStateResponse)(nil), // 11: fs_external.GetStateResponse + (*PutStateRequest)(nil), // 12: fs_external.PutStateRequest + (*PutStateResponse)(nil), // 13: fs_external.PutStateResponse + (*ListStatesRequest)(nil), // 14: fs_external.ListStatesRequest + (*ListStatesResponse)(nil), // 15: fs_external.ListStatesResponse + (*DeleteStateRequest)(nil), // 16: fs_external.DeleteStateRequest + (*DeleteStateResponse)(nil), // 17: fs_external.DeleteStateResponse + (*FunctionEvent)(nil), // 18: fs_external.FunctionEvent + (*Function)(nil), // 19: fs_external.Function + (*OnDeployRequest)(nil), // 20: fs_external.OnDeployRequest + nil, // 21: fs_external.Event.PropertiesEntry + nil, // 22: fs_external.Function.ConfigEntry +} +var file_fs_runtime_external_proto_fs_proto_depIdxs = []int32{ + 21, // 0: fs_external.Event.properties:type_name -> fs_external.Event.PropertiesEntry + 2, // 1: fs_external.ReadRequest.context:type_name -> fs_external.Context + 1, // 2: fs_external.ReadResponse.event:type_name -> fs_external.Event + 2, // 3: fs_external.WriteRequest.context:type_name -> fs_external.Context + 2, // 4: fs_external.CommitRequest.context:type_name -> fs_external.Context + 2, // 5: fs_external.StateContext.context:type_name -> fs_external.Context + 9, // 6: fs_external.GetStateRequest.context:type_name -> fs_external.StateContext + 9, // 7: fs_external.PutStateRequest.context:type_name -> fs_external.StateContext + 9, // 8: fs_external.ListStatesRequest.context:type_name -> fs_external.StateContext + 9, // 9: fs_external.DeleteStateRequest.context:type_name -> fs_external.StateContext + 0, // 10: fs_external.FunctionEvent.type:type_name -> fs_external.FunctionEventType + 19, // 11: fs_external.FunctionEvent.function:type_name -> fs_external.Function + 22, // 12: fs_external.Function.config:type_name -> fs_external.Function.ConfigEntry + 3, // 13: fs_external.FunctionService.Read:input_type -> fs_external.ReadRequest + 5, // 14: fs_external.FunctionService.Write:input_type -> fs_external.WriteRequest + 7, // 15: fs_external.FunctionService.Commit:input_type -> fs_external.CommitRequest + 12, // 16: fs_external.FunctionService.PutState:input_type -> fs_external.PutStateRequest + 10, // 17: fs_external.FunctionService.GetState:input_type -> fs_external.GetStateRequest + 14, // 18: fs_external.FunctionService.ListStates:input_type -> fs_external.ListStatesRequest + 16, // 19: fs_external.FunctionService.DeleteState:input_type -> fs_external.DeleteStateRequest + 20, // 20: fs_external.RuntimeService.OnEvent:input_type -> fs_external.OnDeployRequest + 1, // 21: fs_external.FunctionService.Read:output_type -> fs_external.Event + 6, // 22: fs_external.FunctionService.Write:output_type -> fs_external.WriteResponse + 8, // 23: fs_external.FunctionService.Commit:output_type -> fs_external.CommitResponse + 13, // 24: fs_external.FunctionService.PutState:output_type -> fs_external.PutStateResponse + 11, // 25: fs_external.FunctionService.GetState:output_type -> fs_external.GetStateResponse + 15, // 26: fs_external.FunctionService.ListStates:output_type -> fs_external.ListStatesResponse + 17, // 27: fs_external.FunctionService.DeleteState:output_type -> fs_external.DeleteStateResponse + 18, // 28: fs_external.RuntimeService.OnEvent:output_type -> fs_external.FunctionEvent + 21, // [21:29] is the sub-list for method output_type + 13, // [13:21] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name +} + +func init() { file_fs_runtime_external_proto_fs_proto_init() } +func file_fs_runtime_external_proto_fs_proto_init() { + if File_fs_runtime_external_proto_fs_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_fs_runtime_external_proto_fs_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*Event); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_fs_runtime_external_proto_fs_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*Context); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_fs_runtime_external_proto_fs_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*ReadRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_fs_runtime_external_proto_fs_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*ReadResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_fs_runtime_external_proto_fs_proto_msgTypes[4].Exporter = func(v any, i int) any { + switch v := v.(*WriteRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_fs_runtime_external_proto_fs_proto_msgTypes[5].Exporter = func(v any, i int) any { + switch v := v.(*WriteResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_fs_runtime_external_proto_fs_proto_msgTypes[6].Exporter = func(v any, i int) any { + switch v := v.(*CommitRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_fs_runtime_external_proto_fs_proto_msgTypes[7].Exporter = func(v any, i int) any { + switch v := v.(*CommitResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_fs_runtime_external_proto_fs_proto_msgTypes[8].Exporter = func(v any, i int) any { + switch v := v.(*StateContext); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_fs_runtime_external_proto_fs_proto_msgTypes[9].Exporter = func(v any, i int) any { + switch v := v.(*GetStateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_fs_runtime_external_proto_fs_proto_msgTypes[10].Exporter = func(v any, i int) any { + switch v := v.(*GetStateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_fs_runtime_external_proto_fs_proto_msgTypes[11].Exporter = func(v any, i int) any { + switch v := v.(*PutStateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_fs_runtime_external_proto_fs_proto_msgTypes[12].Exporter = func(v any, i int) any { + switch v := v.(*PutStateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_fs_runtime_external_proto_fs_proto_msgTypes[13].Exporter = func(v any, i int) any { + switch v := v.(*ListStatesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_fs_runtime_external_proto_fs_proto_msgTypes[14].Exporter = func(v any, i int) any { + switch v := v.(*ListStatesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_fs_runtime_external_proto_fs_proto_msgTypes[15].Exporter = func(v any, i int) any { + switch v := v.(*DeleteStateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_fs_runtime_external_proto_fs_proto_msgTypes[16].Exporter = func(v any, i int) any { + switch v := v.(*DeleteStateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_fs_runtime_external_proto_fs_proto_msgTypes[17].Exporter = func(v any, i int) any { + switch v := v.(*FunctionEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_fs_runtime_external_proto_fs_proto_msgTypes[18].Exporter = func(v any, i int) any { + switch v := v.(*Function); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_fs_runtime_external_proto_fs_proto_msgTypes[19].Exporter = func(v any, i int) any { + switch v := v.(*OnDeployRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_fs_runtime_external_proto_fs_proto_msgTypes[17].OneofWrappers = []any{ + (*FunctionEvent_Function)(nil), + (*FunctionEvent_FunctionName)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_fs_runtime_external_proto_fs_proto_rawDesc, + NumEnums: 1, + NumMessages: 22, + NumExtensions: 0, + NumServices: 2, + }, + GoTypes: file_fs_runtime_external_proto_fs_proto_goTypes, + DependencyIndexes: file_fs_runtime_external_proto_fs_proto_depIdxs, + EnumInfos: file_fs_runtime_external_proto_fs_proto_enumTypes, + MessageInfos: file_fs_runtime_external_proto_fs_proto_msgTypes, + }.Build() + File_fs_runtime_external_proto_fs_proto = out.File + file_fs_runtime_external_proto_fs_proto_rawDesc = nil + file_fs_runtime_external_proto_fs_proto_goTypes = nil + file_fs_runtime_external_proto_fs_proto_depIdxs = nil +} diff --git a/fs/runtime/external/proto/fs.proto b/fs/runtime/external/proto/fs.proto new file mode 100644 index 00000000..957cf967 --- /dev/null +++ b/fs/runtime/external/proto/fs.proto @@ -0,0 +1,138 @@ +/* + * Copyright 2024 Function Stream Org. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto3"; +option go_package = "fs/runtime/external/model"; +package fs_external; + +message Event { + string id = 1; + int64 schemaId = 2; + bytes payload = 3; + map properties = 4; +} + +message Context { + string functionName = 1; +} + +message ReadRequest { + Context context = 1; +} + +message ReadResponse { + Event event = 1; +} + +message WriteRequest { + Context context = 1; + bytes payload = 2; +} + +message WriteResponse { + +} + +message CommitRequest { + Context context = 1; + string eventId = 2; +} + +message CommitResponse { + +} + +message StateContext { + Context context = 1; +} + +message GetStateRequest { + StateContext context = 1; + string key = 2; +} + +message GetStateResponse { + bytes value = 2; +} + +message PutStateRequest { + StateContext context = 1; + string key = 2; + bytes value = 3; +} + +message PutStateResponse { + +} + +message ListStatesRequest { + StateContext context = 1; + string start_inclusive = 2; + string end_exclusive = 3; +} + +message ListStatesResponse { + repeated string keys = 1; +} + +message DeleteStateRequest { + StateContext context = 1; + string key = 2; +} + +message DeleteStateResponse { + +} + +service FunctionService { + rpc Read(ReadRequest) returns (stream Event); + rpc Write(WriteRequest) returns (WriteResponse); + rpc Commit(CommitRequest) returns (CommitResponse); + + rpc PutState(PutStateRequest) returns (PutStateResponse); + rpc GetState(GetStateRequest) returns (GetStateResponse); + rpc ListStates(ListStatesRequest) returns (ListStatesResponse); + rpc DeleteState(DeleteStateRequest) returns (DeleteStateResponse); +} + +enum FunctionEventType { + DEPLOY = 0; + DELETE = 1; +} + +message FunctionEvent { + FunctionEventType type = 1; + oneof payload { + Function function = 2; + string functionName = 3; + } +} + +message Function { + string name = 1; + string package = 2; + string module = 3; + map config = 4; +} + +message OnDeployRequest { + string serviceId = 1; + repeated string modules = 2; +} + +service RuntimeService { + rpc OnEvent(OnDeployRequest) returns (stream FunctionEvent); +} diff --git a/fs/runtime/external/proto/fs_grpc.pb.go b/fs/runtime/external/proto/fs_grpc.pb.go new file mode 100644 index 00000000..a83936d5 --- /dev/null +++ b/fs/runtime/external/proto/fs_grpc.pb.go @@ -0,0 +1,473 @@ +// +// Copyright 2024 Function Stream Org. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v4.25.2 +// source: fs/runtime/external/proto/fs.proto + +package model + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + FunctionService_Read_FullMethodName = "/fs_external.FunctionService/Read" + FunctionService_Write_FullMethodName = "/fs_external.FunctionService/Write" + FunctionService_Commit_FullMethodName = "/fs_external.FunctionService/Commit" + FunctionService_PutState_FullMethodName = "/fs_external.FunctionService/PutState" + FunctionService_GetState_FullMethodName = "/fs_external.FunctionService/GetState" + FunctionService_ListStates_FullMethodName = "/fs_external.FunctionService/ListStates" + FunctionService_DeleteState_FullMethodName = "/fs_external.FunctionService/DeleteState" +) + +// FunctionServiceClient is the client API for FunctionService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type FunctionServiceClient interface { + Read(ctx context.Context, in *ReadRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Event], error) + Write(ctx context.Context, in *WriteRequest, opts ...grpc.CallOption) (*WriteResponse, error) + Commit(ctx context.Context, in *CommitRequest, opts ...grpc.CallOption) (*CommitResponse, error) + PutState(ctx context.Context, in *PutStateRequest, opts ...grpc.CallOption) (*PutStateResponse, error) + GetState(ctx context.Context, in *GetStateRequest, opts ...grpc.CallOption) (*GetStateResponse, error) + ListStates(ctx context.Context, in *ListStatesRequest, opts ...grpc.CallOption) (*ListStatesResponse, error) + DeleteState(ctx context.Context, in *DeleteStateRequest, opts ...grpc.CallOption) (*DeleteStateResponse, error) +} + +type functionServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewFunctionServiceClient(cc grpc.ClientConnInterface) FunctionServiceClient { + return &functionServiceClient{cc} +} + +func (c *functionServiceClient) Read(ctx context.Context, in *ReadRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Event], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &FunctionService_ServiceDesc.Streams[0], FunctionService_Read_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[ReadRequest, Event]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type FunctionService_ReadClient = grpc.ServerStreamingClient[Event] + +func (c *functionServiceClient) Write(ctx context.Context, in *WriteRequest, opts ...grpc.CallOption) (*WriteResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(WriteResponse) + err := c.cc.Invoke(ctx, FunctionService_Write_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *functionServiceClient) Commit(ctx context.Context, in *CommitRequest, opts ...grpc.CallOption) (*CommitResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CommitResponse) + err := c.cc.Invoke(ctx, FunctionService_Commit_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *functionServiceClient) PutState(ctx context.Context, in *PutStateRequest, opts ...grpc.CallOption) (*PutStateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PutStateResponse) + err := c.cc.Invoke(ctx, FunctionService_PutState_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *functionServiceClient) GetState(ctx context.Context, in *GetStateRequest, opts ...grpc.CallOption) (*GetStateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetStateResponse) + err := c.cc.Invoke(ctx, FunctionService_GetState_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *functionServiceClient) ListStates(ctx context.Context, in *ListStatesRequest, opts ...grpc.CallOption) (*ListStatesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListStatesResponse) + err := c.cc.Invoke(ctx, FunctionService_ListStates_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *functionServiceClient) DeleteState(ctx context.Context, in *DeleteStateRequest, opts ...grpc.CallOption) (*DeleteStateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteStateResponse) + err := c.cc.Invoke(ctx, FunctionService_DeleteState_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// FunctionServiceServer is the server API for FunctionService service. +// All implementations must embed UnimplementedFunctionServiceServer +// for forward compatibility. +type FunctionServiceServer interface { + Read(*ReadRequest, grpc.ServerStreamingServer[Event]) error + Write(context.Context, *WriteRequest) (*WriteResponse, error) + Commit(context.Context, *CommitRequest) (*CommitResponse, error) + PutState(context.Context, *PutStateRequest) (*PutStateResponse, error) + GetState(context.Context, *GetStateRequest) (*GetStateResponse, error) + ListStates(context.Context, *ListStatesRequest) (*ListStatesResponse, error) + DeleteState(context.Context, *DeleteStateRequest) (*DeleteStateResponse, error) + mustEmbedUnimplementedFunctionServiceServer() +} + +// UnimplementedFunctionServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedFunctionServiceServer struct{} + +func (UnimplementedFunctionServiceServer) Read(*ReadRequest, grpc.ServerStreamingServer[Event]) error { + return status.Errorf(codes.Unimplemented, "method Read not implemented") +} +func (UnimplementedFunctionServiceServer) Write(context.Context, *WriteRequest) (*WriteResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Write not implemented") +} +func (UnimplementedFunctionServiceServer) Commit(context.Context, *CommitRequest) (*CommitResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Commit not implemented") +} +func (UnimplementedFunctionServiceServer) PutState(context.Context, *PutStateRequest) (*PutStateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PutState not implemented") +} +func (UnimplementedFunctionServiceServer) GetState(context.Context, *GetStateRequest) (*GetStateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetState not implemented") +} +func (UnimplementedFunctionServiceServer) ListStates(context.Context, *ListStatesRequest) (*ListStatesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListStates not implemented") +} +func (UnimplementedFunctionServiceServer) DeleteState(context.Context, *DeleteStateRequest) (*DeleteStateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteState not implemented") +} +func (UnimplementedFunctionServiceServer) mustEmbedUnimplementedFunctionServiceServer() {} +func (UnimplementedFunctionServiceServer) testEmbeddedByValue() {} + +// UnsafeFunctionServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to FunctionServiceServer will +// result in compilation errors. +type UnsafeFunctionServiceServer interface { + mustEmbedUnimplementedFunctionServiceServer() +} + +func RegisterFunctionServiceServer(s grpc.ServiceRegistrar, srv FunctionServiceServer) { + // If the following call pancis, it indicates UnimplementedFunctionServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&FunctionService_ServiceDesc, srv) +} + +func _FunctionService_Read_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(ReadRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(FunctionServiceServer).Read(m, &grpc.GenericServerStream[ReadRequest, Event]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type FunctionService_ReadServer = grpc.ServerStreamingServer[Event] + +func _FunctionService_Write_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WriteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(FunctionServiceServer).Write(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: FunctionService_Write_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(FunctionServiceServer).Write(ctx, req.(*WriteRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _FunctionService_Commit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CommitRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(FunctionServiceServer).Commit(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: FunctionService_Commit_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(FunctionServiceServer).Commit(ctx, req.(*CommitRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _FunctionService_PutState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PutStateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(FunctionServiceServer).PutState(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: FunctionService_PutState_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(FunctionServiceServer).PutState(ctx, req.(*PutStateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _FunctionService_GetState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetStateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(FunctionServiceServer).GetState(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: FunctionService_GetState_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(FunctionServiceServer).GetState(ctx, req.(*GetStateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _FunctionService_ListStates_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListStatesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(FunctionServiceServer).ListStates(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: FunctionService_ListStates_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(FunctionServiceServer).ListStates(ctx, req.(*ListStatesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _FunctionService_DeleteState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteStateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(FunctionServiceServer).DeleteState(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: FunctionService_DeleteState_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(FunctionServiceServer).DeleteState(ctx, req.(*DeleteStateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// FunctionService_ServiceDesc is the grpc.ServiceDesc for FunctionService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var FunctionService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "fs_external.FunctionService", + HandlerType: (*FunctionServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Write", + Handler: _FunctionService_Write_Handler, + }, + { + MethodName: "Commit", + Handler: _FunctionService_Commit_Handler, + }, + { + MethodName: "PutState", + Handler: _FunctionService_PutState_Handler, + }, + { + MethodName: "GetState", + Handler: _FunctionService_GetState_Handler, + }, + { + MethodName: "ListStates", + Handler: _FunctionService_ListStates_Handler, + }, + { + MethodName: "DeleteState", + Handler: _FunctionService_DeleteState_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "Read", + Handler: _FunctionService_Read_Handler, + ServerStreams: true, + }, + }, + Metadata: "fs/runtime/external/proto/fs.proto", +} + +const ( + RuntimeService_OnEvent_FullMethodName = "/fs_external.RuntimeService/OnEvent" +) + +// RuntimeServiceClient is the client API for RuntimeService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type RuntimeServiceClient interface { + OnEvent(ctx context.Context, in *OnDeployRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[FunctionEvent], error) +} + +type runtimeServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewRuntimeServiceClient(cc grpc.ClientConnInterface) RuntimeServiceClient { + return &runtimeServiceClient{cc} +} + +func (c *runtimeServiceClient) OnEvent(ctx context.Context, in *OnDeployRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[FunctionEvent], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &RuntimeService_ServiceDesc.Streams[0], RuntimeService_OnEvent_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[OnDeployRequest, FunctionEvent]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type RuntimeService_OnEventClient = grpc.ServerStreamingClient[FunctionEvent] + +// RuntimeServiceServer is the server API for RuntimeService service. +// All implementations must embed UnimplementedRuntimeServiceServer +// for forward compatibility. +type RuntimeServiceServer interface { + OnEvent(*OnDeployRequest, grpc.ServerStreamingServer[FunctionEvent]) error + mustEmbedUnimplementedRuntimeServiceServer() +} + +// UnimplementedRuntimeServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedRuntimeServiceServer struct{} + +func (UnimplementedRuntimeServiceServer) OnEvent(*OnDeployRequest, grpc.ServerStreamingServer[FunctionEvent]) error { + return status.Errorf(codes.Unimplemented, "method OnEvent not implemented") +} +func (UnimplementedRuntimeServiceServer) mustEmbedUnimplementedRuntimeServiceServer() {} +func (UnimplementedRuntimeServiceServer) testEmbeddedByValue() {} + +// UnsafeRuntimeServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to RuntimeServiceServer will +// result in compilation errors. +type UnsafeRuntimeServiceServer interface { + mustEmbedUnimplementedRuntimeServiceServer() +} + +func RegisterRuntimeServiceServer(s grpc.ServiceRegistrar, srv RuntimeServiceServer) { + // If the following call pancis, it indicates UnimplementedRuntimeServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&RuntimeService_ServiceDesc, srv) +} + +func _RuntimeService_OnEvent_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(OnDeployRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(RuntimeServiceServer).OnEvent(m, &grpc.GenericServerStream[OnDeployRequest, FunctionEvent]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type RuntimeService_OnEventServer = grpc.ServerStreamingServer[FunctionEvent] + +// RuntimeService_ServiceDesc is the grpc.ServiceDesc for RuntimeService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var RuntimeService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "fs_external.RuntimeService", + HandlerType: (*RuntimeServiceServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "OnEvent", + Handler: _RuntimeService_OnEvent_Handler, + ServerStreams: true, + }, + }, + Metadata: "fs/runtime/external/proto/fs.proto", +} diff --git a/fs/statestore/memory/memorystatestore.go b/fs/statestore/memory/memorystatestore.go new file mode 100644 index 00000000..ad1fc04d --- /dev/null +++ b/fs/statestore/memory/memorystatestore.go @@ -0,0 +1,61 @@ +package memory + +import ( + "context" + "github.com/functionstream/function-stream/fs/api" + "sort" + "sync" +) + +type Store struct { + mu sync.RWMutex + m map[string][]byte +} + +func (s *Store) Put(_ context.Context, key string, value []byte) error { + s.mu.Lock() + defer s.mu.Unlock() + s.m[key] = value + return nil +} + +func (s *Store) Get(_ context.Context, key string) (value []byte, err error) { + s.mu.RLock() + defer s.mu.RUnlock() + v, ok := s.m[key] + if !ok { + return nil, api.ErrStateNotFound + } + return v, nil +} + +func (s *Store) List(_ context.Context, startInclusive string, endExclusive string) (keys []string, err error) { + s.mu.RLock() + defer s.mu.RUnlock() + + for k := range s.m { + if k >= startInclusive && k < endExclusive { + keys = append(keys, k) + } + } + + sort.Strings(keys) + return keys, nil +} + +func (s *Store) Delete(_ context.Context, key string) error { + s.mu.Lock() + defer s.mu.Unlock() + delete(s.m, key) + return nil +} + +func (s *Store) Close() error { + return nil +} + +func NewMemoryStateStore() api.StateStore { + return &Store{ + m: make(map[string][]byte), + } +} diff --git a/fs/statestore/memory/memorystatestore_test.go b/fs/statestore/memory/memorystatestore_test.go new file mode 100644 index 00000000..643ec300 --- /dev/null +++ b/fs/statestore/memory/memorystatestore_test.go @@ -0,0 +1,20 @@ +package memory + +import ( + "context" + "github.com/functionstream/function-stream/fs/api" + "github.com/stretchr/testify/require" + "testing" +) + +func TestMemoryStateStore(t *testing.T) { + s := NewMemoryStateStore() + ctx := context.Background() + _, err := s.Get(ctx, "key") + require.Error(t, err, api.ErrStateNotFound) + err = s.Put(ctx, "key", []byte("value")) + require.Nil(t, err) + value, err := s.Get(ctx, "key") + require.Nil(t, err) + require.Equal(t, "value", string(value)) +} diff --git a/fs/api/func_ctx.go b/fsold/api/func_ctx.go similarity index 94% rename from fs/api/func_ctx.go rename to fsold/api/func_ctx.go index 52a8a569..39c7e98b 100644 --- a/fs/api/func_ctx.go +++ b/fsold/api/func_ctx.go @@ -19,7 +19,7 @@ package api import ( "context" - "github.com/functionstream/function-stream/fs/contube" + "github.com/functionstream/function-stream/fsold/contube" ) type FunctionContext interface { diff --git a/fs/api/instance.go b/fsold/api/instance.go similarity index 95% rename from fs/api/instance.go rename to fsold/api/instance.go index 01e27f8f..1787ee69 100644 --- a/fs/api/instance.go +++ b/fsold/api/instance.go @@ -19,7 +19,7 @@ package api import ( "github.com/functionstream/function-stream/common" "github.com/functionstream/function-stream/common/model" - "github.com/functionstream/function-stream/fs/contube" + "github.com/functionstream/function-stream/fsold/contube" "golang.org/x/net/context" ) diff --git a/fs/api/package.go b/fsold/api/package.go similarity index 100% rename from fs/api/package.go rename to fsold/api/package.go diff --git a/fs/api/runtime.go b/fsold/api/runtime.go similarity index 93% rename from fs/api/runtime.go rename to fsold/api/runtime.go index 03cde4cf..1192c5ba 100644 --- a/fs/api/runtime.go +++ b/fsold/api/runtime.go @@ -18,7 +18,7 @@ package api import ( "github.com/functionstream/function-stream/common/model" - "github.com/functionstream/function-stream/fs/contube" + "github.com/functionstream/function-stream/fsold/contube" ) type FunctionRuntime interface { diff --git a/fs/api/statestore.go b/fsold/api/statestore.go similarity index 100% rename from fs/api/statestore.go rename to fsold/api/statestore.go diff --git a/fs/contube/contube.go b/fsold/contube/contube.go similarity index 100% rename from fs/contube/contube.go rename to fsold/contube/contube.go diff --git a/fs/contube/http.go b/fsold/contube/http.go similarity index 100% rename from fs/contube/http.go rename to fsold/contube/http.go diff --git a/fs/contube/http_test.go b/fsold/contube/http_test.go similarity index 100% rename from fs/contube/http_test.go rename to fsold/contube/http_test.go diff --git a/fs/contube/memory.go b/fsold/contube/memory.go similarity index 100% rename from fs/contube/memory.go rename to fsold/contube/memory.go diff --git a/fs/contube/memory_test.go b/fsold/contube/memory_test.go similarity index 100% rename from fs/contube/memory_test.go rename to fsold/contube/memory_test.go diff --git a/fs/contube/nats.go b/fsold/contube/nats.go similarity index 100% rename from fs/contube/nats.go rename to fsold/contube/nats.go diff --git a/fs/contube/pulsar.go b/fsold/contube/pulsar.go similarity index 100% rename from fs/contube/pulsar.go rename to fsold/contube/pulsar.go diff --git a/fs/func_ctx_impl.go b/fsold/func_ctx_impl.go similarity index 94% rename from fs/func_ctx_impl.go rename to fsold/func_ctx_impl.go index c57d6971..8a082933 100644 --- a/fs/func_ctx_impl.go +++ b/fsold/func_ctx_impl.go @@ -14,15 +14,15 @@ * limitations under the License. */ -package fs +package fsold import ( "context" "github.com/functionstream/function-stream/common/model" - "github.com/functionstream/function-stream/fs/api" - "github.com/functionstream/function-stream/fs/contube" + "github.com/functionstream/function-stream/fsold/api" + "github.com/functionstream/function-stream/fsold/contube" "github.com/pkg/errors" ) diff --git a/fs/func_ctx_impl_test.go b/fsold/func_ctx_impl_test.go similarity index 98% rename from fs/func_ctx_impl_test.go rename to fsold/func_ctx_impl_test.go index e6eec03b..4be29a39 100644 --- a/fs/func_ctx_impl_test.go +++ b/fsold/func_ctx_impl_test.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package fs +package fsold import ( "context" diff --git a/fs/instance_impl.go b/fsold/instance_impl.go similarity index 97% rename from fs/instance_impl.go rename to fsold/instance_impl.go index b5b0e046..3e461611 100644 --- a/fs/instance_impl.go +++ b/fsold/instance_impl.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package fs +package fsold import ( "context" @@ -22,8 +22,8 @@ import ( "github.com/functionstream/function-stream/common" "github.com/functionstream/function-stream/common/model" - "github.com/functionstream/function-stream/fs/api" - "github.com/functionstream/function-stream/fs/contube" + "github.com/functionstream/function-stream/fsold/api" + "github.com/functionstream/function-stream/fsold/contube" "github.com/pkg/errors" ) diff --git a/fs/instance_impl_test.go b/fsold/instance_impl_test.go similarity index 99% rename from fs/instance_impl_test.go rename to fsold/instance_impl_test.go index 81362f43..451062f4 100644 --- a/fs/instance_impl_test.go +++ b/fsold/instance_impl_test.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package fs +package fsold import ( "testing" diff --git a/fsold/manager.go b/fsold/manager.go new file mode 100644 index 00000000..36d2a580 --- /dev/null +++ b/fsold/manager.go @@ -0,0 +1,366 @@ +/* + * Copyright 2024 Function Stream Org. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package fsold + +import ( + "context" + "fmt" + "math/rand" + "strconv" + "sync" + + "github.com/functionstream/function-stream/fsold/statestore" + + "github.com/functionstream/function-stream/common/config" + _package "github.com/functionstream/function-stream/fsold/package" + + "github.com/functionstream/function-stream/common" + "github.com/functionstream/function-stream/common/model" + "github.com/functionstream/function-stream/fsold/api" + "github.com/functionstream/function-stream/fsold/contube" + "github.com/go-logr/logr" + "github.com/pkg/errors" +) + +type FunctionManager interface { + StartFunction(f *model.Function) error + DeleteFunction(namespace, name string) error + ListFunctions() []string + ProduceEvent(name string, event contube.Record) error + ConsumeEvent(name string) (contube.Record, error) + GetStateStore() (api.StateStore, error) + Close() error +} + +type functionManagerImpl struct { + options *managerOptions + functions map[common.NamespacedName][]api.FunctionInstance //TODO: Use sync.map + functionsLock sync.Mutex + log *common.Logger +} + +type managerOptions struct { + tubeFactoryMap map[string]contube.TubeFactory + runtimeFactoryMap map[string]api.FunctionRuntimeFactory + instanceFactory api.FunctionInstanceFactory + stateStoreFactory api.StateStoreFactory + queueFactory contube.TubeFactory + packageLoader api.PackageLoader // TODO: Need to set it + log *logr.Logger +} + +type ManagerOption interface { + apply(option *managerOptions) (*managerOptions, error) +} + +type managerOptionFunc func(*managerOptions) (*managerOptions, error) + +func (f managerOptionFunc) apply(c *managerOptions) (*managerOptions, error) { + return f(c) +} + +func WithTubeFactory(name string, factory contube.TubeFactory) ManagerOption { + return managerOptionFunc(func(c *managerOptions) (*managerOptions, error) { + c.tubeFactoryMap[name] = factory + return c, nil + }) +} +func WithQueueFactory(factory contube.TubeFactory) ManagerOption { + return managerOptionFunc(func(c *managerOptions) (*managerOptions, error) { + c.queueFactory = factory + return c, nil + }) +} + +func WithRuntimeFactory(name string, factory api.FunctionRuntimeFactory) ManagerOption { + return managerOptionFunc(func(c *managerOptions) (*managerOptions, error) { + c.runtimeFactoryMap[name] = factory + return c, nil + }) +} + +func WithInstanceFactory(factory api.FunctionInstanceFactory) ManagerOption { + return managerOptionFunc(func(c *managerOptions) (*managerOptions, error) { + c.instanceFactory = factory + return c, nil + }) +} + +func WithStateStoreFactory(storeFactory api.StateStoreFactory) ManagerOption { + return managerOptionFunc(func(c *managerOptions) (*managerOptions, error) { + c.stateStoreFactory = storeFactory + return c, nil + }) +} + +func WithLogger(log *logr.Logger) ManagerOption { + return managerOptionFunc(func(c *managerOptions) (*managerOptions, error) { + c.log = log + return c, nil + }) +} + +func WithPackageLoader(loader api.PackageLoader) ManagerOption { + return managerOptionFunc(func(c *managerOptions) (*managerOptions, error) { + c.packageLoader = loader + return c, nil + }) +} + +func NewFunctionManager(opts ...ManagerOption) (FunctionManager, error) { + options := &managerOptions{ + tubeFactoryMap: make(map[string]contube.TubeFactory), + runtimeFactoryMap: make(map[string]api.FunctionRuntimeFactory), + } + options.instanceFactory = NewDefaultInstanceFactory() + for _, o := range opts { + _, err := o.apply(options) + if err != nil { + return nil, err + } + } + var log *common.Logger + if options.log == nil { + log = common.NewDefaultLogger() + } else { + log = common.NewLogger(options.log) + } + loadedRuntimeFact := make([]string, 0, len(options.runtimeFactoryMap)) + for k := range options.runtimeFactoryMap { + loadedRuntimeFact = append(loadedRuntimeFact, k) + } + loadedTubeFact := make([]string, 0, len(options.tubeFactoryMap)) + for k := range options.tubeFactoryMap { + loadedTubeFact = append(loadedTubeFact, k) + } + if options.packageLoader == nil { + options.packageLoader = _package.NewDefaultPackageLoader() + } + if options.stateStoreFactory == nil { + if fact, err := statestore.NewDefaultPebbleStateStoreFactory(); err != nil { + return nil, err + } else { + options.stateStoreFactory = fact + } + } + log.Info("Function manager created", "runtime-factories", loadedRuntimeFact, + "tube-factories", loadedTubeFact) + return &functionManagerImpl{ + options: options, + functions: make(map[common.NamespacedName][]api.FunctionInstance), + log: log, + }, nil +} + +func (fm *functionManagerImpl) getTubeFactory(tubeConfig *model.TubeConfig) (contube.TubeFactory, error) { + factory, exist := fm.options.tubeFactoryMap[tubeConfig.Type] + if !exist { + return nil, fmt.Errorf("failed to get tube factory: %w, type: %s", common.ErrorTubeFactoryNotFound, tubeConfig.Type) + } + return factory, nil +} + +func (fm *functionManagerImpl) getRuntimeFactory(t string) (api.FunctionRuntimeFactory, error) { + factory, exist := fm.options.runtimeFactoryMap[t] + if !exist { + return nil, fmt.Errorf("failed to get runtime factory: %w, type: %s", common.ErrorRuntimeFactoryNotFound, t) + } + return factory, nil +} + +func generateRuntimeConfig(ctx context.Context, p api.Package, f *model.Function) (*model.RuntimeConfig, error) { + log := common.GetLogger(ctx) + rc := &model.RuntimeConfig{} + if p == _package.EmptyPackage { + return &f.Runtime, nil + } + supportedRuntimeConf := p.GetSupportedRuntimeConfig() + rcMap := map[string]*model.RuntimeConfig{} + for k, v := range supportedRuntimeConf { + if v.Type == "" { + log.Warn("Package supported runtime type is empty. Ignore it.", "index", k, "package", f.Package) + continue + } + vCopy := v + rcMap[v.Type] = &vCopy + } + if len(rcMap) == 0 { + return nil, common.ErrorPackageNoSupportedRuntime + } + defaultRC := &supportedRuntimeConf[0] + if f.Runtime.Type == "" { + rc.Type = defaultRC.Type + } else { + if r, exist := rcMap[f.Runtime.Type]; exist { + defaultRC = r + } else { + return nil, fmt.Errorf("runtime type '%s' is not supported by package '%s'", f.Runtime.Type, f.Package) + } + rc.Type = f.Runtime.Type + } + rc.Config = config.MergeConfig(defaultRC.Config, f.Runtime.Config) + return rc, nil +} + +func (fm *functionManagerImpl) StartFunction(f *model.Function) error { // TODO: Shouldn't use pointer here + if err := f.Validate(); err != nil { + return err + } + fm.functionsLock.Lock() + if _, exist := fm.functions[common.GetNamespacedName(f.Namespace, f.Name)]; exist { + fm.functionsLock.Unlock() + return common.ErrorFunctionExists + } + fm.functions[common.GetNamespacedName(f.Namespace, f.Name)] = make([]api.FunctionInstance, f.Replicas) + fm.functionsLock.Unlock() + + for i := int32(0); i < f.Replicas; i++ { + p, err := fm.options.packageLoader.Load(f.Package) + if err != nil { + return err + } + runtimeConfig, err := generateRuntimeConfig(context.Background(), p, f) + if err != nil { + return fmt.Errorf("failed to generate runtime config: %v", err) + } + + store, err := fm.options.stateStoreFactory.NewStateStore(f) + if err != nil { + return fmt.Errorf("failed to create state store: %w", err) + } + + funcCtx := newFuncCtxImpl(f, store) + instanceLogger := fm.log.SubLogger("functionName", f.Name, "instanceIndex", int(i), "runtimeType", runtimeConfig.Type) + instance := fm.options.instanceFactory.NewFunctionInstance(f, funcCtx, i, instanceLogger) + fm.functionsLock.Lock() + fm.functions[common.GetNamespacedName(f.Namespace, f.Name)][i] = instance + fm.functionsLock.Unlock() + runtimeFactory, err := fm.getRuntimeFactory(runtimeConfig.Type) + if err != nil { + return err + } + var sources []<-chan contube.Record + for _, t := range f.Sources { + sourceFactory, err := fm.getTubeFactory(&t) + if err != nil { + return err + } + sourceChan, err := sourceFactory.NewSourceTube(instance.Context(), t.Config) + if err != nil { + return fmt.Errorf("failed to create source event queue: %w", err) + } + sources = append(sources, sourceChan) + } + sinkFactory, err := fm.getTubeFactory(&f.Sink) + if err != nil { + return err + } + sink, err := sinkFactory.NewSinkTube(instance.Context(), f.Sink.Config) + if err != nil { + return fmt.Errorf("failed to create sink event queue: %w", err) + } + funcCtx.setSink(sink) + + runtime, err := runtimeFactory.NewFunctionRuntime(instance, runtimeConfig) + if err != nil { + return fmt.Errorf("failed to create runtime: %w", err) + } + fm.log.Info("Starting function instance", "function", f) + + go instance.Run(runtime, sources, sink) + } + return nil +} + +func (fm *functionManagerImpl) DeleteFunction(namespace, name string) error { + fm.functionsLock.Lock() + defer fm.functionsLock.Unlock() + instances, exist := fm.functions[common.GetNamespacedName(namespace, name)] + if !exist { + return common.ErrorFunctionNotFound + } + delete(fm.functions, common.GetNamespacedName(namespace, name)) + for _, instance := range instances { + instance.Stop() + } + return nil +} + +func (fm *functionManagerImpl) ListFunctions() (result []string) { + fm.functionsLock.Lock() + defer fm.functionsLock.Unlock() + result = make([]string, len(fm.functions)) + i := 0 + for k := range fm.functions { + result[i] = k.String() + i++ + } + return +} + +func (fm *functionManagerImpl) ProduceEvent(name string, event contube.Record) error { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + factory, ok := fm.options.tubeFactoryMap[common.MemoryTubeType] + if !ok { + return errors.New("memory tube factory not found") + } + c, err := factory.NewSinkTube(ctx, (&contube.SinkQueueConfig{Topic: name}).ToConfigMap()) + if err != nil { + return err + } + c <- event + return nil +} + +func (fm *functionManagerImpl) ConsumeEvent(name string) (contube.Record, error) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + factory, ok := fm.options.tubeFactoryMap[common.MemoryTubeType] + if !ok { + return nil, errors.New("memory tube factory not found") + } + c, err := factory.NewSourceTube(ctx, (&contube.SourceQueueConfig{ + Topics: []string{name}, SubName: "consume-" + strconv.Itoa(rand.Int())}).ToConfigMap()) + if err != nil { + return nil, err + } + return <-c, nil +} + +// GetStateStore returns the state store used by the function manager +// Return nil if no state store is configured +func (fm *functionManagerImpl) GetStateStore() (api.StateStore, error) { + return fm.options.stateStoreFactory.NewStateStore(nil) +} + +func (fm *functionManagerImpl) Close() error { + fm.functionsLock.Lock() + defer fm.functionsLock.Unlock() + log := common.NewDefaultLogger() + for _, instances := range fm.functions { + for _, instance := range instances { + instance.Stop() + } + } + if fm.options.stateStoreFactory != nil { + if err := fm.options.stateStoreFactory.Close(); err != nil { + log.Error(err, "failed to close state store") + } + } + return nil +} diff --git a/fsold/manager_test.go b/fsold/manager_test.go new file mode 100644 index 00000000..9d9a9fc6 --- /dev/null +++ b/fsold/manager_test.go @@ -0,0 +1,97 @@ +/* + * Copyright 2024 Function Stream Org. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package fsold + +import ( + "context" + "testing" + + "github.com/functionstream/function-stream/common" + "github.com/functionstream/function-stream/common/model" + "github.com/stretchr/testify/assert" +) + +// Mock implementations of the interfaces and structs +type MockPackage struct { + runtimeConfigs []model.RuntimeConfig +} + +func (m *MockPackage) GetSupportedRuntimeConfig() []model.RuntimeConfig { + return m.runtimeConfigs +} + +func TestGenerateRuntimeConfig_EmptySupportedRuntimeConfig(t *testing.T) { + ctx := context.Background() + p := &MockPackage{runtimeConfigs: []model.RuntimeConfig{}} + f := &model.Function{} + + _, err := generateRuntimeConfig(ctx, p, f) + assert.NotNil(t, err) + assert.Equal(t, common.ErrorPackageNoSupportedRuntime, err) +} + +func TestGenerateRuntimeConfig_EmptyFunctionRuntimeType(t *testing.T) { + ctx := context.Background() + p := &MockPackage{ + runtimeConfigs: []model.RuntimeConfig{ + {Type: "runtime1", Config: map[string]interface{}{"key1": "value1"}}, + }, + } + f := &model.Function{ + Runtime: model.RuntimeConfig{}, + } + + rc, err := generateRuntimeConfig(ctx, p, f) + assert.Nil(t, err) + assert.Equal(t, "runtime1", rc.Type) + assert.Equal(t, "value1", rc.Config["key1"]) +} + +func TestGenerateRuntimeConfig_UnsupportedFunctionRuntimeType(t *testing.T) { + ctx := context.Background() + p := &MockPackage{ + runtimeConfigs: []model.RuntimeConfig{ + {Type: "runtime1", Config: map[string]interface{}{"key1": "value1"}}, + }, + } + f := &model.Function{ + Runtime: model.RuntimeConfig{Type: "unsupported_runtime"}, + } + + _, err := generateRuntimeConfig(ctx, p, f) + assert.NotNil(t, err) + assert.Equal(t, "runtime type 'unsupported_runtime' is not supported by package ''", err.Error()) +} + +func TestGenerateRuntimeConfig_SupportedFunctionRuntimeType(t *testing.T) { + ctx := context.Background() + p := &MockPackage{ + runtimeConfigs: []model.RuntimeConfig{ + {Type: "runtime1", Config: map[string]interface{}{"key1": "value1"}}, + {Type: "runtime2", Config: map[string]interface{}{"key2": "value2"}}, + }, + } + f := &model.Function{ + Runtime: model.RuntimeConfig{Type: "runtime2", Config: map[string]interface{}{"key3": "value3"}}, + } + + rc, err := generateRuntimeConfig(ctx, p, f) + assert.Nil(t, err) + assert.Equal(t, "runtime2", rc.Type) + assert.Equal(t, "value2", rc.Config["key2"]) + assert.Equal(t, "value3", rc.Config["key3"]) +} diff --git a/fs/package/package_loader.go b/fsold/package/package_loader.go similarity index 96% rename from fs/package/package_loader.go rename to fsold/package/package_loader.go index 41213531..37feaa71 100644 --- a/fs/package/package_loader.go +++ b/fsold/package/package_loader.go @@ -19,7 +19,7 @@ package _package import ( "github.com/functionstream/function-stream/common" "github.com/functionstream/function-stream/common/model" - "github.com/functionstream/function-stream/fs/api" + "github.com/functionstream/function-stream/fsold/api" ) type WasmPackage struct { diff --git a/fs/runtime/external/model/fs.pb.go b/fsold/runtime/external/model/fs.pb.go similarity index 52% rename from fs/runtime/external/model/fs.pb.go rename to fsold/runtime/external/model/fs.pb.go index fe093f78..8318ae2f 100644 --- a/fs/runtime/external/model/fs.pb.go +++ b/fsold/runtime/external/model/fs.pb.go @@ -17,7 +17,7 @@ // versions: // protoc-gen-go v1.34.2 // protoc v4.25.2 -// source: fs/runtime/external/model/fs.proto +// source: fsold/runtime/external/model/fs.proto package model @@ -46,7 +46,7 @@ type RegisterSchemaRequest struct { func (x *RegisterSchemaRequest) Reset() { *x = RegisterSchemaRequest{} if protoimpl.UnsafeEnabled { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[0] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -59,7 +59,7 @@ func (x *RegisterSchemaRequest) String() string { func (*RegisterSchemaRequest) ProtoMessage() {} func (x *RegisterSchemaRequest) ProtoReflect() protoreflect.Message { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[0] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -72,7 +72,7 @@ func (x *RegisterSchemaRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterSchemaRequest.ProtoReflect.Descriptor instead. func (*RegisterSchemaRequest) Descriptor() ([]byte, []int) { - return file_fs_runtime_external_model_fs_proto_rawDescGZIP(), []int{0} + return file_fsold_runtime_external_model_fs_proto_rawDescGZIP(), []int{0} } func (x *RegisterSchemaRequest) GetSchema() string { @@ -91,7 +91,7 @@ type RegisterSchemaResponse struct { func (x *RegisterSchemaResponse) Reset() { *x = RegisterSchemaResponse{} if protoimpl.UnsafeEnabled { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[1] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -104,7 +104,7 @@ func (x *RegisterSchemaResponse) String() string { func (*RegisterSchemaResponse) ProtoMessage() {} func (x *RegisterSchemaResponse) ProtoReflect() protoreflect.Message { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[1] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -117,7 +117,7 @@ func (x *RegisterSchemaResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterSchemaResponse.ProtoReflect.Descriptor instead. func (*RegisterSchemaResponse) Descriptor() ([]byte, []int) { - return file_fs_runtime_external_model_fs_proto_rawDescGZIP(), []int{1} + return file_fsold_runtime_external_model_fs_proto_rawDescGZIP(), []int{1} } type ReadRequest struct { @@ -129,7 +129,7 @@ type ReadRequest struct { func (x *ReadRequest) Reset() { *x = ReadRequest{} if protoimpl.UnsafeEnabled { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[2] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -142,7 +142,7 @@ func (x *ReadRequest) String() string { func (*ReadRequest) ProtoMessage() {} func (x *ReadRequest) ProtoReflect() protoreflect.Message { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[2] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -155,7 +155,7 @@ func (x *ReadRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReadRequest.ProtoReflect.Descriptor instead. func (*ReadRequest) Descriptor() ([]byte, []int) { - return file_fs_runtime_external_model_fs_proto_rawDescGZIP(), []int{2} + return file_fsold_runtime_external_model_fs_proto_rawDescGZIP(), []int{2} } type Event struct { @@ -170,7 +170,7 @@ type Event struct { func (x *Event) Reset() { *x = Event{} if protoimpl.UnsafeEnabled { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[3] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -183,7 +183,7 @@ func (x *Event) String() string { func (*Event) ProtoMessage() {} func (x *Event) ProtoReflect() protoreflect.Message { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[3] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -196,7 +196,7 @@ func (x *Event) ProtoReflect() protoreflect.Message { // Deprecated: Use Event.ProtoReflect.Descriptor instead. func (*Event) Descriptor() ([]byte, []int) { - return file_fs_runtime_external_model_fs_proto_rawDescGZIP(), []int{3} + return file_fsold_runtime_external_model_fs_proto_rawDescGZIP(), []int{3} } func (x *Event) GetId() int64 { @@ -222,7 +222,7 @@ type WriteResponse struct { func (x *WriteResponse) Reset() { *x = WriteResponse{} if protoimpl.UnsafeEnabled { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[4] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -235,7 +235,7 @@ func (x *WriteResponse) String() string { func (*WriteResponse) ProtoMessage() {} func (x *WriteResponse) ProtoReflect() protoreflect.Message { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[4] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -248,7 +248,7 @@ func (x *WriteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use WriteResponse.ProtoReflect.Descriptor instead. func (*WriteResponse) Descriptor() ([]byte, []int) { - return file_fs_runtime_external_model_fs_proto_rawDescGZIP(), []int{4} + return file_fsold_runtime_external_model_fs_proto_rawDescGZIP(), []int{4} } type StateContext struct { @@ -260,7 +260,7 @@ type StateContext struct { func (x *StateContext) Reset() { *x = StateContext{} if protoimpl.UnsafeEnabled { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[5] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -273,7 +273,7 @@ func (x *StateContext) String() string { func (*StateContext) ProtoMessage() {} func (x *StateContext) ProtoReflect() protoreflect.Message { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[5] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -286,7 +286,7 @@ func (x *StateContext) ProtoReflect() protoreflect.Message { // Deprecated: Use StateContext.ProtoReflect.Descriptor instead. func (*StateContext) Descriptor() ([]byte, []int) { - return file_fs_runtime_external_model_fs_proto_rawDescGZIP(), []int{5} + return file_fsold_runtime_external_model_fs_proto_rawDescGZIP(), []int{5} } type GetStateRequest struct { @@ -301,7 +301,7 @@ type GetStateRequest struct { func (x *GetStateRequest) Reset() { *x = GetStateRequest{} if protoimpl.UnsafeEnabled { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[6] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -314,7 +314,7 @@ func (x *GetStateRequest) String() string { func (*GetStateRequest) ProtoMessage() {} func (x *GetStateRequest) ProtoReflect() protoreflect.Message { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[6] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -327,7 +327,7 @@ func (x *GetStateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStateRequest.ProtoReflect.Descriptor instead. func (*GetStateRequest) Descriptor() ([]byte, []int) { - return file_fs_runtime_external_model_fs_proto_rawDescGZIP(), []int{6} + return file_fsold_runtime_external_model_fs_proto_rawDescGZIP(), []int{6} } func (x *GetStateRequest) GetContext() *StateContext { @@ -355,7 +355,7 @@ type GetStateResponse struct { func (x *GetStateResponse) Reset() { *x = GetStateResponse{} if protoimpl.UnsafeEnabled { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[7] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -368,7 +368,7 @@ func (x *GetStateResponse) String() string { func (*GetStateResponse) ProtoMessage() {} func (x *GetStateResponse) ProtoReflect() protoreflect.Message { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[7] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -381,7 +381,7 @@ func (x *GetStateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStateResponse.ProtoReflect.Descriptor instead. func (*GetStateResponse) Descriptor() ([]byte, []int) { - return file_fs_runtime_external_model_fs_proto_rawDescGZIP(), []int{7} + return file_fsold_runtime_external_model_fs_proto_rawDescGZIP(), []int{7} } func (x *GetStateResponse) GetValue() []byte { @@ -404,7 +404,7 @@ type PutStateRequest struct { func (x *PutStateRequest) Reset() { *x = PutStateRequest{} if protoimpl.UnsafeEnabled { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[8] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -417,7 +417,7 @@ func (x *PutStateRequest) String() string { func (*PutStateRequest) ProtoMessage() {} func (x *PutStateRequest) ProtoReflect() protoreflect.Message { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[8] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -430,7 +430,7 @@ func (x *PutStateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PutStateRequest.ProtoReflect.Descriptor instead. func (*PutStateRequest) Descriptor() ([]byte, []int) { - return file_fs_runtime_external_model_fs_proto_rawDescGZIP(), []int{8} + return file_fsold_runtime_external_model_fs_proto_rawDescGZIP(), []int{8} } func (x *PutStateRequest) GetContext() *StateContext { @@ -463,7 +463,7 @@ type PutStateResponse struct { func (x *PutStateResponse) Reset() { *x = PutStateResponse{} if protoimpl.UnsafeEnabled { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[9] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -476,7 +476,7 @@ func (x *PutStateResponse) String() string { func (*PutStateResponse) ProtoMessage() {} func (x *PutStateResponse) ProtoReflect() protoreflect.Message { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[9] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -489,7 +489,7 @@ func (x *PutStateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PutStateResponse.ProtoReflect.Descriptor instead. func (*PutStateResponse) Descriptor() ([]byte, []int) { - return file_fs_runtime_external_model_fs_proto_rawDescGZIP(), []int{9} + return file_fsold_runtime_external_model_fs_proto_rawDescGZIP(), []int{9} } type ListStatesRequest struct { @@ -505,7 +505,7 @@ type ListStatesRequest struct { func (x *ListStatesRequest) Reset() { *x = ListStatesRequest{} if protoimpl.UnsafeEnabled { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[10] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -518,7 +518,7 @@ func (x *ListStatesRequest) String() string { func (*ListStatesRequest) ProtoMessage() {} func (x *ListStatesRequest) ProtoReflect() protoreflect.Message { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[10] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -531,7 +531,7 @@ func (x *ListStatesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListStatesRequest.ProtoReflect.Descriptor instead. func (*ListStatesRequest) Descriptor() ([]byte, []int) { - return file_fs_runtime_external_model_fs_proto_rawDescGZIP(), []int{10} + return file_fsold_runtime_external_model_fs_proto_rawDescGZIP(), []int{10} } func (x *ListStatesRequest) GetContext() *StateContext { @@ -566,7 +566,7 @@ type ListStatesResponse struct { func (x *ListStatesResponse) Reset() { *x = ListStatesResponse{} if protoimpl.UnsafeEnabled { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[11] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -579,7 +579,7 @@ func (x *ListStatesResponse) String() string { func (*ListStatesResponse) ProtoMessage() {} func (x *ListStatesResponse) ProtoReflect() protoreflect.Message { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[11] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -592,7 +592,7 @@ func (x *ListStatesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListStatesResponse.ProtoReflect.Descriptor instead. func (*ListStatesResponse) Descriptor() ([]byte, []int) { - return file_fs_runtime_external_model_fs_proto_rawDescGZIP(), []int{11} + return file_fsold_runtime_external_model_fs_proto_rawDescGZIP(), []int{11} } func (x *ListStatesResponse) GetKeys() []string { @@ -614,7 +614,7 @@ type DeleteStateRequest struct { func (x *DeleteStateRequest) Reset() { *x = DeleteStateRequest{} if protoimpl.UnsafeEnabled { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[12] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -627,7 +627,7 @@ func (x *DeleteStateRequest) String() string { func (*DeleteStateRequest) ProtoMessage() {} func (x *DeleteStateRequest) ProtoReflect() protoreflect.Message { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[12] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -640,7 +640,7 @@ func (x *DeleteStateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteStateRequest.ProtoReflect.Descriptor instead. func (*DeleteStateRequest) Descriptor() ([]byte, []int) { - return file_fs_runtime_external_model_fs_proto_rawDescGZIP(), []int{12} + return file_fsold_runtime_external_model_fs_proto_rawDescGZIP(), []int{12} } func (x *DeleteStateRequest) GetContext() *StateContext { @@ -666,7 +666,7 @@ type DeleteStateResponse struct { func (x *DeleteStateResponse) Reset() { *x = DeleteStateResponse{} if protoimpl.UnsafeEnabled { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[13] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -679,7 +679,7 @@ func (x *DeleteStateResponse) String() string { func (*DeleteStateResponse) ProtoMessage() {} func (x *DeleteStateResponse) ProtoReflect() protoreflect.Message { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[13] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -692,7 +692,7 @@ func (x *DeleteStateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteStateResponse.ProtoReflect.Descriptor instead. func (*DeleteStateResponse) Descriptor() ([]byte, []int) { - return file_fs_runtime_external_model_fs_proto_rawDescGZIP(), []int{13} + return file_fsold_runtime_external_model_fs_proto_rawDescGZIP(), []int{13} } type GetConfigRequest struct { @@ -704,7 +704,7 @@ type GetConfigRequest struct { func (x *GetConfigRequest) Reset() { *x = GetConfigRequest{} if protoimpl.UnsafeEnabled { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[14] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -717,7 +717,7 @@ func (x *GetConfigRequest) String() string { func (*GetConfigRequest) ProtoMessage() {} func (x *GetConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[14] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -730,7 +730,7 @@ func (x *GetConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetConfigRequest.ProtoReflect.Descriptor instead. func (*GetConfigRequest) Descriptor() ([]byte, []int) { - return file_fs_runtime_external_model_fs_proto_rawDescGZIP(), []int{14} + return file_fsold_runtime_external_model_fs_proto_rawDescGZIP(), []int{14} } type GetConfigResponse struct { @@ -744,7 +744,7 @@ type GetConfigResponse struct { func (x *GetConfigResponse) Reset() { *x = GetConfigResponse{} if protoimpl.UnsafeEnabled { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[15] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -757,7 +757,7 @@ func (x *GetConfigResponse) String() string { func (*GetConfigResponse) ProtoMessage() {} func (x *GetConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[15] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -770,7 +770,7 @@ func (x *GetConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetConfigResponse.ProtoReflect.Descriptor instead. func (*GetConfigResponse) Descriptor() ([]byte, []int) { - return file_fs_runtime_external_model_fs_proto_rawDescGZIP(), []int{15} + return file_fsold_runtime_external_model_fs_proto_rawDescGZIP(), []int{15} } func (x *GetConfigResponse) GetConfig() map[string]string { @@ -791,7 +791,7 @@ type AckRequest struct { func (x *AckRequest) Reset() { *x = AckRequest{} if protoimpl.UnsafeEnabled { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[16] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -804,7 +804,7 @@ func (x *AckRequest) String() string { func (*AckRequest) ProtoMessage() {} func (x *AckRequest) ProtoReflect() protoreflect.Message { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[16] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -817,7 +817,7 @@ func (x *AckRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AckRequest.ProtoReflect.Descriptor instead. func (*AckRequest) Descriptor() ([]byte, []int) { - return file_fs_runtime_external_model_fs_proto_rawDescGZIP(), []int{16} + return file_fsold_runtime_external_model_fs_proto_rawDescGZIP(), []int{16} } func (x *AckRequest) GetId() int64 { @@ -836,7 +836,7 @@ type AckResponse struct { func (x *AckResponse) Reset() { *x = AckResponse{} if protoimpl.UnsafeEnabled { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[17] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -849,7 +849,7 @@ func (x *AckResponse) String() string { func (*AckResponse) ProtoMessage() {} func (x *AckResponse) ProtoReflect() protoreflect.Message { - mi := &file_fs_runtime_external_model_fs_proto_msgTypes[17] + mi := &file_fsold_runtime_external_model_fs_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -862,179 +862,197 @@ func (x *AckResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AckResponse.ProtoReflect.Descriptor instead. func (*AckResponse) Descriptor() ([]byte, []int) { - return file_fs_runtime_external_model_fs_proto_rawDescGZIP(), []int{17} -} - -var File_fs_runtime_external_model_fs_proto protoreflect.FileDescriptor - -var file_fs_runtime_external_model_fs_proto_rawDesc = []byte{ - 0x0a, 0x22, 0x66, 0x73, 0x2f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2f, 0x65, 0x78, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x66, 0x73, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x22, 0x2f, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, - 0x68, 0x65, 0x6d, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, - 0x6d, 0x61, 0x22, 0x18, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, 0x63, - 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0d, 0x0a, 0x0b, - 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x31, 0x0a, 0x05, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x0f, - 0x0a, 0x0d, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x0e, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, - 0x58, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x33, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x07, - 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x28, 0x0a, 0x10, 0x47, 0x65, 0x74, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x22, 0x6e, 0x0a, 0x0f, 0x50, 0x75, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, - 0x78, 0x74, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x50, 0x75, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x96, 0x01, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, - 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, - 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x53, 0x74, 0x61, + return file_fsold_runtime_external_model_fs_proto_rawDescGZIP(), []int{17} +} + +var File_fsold_runtime_external_model_fs_proto protoreflect.FileDescriptor + +var file_fsold_runtime_external_model_fs_proto_rawDesc = []byte{ + 0x0a, 0x25, 0x66, 0x73, 0x6f, 0x6c, 0x64, 0x2f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2f, + 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x66, + 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x17, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x22, 0x2f, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x22, 0x18, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0d, 0x0a, 0x0b, 0x52, + 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x31, 0x0a, 0x05, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x0f, 0x0a, + 0x0d, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0e, + 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x64, + 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x3f, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, - 0x78, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x63, 0x6c, - 0x75, 0x73, 0x69, 0x76, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65, - 0x6e, 0x64, 0x5f, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x65, 0x6e, 0x64, 0x45, 0x78, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, - 0x22, 0x28, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x22, 0x5b, 0x0a, 0x12, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x33, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, + 0x78, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x22, 0x28, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x7a, + 0x0a, 0x0f, 0x50, 0x75, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x3f, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x78, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x50, 0x75, + 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xa2, + 0x01, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x07, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x15, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, - 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x22, 0x92, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x39, 0x0a, 0x0b, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 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, 0x09, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x1c, 0x0a, 0x0a, 0x41, 0x63, 0x6b, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x02, 0x69, 0x64, 0x22, 0x0d, 0x0a, 0x0b, 0x41, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x8d, 0x05, 0x0a, 0x08, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x59, 0x0a, 0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x12, 0x22, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, 0x63, - 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x04, - 0x52, 0x65, 0x61, 0x64, 0x12, 0x18, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, - 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x12, 0x37, 0x0a, 0x05, 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, 0x12, 0x2e, 0x66, 0x73, - 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x1a, - 0x1a, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x57, 0x72, - 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x03, 0x41, - 0x63, 0x6b, 0x12, 0x17, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x2e, 0x41, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x66, 0x73, - 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x41, 0x63, 0x6b, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x08, 0x50, 0x75, 0x74, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x12, 0x1c, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, - 0x50, 0x75, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1d, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x50, 0x75, - 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, - 0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x66, 0x73, 0x5f, - 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1e, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1d, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x66, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x1b, 0x5a, 0x19, 0x66, 0x73, 0x2f, 0x72, 0x75, 0x6e, 0x74, 0x69, - 0x6d, 0x65, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x6f, 0x64, 0x65, - 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x69, + 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x12, 0x23, + 0x0a, 0x0d, 0x65, 0x6e, 0x64, 0x5f, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x6e, 0x64, 0x45, 0x78, 0x63, 0x6c, 0x75, 0x73, + 0x69, 0x76, 0x65, 0x22, 0x28, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x65, 0x79, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x22, 0x67, 0x0a, + 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x07, 0x63, 0x6f, 0x6e, + 0x74, 0x65, 0x78, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x15, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, 0x0a, + 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x22, 0x9e, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x39, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 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, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x22, 0x1c, 0x0a, 0x0a, 0x41, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, + 0x22, 0x0d, 0x0a, 0x0b, 0x41, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, + 0xe5, 0x06, 0x0a, 0x08, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x71, 0x0a, 0x0e, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x2e, + 0x2e, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, + 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, + 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, + 0x2e, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, + 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, + 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x4c, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x24, 0x2e, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, + 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, + 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x4f, 0x0a, + 0x05, 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, 0x1e, 0x2e, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x1a, 0x26, 0x2e, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, + 0x0a, 0x03, 0x41, 0x63, 0x6b, 0x12, 0x23, 0x2e, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, + 0x41, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x66, 0x75, 0x6e, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x78, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x41, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x5f, 0x0a, 0x08, 0x50, 0x75, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x28, 0x2e, 0x66, + 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x78, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x50, 0x75, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2e, 0x50, 0x75, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x5f, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x28, 0x2e, + 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, + 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x65, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73, + 0x12, 0x2a, 0x2e, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x66, + 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x78, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x0b, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x2b, 0x2e, 0x66, 0x75, 0x6e, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x29, 0x2e, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x66, 0x75, + 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x78, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x1b, 0x5a, 0x19, 0x66, 0x73, 0x2f, 0x72, 0x75, + 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, + 0x6f, 0x64, 0x65, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( - file_fs_runtime_external_model_fs_proto_rawDescOnce sync.Once - file_fs_runtime_external_model_fs_proto_rawDescData = file_fs_runtime_external_model_fs_proto_rawDesc + file_fsold_runtime_external_model_fs_proto_rawDescOnce sync.Once + file_fsold_runtime_external_model_fs_proto_rawDescData = file_fsold_runtime_external_model_fs_proto_rawDesc ) -func file_fs_runtime_external_model_fs_proto_rawDescGZIP() []byte { - file_fs_runtime_external_model_fs_proto_rawDescOnce.Do(func() { - file_fs_runtime_external_model_fs_proto_rawDescData = protoimpl.X.CompressGZIP(file_fs_runtime_external_model_fs_proto_rawDescData) +func file_fsold_runtime_external_model_fs_proto_rawDescGZIP() []byte { + file_fsold_runtime_external_model_fs_proto_rawDescOnce.Do(func() { + file_fsold_runtime_external_model_fs_proto_rawDescData = protoimpl.X.CompressGZIP(file_fsold_runtime_external_model_fs_proto_rawDescData) }) - return file_fs_runtime_external_model_fs_proto_rawDescData -} - -var file_fs_runtime_external_model_fs_proto_msgTypes = make([]protoimpl.MessageInfo, 19) -var file_fs_runtime_external_model_fs_proto_goTypes = []any{ - (*RegisterSchemaRequest)(nil), // 0: fs_external.RegisterSchemaRequest - (*RegisterSchemaResponse)(nil), // 1: fs_external.RegisterSchemaResponse - (*ReadRequest)(nil), // 2: fs_external.ReadRequest - (*Event)(nil), // 3: fs_external.Event - (*WriteResponse)(nil), // 4: fs_external.WriteResponse - (*StateContext)(nil), // 5: fs_external.StateContext - (*GetStateRequest)(nil), // 6: fs_external.GetStateRequest - (*GetStateResponse)(nil), // 7: fs_external.GetStateResponse - (*PutStateRequest)(nil), // 8: fs_external.PutStateRequest - (*PutStateResponse)(nil), // 9: fs_external.PutStateResponse - (*ListStatesRequest)(nil), // 10: fs_external.ListStatesRequest - (*ListStatesResponse)(nil), // 11: fs_external.ListStatesResponse - (*DeleteStateRequest)(nil), // 12: fs_external.DeleteStateRequest - (*DeleteStateResponse)(nil), // 13: fs_external.DeleteStateResponse - (*GetConfigRequest)(nil), // 14: fs_external.GetConfigRequest - (*GetConfigResponse)(nil), // 15: fs_external.GetConfigResponse - (*AckRequest)(nil), // 16: fs_external.AckRequest - (*AckResponse)(nil), // 17: fs_external.AckResponse - nil, // 18: fs_external.GetConfigResponse.ConfigEntry -} -var file_fs_runtime_external_model_fs_proto_depIdxs = []int32{ - 5, // 0: fs_external.GetStateRequest.context:type_name -> fs_external.StateContext - 5, // 1: fs_external.PutStateRequest.context:type_name -> fs_external.StateContext - 5, // 2: fs_external.ListStatesRequest.context:type_name -> fs_external.StateContext - 5, // 3: fs_external.DeleteStateRequest.context:type_name -> fs_external.StateContext - 18, // 4: fs_external.GetConfigResponse.config:type_name -> fs_external.GetConfigResponse.ConfigEntry - 0, // 5: fs_external.Function.RegisterSchema:input_type -> fs_external.RegisterSchemaRequest - 2, // 6: fs_external.Function.Read:input_type -> fs_external.ReadRequest - 3, // 7: fs_external.Function.Write:input_type -> fs_external.Event - 16, // 8: fs_external.Function.Ack:input_type -> fs_external.AckRequest - 8, // 9: fs_external.Function.PutState:input_type -> fs_external.PutStateRequest - 6, // 10: fs_external.Function.GetState:input_type -> fs_external.GetStateRequest - 10, // 11: fs_external.Function.ListStates:input_type -> fs_external.ListStatesRequest - 12, // 12: fs_external.Function.DeleteState:input_type -> fs_external.DeleteStateRequest - 14, // 13: fs_external.Function.GetConfig:input_type -> fs_external.GetConfigRequest - 1, // 14: fs_external.Function.RegisterSchema:output_type -> fs_external.RegisterSchemaResponse - 3, // 15: fs_external.Function.Read:output_type -> fs_external.Event - 4, // 16: fs_external.Function.Write:output_type -> fs_external.WriteResponse - 17, // 17: fs_external.Function.Ack:output_type -> fs_external.AckResponse - 9, // 18: fs_external.Function.PutState:output_type -> fs_external.PutStateResponse - 7, // 19: fs_external.Function.GetState:output_type -> fs_external.GetStateResponse - 11, // 20: fs_external.Function.ListStates:output_type -> fs_external.ListStatesResponse - 13, // 21: fs_external.Function.DeleteState:output_type -> fs_external.DeleteStateResponse - 15, // 22: fs_external.Function.GetConfig:output_type -> fs_external.GetConfigResponse + return file_fsold_runtime_external_model_fs_proto_rawDescData +} + +var file_fsold_runtime_external_model_fs_proto_msgTypes = make([]protoimpl.MessageInfo, 19) +var file_fsold_runtime_external_model_fs_proto_goTypes = []any{ + (*RegisterSchemaRequest)(nil), // 0: functionstream_external.RegisterSchemaRequest + (*RegisterSchemaResponse)(nil), // 1: functionstream_external.RegisterSchemaResponse + (*ReadRequest)(nil), // 2: functionstream_external.ReadRequest + (*Event)(nil), // 3: functionstream_external.Event + (*WriteResponse)(nil), // 4: functionstream_external.WriteResponse + (*StateContext)(nil), // 5: functionstream_external.StateContext + (*GetStateRequest)(nil), // 6: functionstream_external.GetStateRequest + (*GetStateResponse)(nil), // 7: functionstream_external.GetStateResponse + (*PutStateRequest)(nil), // 8: functionstream_external.PutStateRequest + (*PutStateResponse)(nil), // 9: functionstream_external.PutStateResponse + (*ListStatesRequest)(nil), // 10: functionstream_external.ListStatesRequest + (*ListStatesResponse)(nil), // 11: functionstream_external.ListStatesResponse + (*DeleteStateRequest)(nil), // 12: functionstream_external.DeleteStateRequest + (*DeleteStateResponse)(nil), // 13: functionstream_external.DeleteStateResponse + (*GetConfigRequest)(nil), // 14: functionstream_external.GetConfigRequest + (*GetConfigResponse)(nil), // 15: functionstream_external.GetConfigResponse + (*AckRequest)(nil), // 16: functionstream_external.AckRequest + (*AckResponse)(nil), // 17: functionstream_external.AckResponse + nil, // 18: functionstream_external.GetConfigResponse.ConfigEntry +} +var file_fsold_runtime_external_model_fs_proto_depIdxs = []int32{ + 5, // 0: functionstream_external.GetStateRequest.context:type_name -> functionstream_external.StateContext + 5, // 1: functionstream_external.PutStateRequest.context:type_name -> functionstream_external.StateContext + 5, // 2: functionstream_external.ListStatesRequest.context:type_name -> functionstream_external.StateContext + 5, // 3: functionstream_external.DeleteStateRequest.context:type_name -> functionstream_external.StateContext + 18, // 4: functionstream_external.GetConfigResponse.config:type_name -> functionstream_external.GetConfigResponse.ConfigEntry + 0, // 5: functionstream_external.Function.RegisterSchema:input_type -> functionstream_external.RegisterSchemaRequest + 2, // 6: functionstream_external.Function.Read:input_type -> functionstream_external.ReadRequest + 3, // 7: functionstream_external.Function.Write:input_type -> functionstream_external.Event + 16, // 8: functionstream_external.Function.Ack:input_type -> functionstream_external.AckRequest + 8, // 9: functionstream_external.Function.PutState:input_type -> functionstream_external.PutStateRequest + 6, // 10: functionstream_external.Function.GetState:input_type -> functionstream_external.GetStateRequest + 10, // 11: functionstream_external.Function.ListStates:input_type -> functionstream_external.ListStatesRequest + 12, // 12: functionstream_external.Function.DeleteState:input_type -> functionstream_external.DeleteStateRequest + 14, // 13: functionstream_external.Function.GetConfig:input_type -> functionstream_external.GetConfigRequest + 1, // 14: functionstream_external.Function.RegisterSchema:output_type -> functionstream_external.RegisterSchemaResponse + 3, // 15: functionstream_external.Function.Read:output_type -> functionstream_external.Event + 4, // 16: functionstream_external.Function.Write:output_type -> functionstream_external.WriteResponse + 17, // 17: functionstream_external.Function.Ack:output_type -> functionstream_external.AckResponse + 9, // 18: functionstream_external.Function.PutState:output_type -> functionstream_external.PutStateResponse + 7, // 19: functionstream_external.Function.GetState:output_type -> functionstream_external.GetStateResponse + 11, // 20: functionstream_external.Function.ListStates:output_type -> functionstream_external.ListStatesResponse + 13, // 21: functionstream_external.Function.DeleteState:output_type -> functionstream_external.DeleteStateResponse + 15, // 22: functionstream_external.Function.GetConfig:output_type -> functionstream_external.GetConfigResponse 14, // [14:23] is the sub-list for method output_type 5, // [5:14] is the sub-list for method input_type 5, // [5:5] is the sub-list for extension type_name @@ -1042,13 +1060,13 @@ var file_fs_runtime_external_model_fs_proto_depIdxs = []int32{ 0, // [0:5] is the sub-list for field type_name } -func init() { file_fs_runtime_external_model_fs_proto_init() } -func file_fs_runtime_external_model_fs_proto_init() { - if File_fs_runtime_external_model_fs_proto != nil { +func init() { file_fsold_runtime_external_model_fs_proto_init() } +func file_fsold_runtime_external_model_fs_proto_init() { + if File_fsold_runtime_external_model_fs_proto != nil { return } if !protoimpl.UnsafeEnabled { - file_fs_runtime_external_model_fs_proto_msgTypes[0].Exporter = func(v any, i int) any { + file_fsold_runtime_external_model_fs_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*RegisterSchemaRequest); i { case 0: return &v.state @@ -1060,7 +1078,7 @@ func file_fs_runtime_external_model_fs_proto_init() { return nil } } - file_fs_runtime_external_model_fs_proto_msgTypes[1].Exporter = func(v any, i int) any { + file_fsold_runtime_external_model_fs_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*RegisterSchemaResponse); i { case 0: return &v.state @@ -1072,7 +1090,7 @@ func file_fs_runtime_external_model_fs_proto_init() { return nil } } - file_fs_runtime_external_model_fs_proto_msgTypes[2].Exporter = func(v any, i int) any { + file_fsold_runtime_external_model_fs_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*ReadRequest); i { case 0: return &v.state @@ -1084,7 +1102,7 @@ func file_fs_runtime_external_model_fs_proto_init() { return nil } } - file_fs_runtime_external_model_fs_proto_msgTypes[3].Exporter = func(v any, i int) any { + file_fsold_runtime_external_model_fs_proto_msgTypes[3].Exporter = func(v any, i int) any { switch v := v.(*Event); i { case 0: return &v.state @@ -1096,7 +1114,7 @@ func file_fs_runtime_external_model_fs_proto_init() { return nil } } - file_fs_runtime_external_model_fs_proto_msgTypes[4].Exporter = func(v any, i int) any { + file_fsold_runtime_external_model_fs_proto_msgTypes[4].Exporter = func(v any, i int) any { switch v := v.(*WriteResponse); i { case 0: return &v.state @@ -1108,7 +1126,7 @@ func file_fs_runtime_external_model_fs_proto_init() { return nil } } - file_fs_runtime_external_model_fs_proto_msgTypes[5].Exporter = func(v any, i int) any { + file_fsold_runtime_external_model_fs_proto_msgTypes[5].Exporter = func(v any, i int) any { switch v := v.(*StateContext); i { case 0: return &v.state @@ -1120,7 +1138,7 @@ func file_fs_runtime_external_model_fs_proto_init() { return nil } } - file_fs_runtime_external_model_fs_proto_msgTypes[6].Exporter = func(v any, i int) any { + file_fsold_runtime_external_model_fs_proto_msgTypes[6].Exporter = func(v any, i int) any { switch v := v.(*GetStateRequest); i { case 0: return &v.state @@ -1132,7 +1150,7 @@ func file_fs_runtime_external_model_fs_proto_init() { return nil } } - file_fs_runtime_external_model_fs_proto_msgTypes[7].Exporter = func(v any, i int) any { + file_fsold_runtime_external_model_fs_proto_msgTypes[7].Exporter = func(v any, i int) any { switch v := v.(*GetStateResponse); i { case 0: return &v.state @@ -1144,7 +1162,7 @@ func file_fs_runtime_external_model_fs_proto_init() { return nil } } - file_fs_runtime_external_model_fs_proto_msgTypes[8].Exporter = func(v any, i int) any { + file_fsold_runtime_external_model_fs_proto_msgTypes[8].Exporter = func(v any, i int) any { switch v := v.(*PutStateRequest); i { case 0: return &v.state @@ -1156,7 +1174,7 @@ func file_fs_runtime_external_model_fs_proto_init() { return nil } } - file_fs_runtime_external_model_fs_proto_msgTypes[9].Exporter = func(v any, i int) any { + file_fsold_runtime_external_model_fs_proto_msgTypes[9].Exporter = func(v any, i int) any { switch v := v.(*PutStateResponse); i { case 0: return &v.state @@ -1168,7 +1186,7 @@ func file_fs_runtime_external_model_fs_proto_init() { return nil } } - file_fs_runtime_external_model_fs_proto_msgTypes[10].Exporter = func(v any, i int) any { + file_fsold_runtime_external_model_fs_proto_msgTypes[10].Exporter = func(v any, i int) any { switch v := v.(*ListStatesRequest); i { case 0: return &v.state @@ -1180,7 +1198,7 @@ func file_fs_runtime_external_model_fs_proto_init() { return nil } } - file_fs_runtime_external_model_fs_proto_msgTypes[11].Exporter = func(v any, i int) any { + file_fsold_runtime_external_model_fs_proto_msgTypes[11].Exporter = func(v any, i int) any { switch v := v.(*ListStatesResponse); i { case 0: return &v.state @@ -1192,7 +1210,7 @@ func file_fs_runtime_external_model_fs_proto_init() { return nil } } - file_fs_runtime_external_model_fs_proto_msgTypes[12].Exporter = func(v any, i int) any { + file_fsold_runtime_external_model_fs_proto_msgTypes[12].Exporter = func(v any, i int) any { switch v := v.(*DeleteStateRequest); i { case 0: return &v.state @@ -1204,7 +1222,7 @@ func file_fs_runtime_external_model_fs_proto_init() { return nil } } - file_fs_runtime_external_model_fs_proto_msgTypes[13].Exporter = func(v any, i int) any { + file_fsold_runtime_external_model_fs_proto_msgTypes[13].Exporter = func(v any, i int) any { switch v := v.(*DeleteStateResponse); i { case 0: return &v.state @@ -1216,7 +1234,7 @@ func file_fs_runtime_external_model_fs_proto_init() { return nil } } - file_fs_runtime_external_model_fs_proto_msgTypes[14].Exporter = func(v any, i int) any { + file_fsold_runtime_external_model_fs_proto_msgTypes[14].Exporter = func(v any, i int) any { switch v := v.(*GetConfigRequest); i { case 0: return &v.state @@ -1228,7 +1246,7 @@ func file_fs_runtime_external_model_fs_proto_init() { return nil } } - file_fs_runtime_external_model_fs_proto_msgTypes[15].Exporter = func(v any, i int) any { + file_fsold_runtime_external_model_fs_proto_msgTypes[15].Exporter = func(v any, i int) any { switch v := v.(*GetConfigResponse); i { case 0: return &v.state @@ -1240,7 +1258,7 @@ func file_fs_runtime_external_model_fs_proto_init() { return nil } } - file_fs_runtime_external_model_fs_proto_msgTypes[16].Exporter = func(v any, i int) any { + file_fsold_runtime_external_model_fs_proto_msgTypes[16].Exporter = func(v any, i int) any { switch v := v.(*AckRequest); i { case 0: return &v.state @@ -1252,7 +1270,7 @@ func file_fs_runtime_external_model_fs_proto_init() { return nil } } - file_fs_runtime_external_model_fs_proto_msgTypes[17].Exporter = func(v any, i int) any { + file_fsold_runtime_external_model_fs_proto_msgTypes[17].Exporter = func(v any, i int) any { switch v := v.(*AckResponse); i { case 0: return &v.state @@ -1269,18 +1287,18 @@ func file_fs_runtime_external_model_fs_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_fs_runtime_external_model_fs_proto_rawDesc, + RawDescriptor: file_fsold_runtime_external_model_fs_proto_rawDesc, NumEnums: 0, NumMessages: 19, NumExtensions: 0, NumServices: 1, }, - GoTypes: file_fs_runtime_external_model_fs_proto_goTypes, - DependencyIndexes: file_fs_runtime_external_model_fs_proto_depIdxs, - MessageInfos: file_fs_runtime_external_model_fs_proto_msgTypes, + GoTypes: file_fsold_runtime_external_model_fs_proto_goTypes, + DependencyIndexes: file_fsold_runtime_external_model_fs_proto_depIdxs, + MessageInfos: file_fsold_runtime_external_model_fs_proto_msgTypes, }.Build() - File_fs_runtime_external_model_fs_proto = out.File - file_fs_runtime_external_model_fs_proto_rawDesc = nil - file_fs_runtime_external_model_fs_proto_goTypes = nil - file_fs_runtime_external_model_fs_proto_depIdxs = nil + File_fsold_runtime_external_model_fs_proto = out.File + file_fsold_runtime_external_model_fs_proto_rawDesc = nil + file_fsold_runtime_external_model_fs_proto_goTypes = nil + file_fsold_runtime_external_model_fs_proto_depIdxs = nil } diff --git a/fs/runtime/external/model/fs.proto b/fsold/runtime/external/model/fs.proto similarity index 98% rename from fs/runtime/external/model/fs.proto rename to fsold/runtime/external/model/fs.proto index 1c97779f..12c4c8cc 100644 --- a/fs/runtime/external/model/fs.proto +++ b/fsold/runtime/external/model/fs.proto @@ -16,7 +16,7 @@ syntax = "proto3"; option go_package = "fs/runtime/external/model"; -package fs_external; +package functionstream_external; message RegisterSchemaRequest { string schema = 1; diff --git a/fs/runtime/external/model/fs_grpc.pb.go b/fsold/runtime/external/model/fs_grpc.pb.go similarity index 75% rename from fs/runtime/external/model/fs_grpc.pb.go rename to fsold/runtime/external/model/fs_grpc.pb.go index 4064443f..be11a45c 100644 --- a/fs/runtime/external/model/fs_grpc.pb.go +++ b/fsold/runtime/external/model/fs_grpc.pb.go @@ -1,4 +1,23 @@ +// +// Copyright 2024 Function Stream Org. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + // Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v4.25.2 +// source: fsold/runtime/external/model/fs.proto package model @@ -11,8 +30,20 @@ import ( // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + Function_RegisterSchema_FullMethodName = "/functionstream_external.Function/RegisterSchema" + Function_Read_FullMethodName = "/functionstream_external.Function/Read" + Function_Write_FullMethodName = "/functionstream_external.Function/Write" + Function_Ack_FullMethodName = "/functionstream_external.Function/Ack" + Function_PutState_FullMethodName = "/functionstream_external.Function/PutState" + Function_GetState_FullMethodName = "/functionstream_external.Function/GetState" + Function_ListStates_FullMethodName = "/functionstream_external.Function/ListStates" + Function_DeleteState_FullMethodName = "/functionstream_external.Function/DeleteState" + Function_GetConfig_FullMethodName = "/functionstream_external.Function/GetConfig" +) // FunctionClient is the client API for Function service. // @@ -38,8 +69,9 @@ func NewFunctionClient(cc grpc.ClientConnInterface) FunctionClient { } func (c *functionClient) RegisterSchema(ctx context.Context, in *RegisterSchemaRequest, opts ...grpc.CallOption) (*RegisterSchemaResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(RegisterSchemaResponse) - err := c.cc.Invoke(ctx, "/fs_external.Function/RegisterSchema", in, out, opts...) + err := c.cc.Invoke(ctx, Function_RegisterSchema_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -47,8 +79,9 @@ func (c *functionClient) RegisterSchema(ctx context.Context, in *RegisterSchemaR } func (c *functionClient) Read(ctx context.Context, in *ReadRequest, opts ...grpc.CallOption) (*Event, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Event) - err := c.cc.Invoke(ctx, "/fs_external.Function/Read", in, out, opts...) + err := c.cc.Invoke(ctx, Function_Read_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -56,8 +89,9 @@ func (c *functionClient) Read(ctx context.Context, in *ReadRequest, opts ...grpc } func (c *functionClient) Write(ctx context.Context, in *Event, opts ...grpc.CallOption) (*WriteResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(WriteResponse) - err := c.cc.Invoke(ctx, "/fs_external.Function/Write", in, out, opts...) + err := c.cc.Invoke(ctx, Function_Write_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -65,8 +99,9 @@ func (c *functionClient) Write(ctx context.Context, in *Event, opts ...grpc.Call } func (c *functionClient) Ack(ctx context.Context, in *AckRequest, opts ...grpc.CallOption) (*AckResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(AckResponse) - err := c.cc.Invoke(ctx, "/fs_external.Function/Ack", in, out, opts...) + err := c.cc.Invoke(ctx, Function_Ack_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -74,8 +109,9 @@ func (c *functionClient) Ack(ctx context.Context, in *AckRequest, opts ...grpc.C } func (c *functionClient) PutState(ctx context.Context, in *PutStateRequest, opts ...grpc.CallOption) (*PutStateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(PutStateResponse) - err := c.cc.Invoke(ctx, "/fs_external.Function/PutState", in, out, opts...) + err := c.cc.Invoke(ctx, Function_PutState_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -83,8 +119,9 @@ func (c *functionClient) PutState(ctx context.Context, in *PutStateRequest, opts } func (c *functionClient) GetState(ctx context.Context, in *GetStateRequest, opts ...grpc.CallOption) (*GetStateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetStateResponse) - err := c.cc.Invoke(ctx, "/fs_external.Function/GetState", in, out, opts...) + err := c.cc.Invoke(ctx, Function_GetState_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -92,8 +129,9 @@ func (c *functionClient) GetState(ctx context.Context, in *GetStateRequest, opts } func (c *functionClient) ListStates(ctx context.Context, in *ListStatesRequest, opts ...grpc.CallOption) (*ListStatesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListStatesResponse) - err := c.cc.Invoke(ctx, "/fs_external.Function/ListStates", in, out, opts...) + err := c.cc.Invoke(ctx, Function_ListStates_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -101,8 +139,9 @@ func (c *functionClient) ListStates(ctx context.Context, in *ListStatesRequest, } func (c *functionClient) DeleteState(ctx context.Context, in *DeleteStateRequest, opts ...grpc.CallOption) (*DeleteStateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(DeleteStateResponse) - err := c.cc.Invoke(ctx, "/fs_external.Function/DeleteState", in, out, opts...) + err := c.cc.Invoke(ctx, Function_DeleteState_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -110,8 +149,9 @@ func (c *functionClient) DeleteState(ctx context.Context, in *DeleteStateRequest } func (c *functionClient) GetConfig(ctx context.Context, in *GetConfigRequest, opts ...grpc.CallOption) (*GetConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetConfigResponse) - err := c.cc.Invoke(ctx, "/fs_external.Function/GetConfig", in, out, opts...) + err := c.cc.Invoke(ctx, Function_GetConfig_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -120,7 +160,7 @@ func (c *functionClient) GetConfig(ctx context.Context, in *GetConfigRequest, op // FunctionServer is the server API for Function service. // All implementations must embed UnimplementedFunctionServer -// for forward compatibility +// for forward compatibility. type FunctionServer interface { RegisterSchema(context.Context, *RegisterSchemaRequest) (*RegisterSchemaResponse, error) Read(context.Context, *ReadRequest) (*Event, error) @@ -134,9 +174,12 @@ type FunctionServer interface { mustEmbedUnimplementedFunctionServer() } -// UnimplementedFunctionServer must be embedded to have forward compatible implementations. -type UnimplementedFunctionServer struct { -} +// UnimplementedFunctionServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedFunctionServer struct{} func (UnimplementedFunctionServer) RegisterSchema(context.Context, *RegisterSchemaRequest) (*RegisterSchemaResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RegisterSchema not implemented") @@ -166,6 +209,7 @@ func (UnimplementedFunctionServer) GetConfig(context.Context, *GetConfigRequest) return nil, status.Errorf(codes.Unimplemented, "method GetConfig not implemented") } func (UnimplementedFunctionServer) mustEmbedUnimplementedFunctionServer() {} +func (UnimplementedFunctionServer) testEmbeddedByValue() {} // UnsafeFunctionServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to FunctionServer will @@ -175,6 +219,13 @@ type UnsafeFunctionServer interface { } func RegisterFunctionServer(s grpc.ServiceRegistrar, srv FunctionServer) { + // If the following call pancis, it indicates UnimplementedFunctionServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } s.RegisterService(&Function_ServiceDesc, srv) } @@ -188,7 +239,7 @@ func _Function_RegisterSchema_Handler(srv interface{}, ctx context.Context, dec } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/fs_external.Function/RegisterSchema", + FullMethod: Function_RegisterSchema_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(FunctionServer).RegisterSchema(ctx, req.(*RegisterSchemaRequest)) @@ -206,7 +257,7 @@ func _Function_Read_Handler(srv interface{}, ctx context.Context, dec func(inter } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/fs_external.Function/Read", + FullMethod: Function_Read_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(FunctionServer).Read(ctx, req.(*ReadRequest)) @@ -224,7 +275,7 @@ func _Function_Write_Handler(srv interface{}, ctx context.Context, dec func(inte } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/fs_external.Function/Write", + FullMethod: Function_Write_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(FunctionServer).Write(ctx, req.(*Event)) @@ -242,7 +293,7 @@ func _Function_Ack_Handler(srv interface{}, ctx context.Context, dec func(interf } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/fs_external.Function/Ack", + FullMethod: Function_Ack_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(FunctionServer).Ack(ctx, req.(*AckRequest)) @@ -260,7 +311,7 @@ func _Function_PutState_Handler(srv interface{}, ctx context.Context, dec func(i } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/fs_external.Function/PutState", + FullMethod: Function_PutState_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(FunctionServer).PutState(ctx, req.(*PutStateRequest)) @@ -278,7 +329,7 @@ func _Function_GetState_Handler(srv interface{}, ctx context.Context, dec func(i } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/fs_external.Function/GetState", + FullMethod: Function_GetState_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(FunctionServer).GetState(ctx, req.(*GetStateRequest)) @@ -296,7 +347,7 @@ func _Function_ListStates_Handler(srv interface{}, ctx context.Context, dec func } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/fs_external.Function/ListStates", + FullMethod: Function_ListStates_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(FunctionServer).ListStates(ctx, req.(*ListStatesRequest)) @@ -314,7 +365,7 @@ func _Function_DeleteState_Handler(srv interface{}, ctx context.Context, dec fun } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/fs_external.Function/DeleteState", + FullMethod: Function_DeleteState_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(FunctionServer).DeleteState(ctx, req.(*DeleteStateRequest)) @@ -332,7 +383,7 @@ func _Function_GetConfig_Handler(srv interface{}, ctx context.Context, dec func( } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/fs_external.Function/GetConfig", + FullMethod: Function_GetConfig_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(FunctionServer).GetConfig(ctx, req.(*GetConfigRequest)) @@ -344,7 +395,7 @@ func _Function_GetConfig_Handler(srv interface{}, ctx context.Context, dec func( // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var Function_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "fs_external.Function", + ServiceName: "functionstream_external.Function", HandlerType: (*FunctionServer)(nil), Methods: []grpc.MethodDesc{ { @@ -385,5 +436,5 @@ var Function_ServiceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: "fs/runtime/external/model/fs.proto", + Metadata: "fsold/runtime/external/model/fs.proto", } diff --git a/fs/runtime/external/runtime.go b/fsold/runtime/external/runtime.go similarity index 96% rename from fs/runtime/external/runtime.go rename to fsold/runtime/external/runtime.go index 9d7b4673..414ae9d2 100644 --- a/fs/runtime/external/runtime.go +++ b/fsold/runtime/external/runtime.go @@ -27,9 +27,9 @@ import ( funcModel "github.com/functionstream/function-stream/common/model" "github.com/functionstream/function-stream/common" - "github.com/functionstream/function-stream/fs/api" - "github.com/functionstream/function-stream/fs/contube" - "github.com/functionstream/function-stream/fs/runtime/external/model" + "github.com/functionstream/function-stream/fsold/api" + "github.com/functionstream/function-stream/fsold/contube" + "github.com/functionstream/function-stream/fsold/runtime/external/model" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/health" @@ -193,7 +193,7 @@ func NewFactory(lis net.Listener) api.FunctionRuntimeFactory { // Register the health check service healthServer := health.NewServer() grpc_health_v1.RegisterHealthServer(s, healthServer) - healthServer.SetServingStatus("fs_external.Function", grpc_health_v1.HealthCheckResponse_SERVING) + healthServer.SetServingStatus("fs-external.Function", grpc_health_v1.HealthCheckResponse_SERVING) go func() { log.Info("Starting external runtime server") diff --git a/fs/runtime/external/runtime_test.go b/fsold/runtime/external/runtime_test.go similarity index 75% rename from fs/runtime/external/runtime_test.go rename to fsold/runtime/external/runtime_test.go index 06e11c4c..38822324 100644 --- a/fs/runtime/external/runtime_test.go +++ b/fsold/runtime/external/runtime_test.go @@ -20,18 +20,19 @@ import ( "context" "encoding/json" "fmt" + "github.com/stretchr/testify/require" "net" "os" "testing" "time" - "github.com/functionstream/function-stream/fs/statestore" + "github.com/functionstream/function-stream/fsold/statestore" - "github.com/functionstream/function-stream/clients/gofs" + "github.com/functionstream/function-stream/clients/gofsold" "github.com/functionstream/function-stream/common" "github.com/functionstream/function-stream/common/model" - "github.com/functionstream/function-stream/fs" - "github.com/functionstream/function-stream/fs/contube" + "github.com/functionstream/function-stream/fsold" + "github.com/functionstream/function-stream/fsold/contube" "github.com/stretchr/testify/assert" ) @@ -55,39 +56,39 @@ var log = common.NewDefaultLogger() type TestFunction struct { } -func (f *TestFunction) Init(_ gofs.FunctionContext) error { +func (f *TestFunction) Init(_ gofsold.FunctionContext) error { return nil } -func (f *TestFunction) Handle(_ gofs.FunctionContext, event gofs.Event[Person]) (gofs.Event[Person], error) { +func (f *TestFunction) Handle(_ gofsold.FunctionContext, event gofsold.Event[Person]) (gofsold.Event[Person], error) { p := event.Data() p.Money += 1 - return gofs.NewEvent(p), nil + return gofsold.NewEvent(p), nil } type TestCounterFunction struct { } -func (f *TestCounterFunction) Init(ctx gofs.FunctionContext) error { +func (f *TestCounterFunction) Init(ctx gofsold.FunctionContext) error { return nil } -func (f *TestCounterFunction) Handle(_ gofs.FunctionContext, event gofs.Event[Counter]) (gofs.Event[Counter], error) { +func (f *TestCounterFunction) Handle(_ gofsold.FunctionContext, event gofsold.Event[Counter]) (gofsold.Event[Counter], error) { c := event.Data() c.Count += 1 - return gofs.NewEvent(c), nil + return gofsold.NewEvent(c), nil } type TestSource struct { } -func (f *TestSource) Init(_ gofs.FunctionContext) error { +func (f *TestSource) Init(_ gofsold.FunctionContext) error { return nil } -func (f *TestSource) Handle(_ gofs.FunctionContext, emit func(context.Context, gofs.Event[testRecord]) error) error { +func (f *TestSource) Handle(_ gofsold.FunctionContext, emit func(context.Context, gofsold.Event[testRecord]) error) error { for i := 0; i < 10; i++ { - err := emit(context.Background(), gofs.NewEvent(&testRecord{ + err := emit(context.Background(), gofsold.NewEvent(&testRecord{ ID: i, Name: "test", })) @@ -117,22 +118,26 @@ func NewTestModules() *TestModules { } func (t *TestModules) Run() { - err := gofs.NewFSClient(). - Register(gofs.DefaultModule, gofs.WithFunction(t.testFunction)). - Register("counter", gofs.WithFunction(t.testCounter)). - Register("test-source", gofs.WithSource(t.testSource)). - Register("test-sink", gofs.WithSink(t.testSink)). + err := gofsold.NewFSClient(). + Register(gofsold.DefaultModule, gofsold.WithFunction(t.testFunction)). + Register("counter", gofsold.WithFunction(t.testCounter)). + Register("test-source", gofsold.WithSource(t.testSource)). + Register("test-sink", gofsold.WithSink(t.testSink)). Run() if err != nil { log.Error(err, "failed to run mock client") } } +func setupFSTarget(t *testing.T, socketPath string) { + require.NoError(t, os.Setenv("FS_TARGET", "unix:"+socketPath)) +} + //nolint:goconst func TestExternalRuntime(t *testing.T) { testSocketPath := fmt.Sprintf("/tmp/%s.sock", t.Name()) assert.NoError(t, os.RemoveAll(testSocketPath)) - assert.NoError(t, os.Setenv("FS_SOCKET_PATH", testSocketPath)) + setupFSTarget(t, testSocketPath) assert.NoError(t, os.Setenv("FS_FUNCTION_NAME", "test")) lis, err := net.Listen("unix", testSocketPath) assert.NoError(t, err) @@ -140,9 +145,9 @@ func TestExternalRuntime(t *testing.T) { _ = lis.Close() }(lis) - fm, err := fs.NewFunctionManager( - fs.WithRuntimeFactory("external", NewFactory(lis)), - fs.WithTubeFactory("memory", contube.NewMemoryQueueFactory(context.Background())), + fm, err := fsold.NewFunctionManager( + fsold.WithRuntimeFactory("external", NewFactory(lis)), + fsold.WithTubeFactory("memory", contube.NewMemoryQueueFactory(context.Background())), ) if err != nil { t.Fatal(err) @@ -210,7 +215,7 @@ func TestExternalRuntime(t *testing.T) { func TestNonDefaultModule(t *testing.T) { testSocketPath := fmt.Sprintf("/tmp/%s.sock", t.Name()) assert.NoError(t, os.RemoveAll(testSocketPath)) - assert.NoError(t, os.Setenv("FS_SOCKET_PATH", testSocketPath)) + setupFSTarget(t, testSocketPath) assert.NoError(t, os.Setenv("FS_FUNCTION_NAME", "test")) assert.NoError(t, os.Setenv("FS_MODULE_NAME", "counter")) lis, err := net.Listen("unix", testSocketPath) @@ -219,9 +224,9 @@ func TestNonDefaultModule(t *testing.T) { _ = lis.Close() }(lis) - fm, err := fs.NewFunctionManager( - fs.WithRuntimeFactory("external", NewFactory(lis)), - fs.WithTubeFactory("memory", contube.NewMemoryQueueFactory(context.Background())), + fm, err := fsold.NewFunctionManager( + fsold.WithRuntimeFactory("external", NewFactory(lis)), + fsold.WithTubeFactory("memory", contube.NewMemoryQueueFactory(context.Background())), ) if err != nil { t.Fatal(err) @@ -279,7 +284,7 @@ func TestNonDefaultModule(t *testing.T) { func TestExternalSourceModule(t *testing.T) { testSocketPath := fmt.Sprintf("/tmp/%s.sock", t.Name()) assert.NoError(t, os.RemoveAll(testSocketPath)) - assert.NoError(t, os.Setenv("FS_SOCKET_PATH", testSocketPath)) + setupFSTarget(t, testSocketPath) assert.NoError(t, os.Setenv("FS_FUNCTION_NAME", "test")) assert.NoError(t, os.Setenv("FS_MODULE_NAME", "test-source")) lis, err := net.Listen("unix", testSocketPath) @@ -288,10 +293,10 @@ func TestExternalSourceModule(t *testing.T) { _ = lis.Close() }(lis) - fm, err := fs.NewFunctionManager( - fs.WithRuntimeFactory("external", NewFactory(lis)), - fs.WithTubeFactory("memory", contube.NewMemoryQueueFactory(context.Background())), - fs.WithTubeFactory("empty", contube.NewEmptyTubeFactory()), + fm, err := fsold.NewFunctionManager( + fsold.WithRuntimeFactory("external", NewFactory(lis)), + fsold.WithTubeFactory("memory", contube.NewMemoryQueueFactory(context.Background())), + fsold.WithTubeFactory("empty", contube.NewEmptyTubeFactory()), ) if err != nil { t.Fatal(err) @@ -341,11 +346,11 @@ type TestSink struct { sinkCh chan Counter } -func (f *TestSink) Init(_ gofs.FunctionContext) error { +func (f *TestSink) Init(_ gofsold.FunctionContext) error { return nil } -func (f *TestSink) Handle(ctx gofs.FunctionContext, event gofs.Event[Counter]) error { +func (f *TestSink) Handle(ctx gofsold.FunctionContext, event gofsold.Event[Counter]) error { f.sinkCh <- *event.Data() return event.Ack(ctx) } @@ -353,7 +358,7 @@ func (f *TestSink) Handle(ctx gofs.FunctionContext, event gofs.Event[Counter]) e func TestExternalSinkModule(t *testing.T) { testSocketPath := fmt.Sprintf("/tmp/%s.sock", t.Name()) assert.NoError(t, os.RemoveAll(testSocketPath)) - assert.NoError(t, os.Setenv("FS_SOCKET_PATH", testSocketPath)) + setupFSTarget(t, testSocketPath) assert.NoError(t, os.Setenv("FS_FUNCTION_NAME", "test")) assert.NoError(t, os.Setenv("FS_MODULE_NAME", "test-sink")) lis, err := net.Listen("unix", testSocketPath) @@ -362,10 +367,10 @@ func TestExternalSinkModule(t *testing.T) { _ = lis.Close() }(lis) - fm, err := fs.NewFunctionManager( - fs.WithRuntimeFactory("external", NewFactory(lis)), - fs.WithTubeFactory("memory", contube.NewMemoryQueueFactory(context.Background())), - fs.WithTubeFactory("empty", contube.NewEmptyTubeFactory()), + fm, err := fsold.NewFunctionManager( + fsold.WithRuntimeFactory("external", NewFactory(lis)), + fsold.WithTubeFactory("memory", contube.NewMemoryQueueFactory(context.Background())), + fsold.WithTubeFactory("empty", contube.NewEmptyTubeFactory()), ) if err != nil { t.Fatal(err) @@ -428,7 +433,7 @@ func TestExternalSinkModule(t *testing.T) { func TestExternalStatefulModule(t *testing.T) { testSocketPath := fmt.Sprintf("/tmp/%s.sock", t.Name()) assert.NoError(t, os.RemoveAll(testSocketPath)) - assert.NoError(t, os.Setenv("FS_SOCKET_PATH", testSocketPath)) + setupFSTarget(t, testSocketPath) assert.NoError(t, os.Setenv("FS_FUNCTION_NAME", "test")) assert.NoError(t, os.Setenv("FS_MODULE_NAME", "test-stateful")) lis, err := net.Listen("unix", testSocketPath) @@ -440,11 +445,11 @@ func TestExternalStatefulModule(t *testing.T) { storeFactory, err := statestore.NewDefaultPebbleStateStoreFactory() assert.NoError(t, err) - fm, err := fs.NewFunctionManager( - fs.WithRuntimeFactory("external", NewFactory(lis)), - fs.WithTubeFactory("memory", contube.NewMemoryQueueFactory(context.Background())), - fs.WithTubeFactory("empty", contube.NewEmptyTubeFactory()), - fs.WithStateStoreFactory(storeFactory), + fm, err := fsold.NewFunctionManager( + fsold.WithRuntimeFactory("external", NewFactory(lis)), + fsold.WithTubeFactory("memory", contube.NewMemoryQueueFactory(context.Background())), + fsold.WithTubeFactory("empty", contube.NewEmptyTubeFactory()), + fsold.WithStateStoreFactory(storeFactory), ) assert.NoError(t, err) @@ -471,8 +476,8 @@ func TestExternalStatefulModule(t *testing.T) { readyCh := make(chan struct{}) go func() { - err := gofs.NewFSClient().Register("test-stateful", gofs.WithCustom(gofs.NewSimpleCustom( - func(ctx gofs.FunctionContext) error { + err := gofsold.NewFSClient().Register("test-stateful", gofsold.WithCustom(gofsold.NewSimpleCustom( + func(ctx gofsold.FunctionContext) error { err = ctx.PutState(context.Background(), "test-key", []byte("test-value")) if err != nil { log.Error(err, "failed to put state") @@ -500,7 +505,7 @@ func TestFunctionConfig(t *testing.T) { testSocketPath := fmt.Sprintf("/tmp/%s.sock", t.Name()) assert.NoError(t, os.RemoveAll(testSocketPath)) module := "test-function-config" - assert.NoError(t, os.Setenv("FS_SOCKET_PATH", testSocketPath)) + setupFSTarget(t, testSocketPath) assert.NoError(t, os.Setenv("FS_FUNCTION_NAME", "test")) assert.NoError(t, os.Setenv("FS_MODULE_NAME", module)) lis, err := net.Listen("unix", testSocketPath) @@ -512,10 +517,10 @@ func TestFunctionConfig(t *testing.T) { storeFactory, err := statestore.NewDefaultPebbleStateStoreFactory() assert.NoError(t, err) - fm, err := fs.NewFunctionManager( - fs.WithRuntimeFactory("external", NewFactory(lis)), - fs.WithTubeFactory("empty", contube.NewEmptyTubeFactory()), - fs.WithStateStoreFactory(storeFactory), + fm, err := fsold.NewFunctionManager( + fsold.WithRuntimeFactory("external", NewFactory(lis)), + fsold.WithTubeFactory("empty", contube.NewEmptyTubeFactory()), + fsold.WithStateStoreFactory(storeFactory), ) assert.NoError(t, err) @@ -546,8 +551,8 @@ func TestFunctionConfig(t *testing.T) { readyCh := make(chan struct{}) go func() { - err := gofs.NewFSClient().Register(module, gofs.WithCustom(gofs.NewSimpleCustom( - func(ctx gofs.FunctionContext) error { + err := gofsold.NewFSClient().Register(module, gofsold.WithCustom(gofsold.NewSimpleCustom( + func(ctx gofsold.FunctionContext) error { err = ctx.PutState(context.Background(), "test-key", []byte("test-value")) if err != nil { log.Error(err, "failed to put state") diff --git a/fs/runtime/wazero/fs.go b/fsold/runtime/wazero/fs.go similarity index 100% rename from fs/runtime/wazero/fs.go rename to fsold/runtime/wazero/fs.go diff --git a/fs/runtime/wazero/wazero_runtime.go b/fsold/runtime/wazero/wazero_runtime.go similarity index 94% rename from fs/runtime/wazero/wazero_runtime.go rename to fsold/runtime/wazero/wazero_runtime.go index df0971b6..cd9cffe9 100644 --- a/fs/runtime/wazero/wazero_runtime.go +++ b/fsold/runtime/wazero/wazero_runtime.go @@ -22,13 +22,13 @@ import ( "fmt" "os" - "github.com/functionstream/function-stream/clients/gofs" + "github.com/functionstream/function-stream/clients/gofsold" "github.com/functionstream/function-stream/common/model" "github.com/functionstream/function-stream/common" - "github.com/functionstream/function-stream/fs/api" - "github.com/functionstream/function-stream/fs/contube" + "github.com/functionstream/function-stream/fsold/api" + "github.com/functionstream/function-stream/fsold/contube" "github.com/tetratelabs/wazero" wazero_api "github.com/tetratelabs/wazero/api" exp_sys "github.com/tetratelabs/wazero/experimental/sys" @@ -99,7 +99,7 @@ func (f *WazeroFunctionRuntimeFactory) NewFunctionRuntime(instance api.FunctionI } fsConfig := wazero.NewFSConfig().(sysfs.FSConfig).WithSysFSMount(newMemoryFS(fileMap), "") config := wazero.NewModuleConfig(). - WithEnv(gofs.FSFunctionName, common.GetNamespacedName(instance.Definition().Namespace, + WithEnv(gofsold.FSFunctionName, common.GetNamespacedName(instance.Definition().Namespace, instance.Definition().Name).String()). WithStdout(wasmLog).WithStderr(wasmLog).WithFSConfig(fsConfig) diff --git a/fs/statestore/pebble.go b/fsold/statestore/pebble.go similarity index 98% rename from fs/statestore/pebble.go rename to fsold/statestore/pebble.go index 974767ab..c4a0d87c 100644 --- a/fs/statestore/pebble.go +++ b/fsold/statestore/pebble.go @@ -25,7 +25,7 @@ import ( "github.com/functionstream/function-stream/common/model" "github.com/cockroachdb/pebble" - "github.com/functionstream/function-stream/fs/api" + "github.com/functionstream/function-stream/fsold/api" "github.com/pkg/errors" ) diff --git a/fs/statestore/pebble_test.go b/fsold/statestore/pebble_test.go similarity index 90% rename from fs/statestore/pebble_test.go rename to fsold/statestore/pebble_test.go index 725f80f8..aecb1ec2 100644 --- a/fs/statestore/pebble_test.go +++ b/fsold/statestore/pebble_test.go @@ -20,8 +20,8 @@ import ( "context" "testing" - "github.com/functionstream/function-stream/fs/api" - "github.com/functionstream/function-stream/fs/statestore" + "github.com/functionstream/function-stream/fsold/api" + "github.com/functionstream/function-stream/fsold/statestore" "github.com/stretchr/testify/assert" ) diff --git a/go.mod b/go.mod index 8fe63292..410469de 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/functionstream/function-stream -go 1.22 +go 1.22.0 + +toolchain go1.23.4 require ( github.com/apache/pulsar-client-go v0.12.0 @@ -11,22 +13,25 @@ require ( github.com/go-logr/logr v1.4.1 github.com/go-logr/zapr v1.3.0 github.com/go-openapi/spec v0.21.0 - github.com/go-playground/validator/v10 v10.11.1 + github.com/go-playground/validator/v10 v10.24.0 github.com/nats-io/nats.go v1.37.0 github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/tetratelabs/wazero v1.6.0 github.com/wirelessr/avroschema v0.0.0-20240111032105-ef4f4560e2a7 go.uber.org/zap v1.26.0 - golang.org/x/net v0.26.0 + golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 + golang.org/x/net v0.34.0 golang.org/x/time v0.5.0 google.golang.org/grpc v1.64.1 - google.golang.org/protobuf v1.33.0 + google.golang.org/protobuf v1.36.3 gopkg.in/yaml.v3 v3.0.1 ) +replace github.com/onsi/ginkgo => github.com/onsi/ginkgo/v2 v2.22.2 + require ( github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.2 // indirect @@ -44,12 +49,13 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dvsekhvalnov/jose2go v1.6.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect - github.com/go-playground/locales v0.14.0 // indirect - github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect @@ -67,17 +73,17 @@ require ( github.com/klauspost/compress v1.17.7 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/leodido/go-urn v1.2.1 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/linkedin/goavro/v2 v2.12.0 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mtibben/percent v0.2.1 // indirect github.com/nats-io/nkeys v0.4.7 // indirect github.com/nats-io/nuid v1.0.1 // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.19.0 // indirect @@ -96,13 +102,13 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.24.0 // indirect - golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect - golang.org/x/mod v0.17.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/mod v0.22.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect - golang.org/x/sys v0.21.0 // indirect - golang.org/x/term v0.21.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/tools v0.29.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index b0601130..1a90a8f9 100644 --- a/go.sum +++ b/go.sum @@ -51,6 +51,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -73,14 +75,16 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= -github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg= +github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -101,6 +105,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -124,16 +130,14 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/linkedin/goavro/v2 v2.12.0 h1:rIQQSj8jdAUlKQh6DttK8wCRv4t4QO09g1C4aBWXslg= github.com/linkedin/goavro/v2 v2.12.0/go.mod h1:KXx+erlq+RPlGSPmLF7xGo6SAbh8sCQ53x064+ioxhk= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -141,8 +145,8 @@ github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3v github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -159,14 +163,14 @@ github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDm github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= +github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= -github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= @@ -185,8 +189,6 @@ github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSz github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= @@ -213,7 +215,6 @@ github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -221,10 +222,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g= @@ -246,56 +245,51 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -303,6 +297,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -315,30 +311,26 @@ google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc= diff --git a/perf/perf.go b/perf/perf.go index 8c8c27ef..ef9f931a 100644 --- a/perf/perf.go +++ b/perf/perf.go @@ -32,7 +32,7 @@ import ( adminclient "github.com/functionstream/function-stream/admin/client" "github.com/functionstream/function-stream/admin/utils" "github.com/functionstream/function-stream/common" - "github.com/functionstream/function-stream/fs/contube" + "github.com/functionstream/function-stream/fsold/contube" "golang.org/x/time/rate" ) diff --git a/pkg/cmdutil/cmdutil.go b/pkg/cmdutil/cmdutil.go new file mode 100644 index 00000000..cd4e2c86 --- /dev/null +++ b/pkg/cmdutil/cmdutil.go @@ -0,0 +1,19 @@ +package cmdutil + +import ( + "fmt" + "go.uber.org/zap" + "os" +) + +func CheckErr(err error) { + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } +} + +func GetLogger() *zap.Logger { + logger, _ := zap.NewProduction() + return logger +} diff --git a/pkg/configmap/configmap.go b/pkg/configmap/configmap.go new file mode 100644 index 00000000..a9aaf27a --- /dev/null +++ b/pkg/configmap/configmap.go @@ -0,0 +1,47 @@ +package configmap + +import ( + "encoding/json" + + "github.com/go-playground/validator/v10" +) + +// ConfigMap is a custom type that represents a map where keys are strings and values are of any type. +// Since Viper is not case-sensitive, we use '-' to separate words in all field names in the config map. +// This convention helps in maintaining consistency across different configurations and makes them easier to read. +type ConfigMap map[string]interface{} + +// MergeConfig merges multiple ConfigMap into one +func MergeConfig(configs ...ConfigMap) ConfigMap { + result := ConfigMap{} + for _, config := range configs { + for k, v := range config { + result[k] = v + } + } + return result +} + +func (c ConfigMap) ToConfigStruct(v any) error { + jsonData, err := json.Marshal(c) + if err != nil { + return err + } + if err := json.Unmarshal(jsonData, v); err != nil { + return err + } + validate := validator.New() + return validate.Struct(v) +} + +func ToConfigMap(v any) (ConfigMap, error) { + jsonData, err := json.Marshal(v) + if err != nil { + return nil, err + } + var result ConfigMap + if err := json.Unmarshal(jsonData, &result); err != nil { + return nil, err + } + return result, nil +} diff --git a/common/run.go b/pkg/process/run.go similarity index 98% rename from common/run.go rename to pkg/process/run.go index 99031f1f..e97d06e1 100644 --- a/common/run.go +++ b/pkg/process/run.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package common +package process import ( "io" diff --git a/common/signal.go b/pkg/process/signal.go similarity index 98% rename from common/signal.go rename to pkg/process/signal.go index 93315b86..cf65ee40 100644 --- a/common/signal.go +++ b/pkg/process/signal.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package common +package process import ( "io" diff --git a/pkg/testfunctions/externalfunction.go b/pkg/testfunctions/externalfunction.go new file mode 100644 index 00000000..cb5f0ebb --- /dev/null +++ b/pkg/testfunctions/externalfunction.go @@ -0,0 +1,126 @@ +package testfunctions + +import ( + "context" + "github.com/functionstream/function-stream/clients/gofs" + gofsapi "github.com/functionstream/function-stream/clients/gofs/api" + "github.com/stretchr/testify/require" + "os" + "testing" +) + +type Person struct { + Name string `json:"name"` + Money int `json:"money"` + Expected int `json:"expected"` +} + +type Counter struct { + Count int `json:"count"` +} + +type TestRecord struct { + ID int `json:"id"` + Name string `json:"name"` +} + +type TestFunction struct { +} + +func (f *TestFunction) Init(_ gofsapi.FunctionContext) error { + return nil +} + +func (f *TestFunction) Handle(_ gofsapi.FunctionContext, event gofsapi.Event[Person]) (gofsapi.Event[Person], error) { + p := event.Data() + p.Money += 1 + return gofsapi.NewEvent(p), nil +} + +type TestCounterFunction struct { +} + +func (f *TestCounterFunction) Init(ctx gofsapi.FunctionContext) error { + return nil +} + +func (f *TestCounterFunction) Handle(_ gofsapi.FunctionContext, event gofsapi.Event[Counter]) (gofsapi.Event[Counter], error) { + c := event.Data() + c.Count += 1 + return gofsapi.NewEvent(c), nil +} + +type TestSource struct { +} + +func (f *TestSource) Init(_ gofsapi.FunctionContext) error { + return nil +} + +func (f *TestSource) Handle(_ gofsapi.FunctionContext, emit func(context.Context, gofsapi.Event[TestRecord]) error) error { + for i := 0; i < 10; i++ { + err := emit(context.Background(), gofsapi.NewEvent(&TestRecord{ + ID: i, + Name: "test", + })) + if err != nil { + return err + } + } + return nil +} + +type TestSink struct { + SinkCh chan Counter +} + +func (f *TestSink) Init(_ gofsapi.FunctionContext) error { + return nil +} + +func (f *TestSink) Handle(ctx gofsapi.FunctionContext, event gofsapi.Event[Counter]) error { + f.SinkCh <- *event.Data() + return event.Commit(ctx) +} + +type TestModules struct { + testFunction *TestFunction + testCounter *TestCounterFunction + testSource *TestSource + TestSink *TestSink +} + +func NewTestModules() *TestModules { + return &TestModules{ + testFunction: &TestFunction{}, + testCounter: &TestCounterFunction{}, + testSource: &TestSource{}, + TestSink: &TestSink{ + SinkCh: make(chan Counter), + }, + } +} + +func (t *TestModules) Run(ctx context.Context) { + err := gofs.NewFSClient(). + Register(gofsapi.DefaultModule, gofs.WithFunction(func() gofsapi.Function[Person, Person] { + return t.testFunction + })). + Register("counter", gofs.WithFunction(func() gofsapi.Function[Counter, Counter] { + return t.testCounter + })). + Register("test-source", gofs.WithSource(func() gofsapi.Source[TestRecord] { + return t.testSource + })). + Register("test-sink", gofs.WithSink(func() gofsapi.Sink[Counter] { + return t.TestSink + })). + Run(ctx) + if err != nil { + panic(err) + } +} + +func SetupFSTarget(t *testing.T, target string) { + require.NoError(t, os.Setenv("FS_TARGET", target)) +} diff --git a/pkg/testutil/testutil.go b/pkg/testutil/testutil.go new file mode 100644 index 00000000..4924c6c5 --- /dev/null +++ b/pkg/testutil/testutil.go @@ -0,0 +1,32 @@ +package testutil + +import ( + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "os" + "testing" +) + +// createTempFile creates a temporary test file with cleanup +func CreateTempFile(t *testing.T, content, pattern string) (string, func()) { + tmpFile, err := os.CreateTemp("", pattern) + require.NoError(t, err, "Failed to create temp file") + + _, err = tmpFile.WriteString(content) + require.NoError(t, err, "Failed to write test content") + + err = tmpFile.Close() + require.NoError(t, err, "Failed to close temp file") + + return tmpFile.Name(), func() { + os.Remove(tmpFile.Name()) + } +} + +func GetTestLogger(t *testing.T) *zap.Logger { + config := zap.NewDevelopmentConfig() + config.Level = zap.NewAtomicLevelAt(zap.DebugLevel) + log, err := config.Build() + require.NoError(t, err) + return log +} diff --git a/server/config.go b/server/config.go index c86fff42..0fff8241 100644 --- a/server/config.go +++ b/server/config.go @@ -1,80 +1,37 @@ -/* - * Copyright 2024 Function Stream Org. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package server import ( "fmt" - "os" - "strings" - "github.com/functionstream/function-stream/common/config" - "github.com/go-playground/validator/v10" - "github.com/spf13/viper" ) -type FactoryConfig struct { - // Deprecate - Ref *string `mapstructure:"ref"` - Type *string `mapstructure:"type"` - Config *config.ConfigMap `mapstructure:"config"` -} - -type StateStoreConfig struct { - Type *string `mapstructure:"type"` - Config config.ConfigMap `mapstructure:"config"` -} - -type QueueConfig struct { +type ComponentConfig struct { Type string `mapstructure:"type"` Config config.ConfigMap `mapstructure:"config"` } type Config struct { - // ListenAddr is the address that the function stream REST service will listen on. - ListenAddr string `mapstructure:"listen-addr"` - - Queue QueueConfig `mapstructure:"queue"` + // ListenAddress is the address that the function stream REST service will listen on. + ListenAddress string `mapstructure:"listen-address" validate:"required"` + EnableTLS bool `mapstructure:"enable-tls"` + TLSCertFile string `mapstructure:"tls-cert-file"` + TLSKeyFile string `mapstructure:"tls-key-file"` - TubeConfig map[string]config.ConfigMap `mapstructure:"tube-config"` - - RuntimeConfig map[string]config.ConfigMap `mapstructure:"runtime-config"` - - // StateStore is the configuration for the state store that the function stream server will use. - // Optional - StateStore *StateStoreConfig `mapstructure:"state-store"` - - // FunctionStore is the path to the function store - FunctionStore string `mapstructure:"function-store"` - - EnableTLS bool `mapstructure:"enable-tls"` - TLSCertFile string `mapstructure:"tls-cert-file"` - TLSKeyFile string `mapstructure:"tls-key-file"` + PackageLoader ComponentConfig `mapstructure:"package-loader"` + EventStorage ComponentConfig `mapstructure:"event-storage"` + StateStore ComponentConfig `mapstructure:"state-store"` + Runtimes []ComponentConfig `mapstructure:"runtimes"` } func init() { - viper.SetDefault("listen-addr", ":7300") - viper.SetDefault("function-store", "./functions") + viper.SetDefault("listen-address", ":7300") } func (c *Config) PreprocessConfig() error { - if c.ListenAddr == "" { - return fmt.Errorf("ListenAddr shouldn't be empty") + if c.ListenAddress == "" { + return fmt.Errorf("listen-address shouldn't be empty") } validate := validator.New() if err := validate.Struct(c); err != nil { @@ -83,19 +40,6 @@ func (c *Config) PreprocessConfig() error { return nil } -func loadConfig() (*Config, error) { - var c Config - if err := viper.Unmarshal(&c); err != nil { - return nil, err - } - if err := c.PreprocessConfig(); err != nil { - return nil, err - } - return &c, nil -} - -const envPrefix = "FS_" - func LoadConfigFromFile(filePath string) (*Config, error) { viper.SetConfigFile(filePath) if err := viper.ReadInConfig(); err != nil { @@ -104,18 +48,13 @@ func LoadConfigFromFile(filePath string) (*Config, error) { return loadConfig() } -func LoadConfigFromEnv() (*Config, error) { - for _, env := range os.Environ() { - if strings.HasPrefix(env, envPrefix) { - parts := strings.SplitN(strings.TrimPrefix(env, envPrefix), "=", 2) - key := parts[0] - value := parts[1] - - key = strings.Replace(key, "__", ".", -1) - key = strings.Replace(key, "_", "-", -1) - viper.Set(key, value) - } +func loadConfig() (*Config, error) { + var c Config + if err := viper.Unmarshal(&c); err != nil { + return nil, err } - - return loadConfig() + if err := c.PreprocessConfig(); err != nil { + return nil, err + } + return &c, nil } diff --git a/server/config_test.go b/server/config_test.go index 821ece59..81097efa 100644 --- a/server/config_test.go +++ b/server/config_test.go @@ -17,7 +17,7 @@ package server import ( - "os" + "github.com/functionstream/function-stream/pkg/testutil" "testing" "github.com/spf13/viper" @@ -27,7 +27,7 @@ import ( func TestLoadConfigFromYaml(t *testing.T) { c, err := LoadConfigFromFile("../tests/test_config.yaml") - require.Nil(t, err) + require.NoError(t, err) assertConfig(t, c) } @@ -37,23 +37,248 @@ func TestLoadConfigFromJson(t *testing.T) { assertConfig(t, c) } -func TestLoadConfigFromEnv(t *testing.T) { - assert.Nil(t, os.Setenv("FS_LISTEN_ADDR", ":17300")) - assert.Nil(t, os.Setenv("FS_TUBE_CONFIG__MY_TUBE__KEY", "value")) - assert.Nil(t, os.Setenv("FS_RUNTIME_CONFIG__CUSTOM_RUNTIME__NAME", "test")) +func TestLoadConfigFromFiles(t *testing.T) { + // Setup test cases with different file formats + testCases := []struct { + name string + filePath string + setup func() (string, func()) + wantErr bool + errContains string + }{ + { + name: "ValidYAMLConfig", + filePath: "test_config.yaml", + setup: func() (string, func()) { + content := ` +listen-address: ":17300" +enable-tls: true +tls-cert-file: /path/to/cert.pem +tls-key-file: /path/to/key.pem +package-loader: + type: my-package-loader + config: + path: tests/packages +event-storage: + type: my-event-storage + config: + url: xxx://localhost:12345 +state-store: + type: my-state-store + config: + url: xxx://localhost:12345 +runtimes: + - type: my-runtime-1 + config: + my-config: xxx + - type: my-runtime-2 + config: + my-config: xxx +` + return testutil.CreateTempFile(t, content, "*.yaml") + }, + }, + { + name: "ValidJSONConfig", + filePath: "test_config.json", + setup: func() (string, func()) { + content := `{ + "listen-address": ":17300", + "enable-tls": true, + "tls-cert-file": "/path/to/cert.pem", + "tls-key-file": "/path/to/key.pem", + "package-loader": { + "type": "my-package-loader", + "config": { + "path": "tests/packages" + } + }, + "event-storage": { + "type": "my-event-storage", + "config": { + "url": "xxx://localhost:12345" + } + }, + "state-store": { + "type": "my-state-store", + "config": { + "url": "xxx://localhost:12345" + } + }, + "runtimes": [ + { + "type": "my-runtime-1", + "config": { + "my-config": "xxx" + } + }, + { + "type": "my-runtime-2", + "config": { + "my-config": "xxx" + } + } + ] +}` + return testutil.CreateTempFile(t, content, "*.json") + }, + }, + { + name: "InvalidYAMLFormat", + filePath: "invalid.yaml", + setup: func() (string, func()) { + content := `listen-address: ":17300 + enable-tls: true` + return testutil.CreateTempFile(t, content, "*.yaml") + }, + wantErr: true, + errContains: "unexpected end of stream", + }, + { + name: "InvalidJSONFormat", + filePath: "invalid.json", + setup: func() (string, func()) { + content := `{"listen-address": ":17300,}` + return testutil.CreateTempFile(t, content, "*.json") + }, + wantErr: true, + errContains: "unexpected end of JSON input", + }, + { + name: "MissingRequiredFields", + filePath: "missing_fields.yaml", + setup: func() (string, func()) { + content := `enable-tls: false` + return testutil.CreateTempFile(t, content, "*.yaml") + }, + wantErr: true, + errContains: "listen-address", + }, + } - viper.AutomaticEnv() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Setup test environment + viper.Reset() + filePath, cleanup := tc.setup() + defer cleanup() - c, err := LoadConfigFromEnv() - require.Nil(t, err) - assertConfig(t, c) + // Execute test + cfg, err := LoadConfigFromFile(filePath) + + // Validate results + if tc.wantErr { + require.Error(t, err, "Expected error but got none") + if tc.errContains != "" { + assert.Contains(t, err.Error(), tc.errContains, + "Error message should contain %q", tc.errContains) + } + return + } + + require.NoError(t, err, "Config loading failed") + assertConfig(t, cfg) + }) + } } func assertConfig(t *testing.T, c *Config) { - assert.Equal(t, ":17300", c.ListenAddr) - require.Contains(t, c.TubeConfig, "my-tube") - assert.Equal(t, "value", c.TubeConfig["my-tube"]["key"]) + t.Helper() + + t.Run("BasicConfig", func(t *testing.T) { + if c.ListenAddress != ":17300" { + t.Errorf("expected listen-address :17300, got %q", c.ListenAddress) + } + if !c.EnableTLS { + t.Error("expected enable-tls true") + } + if c.TLSCertFile != "/path/to/cert.pem" { + t.Errorf("expected tls-cert-file /path/to/cert.pem, got %q", c.TLSCertFile) + } + if c.TLSKeyFile != "/path/to/key.pem" { + t.Errorf("expected tls-key-file /path/to/key.pem, got %q", c.TLSKeyFile) + } + }) + + testCases := []struct { + name string + component ComponentConfig + expectType string + configKeys map[string]interface{} + }{ + { + name: "PackageLoader", + component: c.PackageLoader, + expectType: "my-package-loader", + configKeys: map[string]interface{}{ + "path": "tests/packages", + }, + }, + { + name: "EventStorage", + component: c.EventStorage, + expectType: "my-event-storage", + configKeys: map[string]interface{}{ + "url": "xxx://localhost:12345", + }, + }, + { + name: "StateStore", + component: c.StateStore, + expectType: "my-state-store", + configKeys: map[string]interface{}{ + "url": "xxx://localhost:12345", + }, + }, + { + name: "Runtime", + component: c.Runtimes[0], + expectType: "my-runtime-1", + configKeys: map[string]interface{}{ + "my-config": "xxx", + }, + }, + { + name: "Runtime", + component: c.Runtimes[1], + expectType: "my-runtime-2", + configKeys: map[string]interface{}{ + "my-config": "xxx", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if tc.component.Type != tc.expectType { + t.Errorf("expected type %q, got %q", tc.expectType, tc.component.Type) + } + + for key, expected := range tc.configKeys { + actual, exists := tc.component.Config[key] + if !exists { + t.Errorf("missing config key: %q", key) + continue + } + + var value interface{} + switch v := actual.(type) { + case string: + value = v + case int: + value = v + case bool: + value = v + default: + t.Errorf("unsupported type %T for key %q", actual, key) + continue + } - require.Contains(t, c.RuntimeConfig, "custom-runtime") - assert.Equal(t, "test", c.RuntimeConfig["custom-runtime"]["name"]) + if value != expected { + t.Errorf("config %q: expected %v (%T), got %v (%T)", + key, expected, expected, value, value) + } + } + }) + } } diff --git a/server/rest/events.go b/server/rest/events.go new file mode 100644 index 00000000..b24ddea7 --- /dev/null +++ b/server/rest/events.go @@ -0,0 +1,114 @@ +package rest + +import ( + "bytes" + "encoding/json" + restfulspec "github.com/emicklei/go-restful-openapi/v2" + "github.com/emicklei/go-restful/v3" + "github.com/functionstream/function-stream/fs/event" + "github.com/functionstream/function-stream/fs/model" + "io" + "net/http" +) + +func (h *Handler) makeEventsService() *restful.WebService { + ws := new(restful.WebService) + ws.Path("/api/v1/events"). + Consumes(restful.MIME_JSON). + Produces(restful.MIME_JSON) + + tags := []string{"events"} + + eventName := ws.PathParameter("name", "topic name").DataType("string") + + ws.Route(ws.POST("/produce/{name}"). + To(func(request *restful.Request, response *restful.Response) { + name := request.PathParameter("name") + body := request.Request.Body + defer func() { + h.handleRestError(body.Close()) + }() + + data, err := io.ReadAll(body) + if err != nil { + h.handleRestError(response.WriteError(http.StatusInternalServerError, err)) + return + } + + e := event.NewRawEvent("", bytes.NewReader(data)) + if err := h.EventStorage.Write(request.Request.Context(), e, model.TopicConfig{ + Name: name, + }); err != nil { + h.handleRestError(response.WriteError(http.StatusInternalServerError, err)) + return + } + response.WriteHeader(http.StatusOK) + //select { + //case <-request.Request.Context().Done(): + // h.handleRestError(response.WriteError(http.StatusRequestTimeout, request.Request.Context().Err())) + // return + //case <-e.OnCommit(): + // response.WriteHeader(http.StatusOK) + // return + //} + }). + Doc("produce a message"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Operation("produceMessage"). + Reads(map[string]any{}). + Param(eventName)) + + ws.Route(ws.GET("/consume/{name}"). + To(func(request *restful.Request, response *restful.Response) { + name := request.PathParameter("name") + eCh, err := h.EventStorage.Read(request.Request.Context(), []model.TopicConfig{ + { + Name: name, + }, + }) + if err != nil { + h.handleRestError(response.WriteError(http.StatusInternalServerError, err)) + return + } + + response.AddHeader("Transfer-Encoding", "chunked") + for { + select { + case <-request.Request.Context().Done(): + h.handleRestError(response.WriteError(http.StatusRequestTimeout, request.Request.Context().Err())) + return + case e, ok := <-eCh: + if !ok { + return + } + data, err := io.ReadAll(e.Payload()) + if err != nil { + h.handleRestError(response.WriteError(http.StatusInternalServerError, err)) + return + } + entity := make(map[string]any) + if err := json.Unmarshal(data, &entity); err != nil { + h.handleRestError(response.WriteError(http.StatusInternalServerError, err)) + return + } + if _, err := response.Write(data); err != nil { + h.handleRestError(response.WriteError(http.StatusInternalServerError, err)) + return + } + if _, err = response.Write([]byte("\n")); err != nil { + h.handleRestError(response.WriteError(http.StatusInternalServerError, err)) + return + } + response.Flush() + } + } + + }). + Doc("consume a message"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Operation("consumeMessage"). + Writes(map[string]any{}). + Param(eventName)) + + return ws +} diff --git a/server/rest/resources.go b/server/rest/resources.go new file mode 100644 index 00000000..79e346bc --- /dev/null +++ b/server/rest/resources.go @@ -0,0 +1,156 @@ +package rest + +import ( + restfulspec "github.com/emicklei/go-restful-openapi/v2" + "github.com/emicklei/go-restful/v3" + "github.com/functionstream/function-stream/fs/api" + "github.com/pkg/errors" + "go.uber.org/zap" + "net/http" + "strings" +) + +type ResourceServiceBuilder[T any] struct { + name string + basePath string + resourceProvider api.ResourceProvider[T] +} + +func (b *ResourceServiceBuilder[T]) Name(name string) *ResourceServiceBuilder[T] { + b.name = name + return b +} + +func (b *ResourceServiceBuilder[T]) ResourceProvider(resourceProvider api.ResourceProvider[T]) *ResourceServiceBuilder[T] { + b.resourceProvider = resourceProvider + return b +} + +func capitalizeFirstLetter(s string) string { + if len(s) == 0 { + return s + } + return strings.ToUpper(string(s[0])) + s[1:] +} + +func (b *ResourceServiceBuilder[T]) Build(basePath string, log *zap.Logger) *restful.WebService { + ws := &restful.WebService{} + ws.Path(basePath + b.name).Consumes(restful.MIME_JSON).Produces(restful.MIME_JSON) + + tags := []string{b.name} + r := b.resourceProvider + + l := log.With(zap.String("resource", b.name)) + + handleRestError := func(e error) { + if e == nil { + return + } + l.Error("Error handling REST request", zap.Error(e)) + } + + ws.Route(ws.GET("/"). + To(func(request *restful.Request, response *restful.Response) { + packages, err := r.List(request.Request.Context()) + if err != nil { + handleRestError(response.WriteError(http.StatusBadRequest, err)) + return + } + handleRestError(response.WriteEntity(packages)) + }). + Doc("get all "+b.name). + Metadata(restfulspec.KeyOpenAPITags, tags). + Operation("getAll"+capitalizeFirstLetter(b.name)). + Returns(http.StatusOK, "OK", []*T{}). + Writes([]*T{})) + + ws.Route(ws.GET("/{name}"). + To(func(request *restful.Request, response *restful.Response) { + name := request.PathParameter("name") + t, err := r.Read(request.Request.Context(), name) + if err != nil { + if errors.Is(err, api.ErrResourceNotFound) { + handleRestError(response.WriteError(http.StatusNotFound, err)) + return + } + handleRestError(response.WriteError(http.StatusBadRequest, err)) + return + } + handleRestError(response.WriteEntity(t)) + }). + Doc("get "+b.name). + Metadata(restfulspec.KeyOpenAPITags, tags). + Operation("get"+capitalizeFirstLetter(b.name)). + Param(ws.PathParameter("name", "name of the "+b.name).DataType("string")). + Returns(http.StatusOK, "OK", new(T)). + Writes(new(T))) + + ws.Route(ws.POST("/"). + To(func(request *restful.Request, response *restful.Response) { + t := new(T) + err := request.ReadEntity(t) + if err != nil { + handleRestError(response.WriteError(http.StatusBadRequest, err)) + return + } + err = r.Create(request.Request.Context(), t) + if err != nil { + if errors.Is(err, api.ErrResourceAlreadyExists) { + handleRestError(response.WriteError(http.StatusConflict, err)) + return + } + handleRestError(response.WriteError(http.StatusBadRequest, err)) + return + } + response.WriteHeader(http.StatusOK) + }). + Doc("create "+b.name). + Metadata(restfulspec.KeyOpenAPITags, tags). + Operation("create" + capitalizeFirstLetter(b.name)). + Reads(new(T))) + + ws.Route(ws.PUT("/"). + To(func(request *restful.Request, response *restful.Response) { + t := new(T) + err := request.ReadEntity(t) + if err != nil { + handleRestError(response.WriteError(http.StatusBadRequest, err)) + return + } + err = r.Upsert(request.Request.Context(), t) + if err != nil { + if errors.Is(err, api.ErrResourceNotFound) { + handleRestError(response.WriteError(http.StatusNotFound, err)) + return + } + handleRestError(response.WriteError(http.StatusBadRequest, err)) + return + } + response.WriteHeader(http.StatusOK) + }). + Doc("update "+b.name). + Metadata(restfulspec.KeyOpenAPITags, tags). + Operation("update" + capitalizeFirstLetter(b.name)). + Reads(new(T))) + + ws.Route(ws.DELETE("/{name}"). + To(func(request *restful.Request, response *restful.Response) { + name := request.PathParameter("name") + if err := r.Delete(request.Request.Context(), name); err != nil { + if errors.Is(err, api.ErrResourceNotFound) { + handleRestError(response.WriteError(http.StatusNotFound, err)) + return + } + handleRestError(response.WriteError(http.StatusBadRequest, err)) + return + } + }). + Doc("delete "+b.name). + Metadata(restfulspec.KeyOpenAPITags, tags). + Operation("delete" + capitalizeFirstLetter(b.name)). + Param(ws.PathParameter("name", "name of the package").DataType("string"))) + + l.Info("Resource service setup complete", zap.String("name", b.name), zap.String("apiPath", basePath+b.name)) + + return ws +} diff --git a/server/rest/rest.go b/server/rest/rest.go new file mode 100644 index 00000000..d3fec4e2 --- /dev/null +++ b/server/rest/rest.go @@ -0,0 +1,177 @@ +package rest + +import ( + "context" + restfulspec "github.com/emicklei/go-restful-openapi/v2" + "github.com/emicklei/go-restful/v3" + "github.com/functionstream/function-stream/fs/api" + "github.com/functionstream/function-stream/fs/model" + "github.com/go-openapi/spec" + "go.uber.org/zap" + "net" + "net/http" + "net/url" + "sync/atomic" + "time" +) + +type Handler struct { + HttpListener net.Listener + EnableTls bool + TlsCertFile string + TlsKeyFile string + Manager api.Manager + PackageStorage api.PackageStorage + EventStorage api.EventStorage + Log *zap.Logger + + httpSvr atomic.Pointer[http.Server] +} + +func enrichSwaggerObject(swo *spec.Swagger) { + swo.Info = &spec.Info{ + InfoProps: spec.InfoProps{ + Title: "Function Stream Service", + Description: "Manage Function Stream Resources", + Contact: &spec.ContactInfo{ + ContactInfoProps: spec.ContactInfoProps{ + Name: "Function Stream Org", + URL: "https://github.com/FunctionStream", + }, + }, + License: &spec.License{ + LicenseProps: spec.LicenseProps{ + Name: "Apache 2", + URL: "http://www.apache.org/licenses/", + }, + }, + Version: "1.0.0", // TODO: Version control + }, + } + swo.Host = "localhost:7300" + swo.Schemes = []string{"http"} + swo.Tags = []spec.Tag{ + { + TagProps: spec.TagProps{ + Name: "functions", + Description: "Managing functions"}, + }, + { + TagProps: spec.TagProps{ + Name: "packages", + Description: "Managing packages"}, + }, + { + TagProps: spec.TagProps{ + Name: "events", + Description: "Producing and consuming events"}, + }, + } +} + +func (h *Handler) Run() error { + statusSvr := new(restful.WebService) + statusSvr.Path("/api/v1/status"). + Route(statusSvr.GET("/").To(func(request *restful.Request, response *restful.Response) { + response.WriteHeader(http.StatusOK) + }). + Doc("Get the status of the Function Stream"). + Metadata(restfulspec.KeyOpenAPITags, []string{"status"}). + Operation("getStatus")) + + container := restful.NewContainer() + + cors := restful.CrossOriginResourceSharing{ + AllowedHeaders: []string{"Content-Type", "Accept"}, + AllowedMethods: []string{"GET", "POST", "OPTIONS", "PUT", "DELETE"}, + CookiesAllowed: false, + Container: container} + container.Filter(cors.Filter) + container.Filter(container.OPTIONSFilter) + + config := restfulspec.Config{ + WebServices: container.RegisteredWebServices(), + APIPath: "/apidocs", + PostBuildSwaggerObjectHandler: enrichSwaggerObject} + container.Add(restfulspec.NewOpenAPIService(config)) + container.Add(h.makeEventsService()) + basePath := "/apis/v1/" + container.Add(new(ResourceServiceBuilder[model.Package]). + Name("package"). + ResourceProvider(h.PackageStorage). + Build(basePath, h.Log)) + container.Add(new(ResourceServiceBuilder[model.Function]). + Name("function"). + ResourceProvider(h.Manager). + Build(basePath, h.Log)) + + httpSvr := &http.Server{ + Handler: container.ServeMux, + } + h.httpSvr.Store(httpSvr) + + if h.EnableTls { + return httpSvr.ServeTLS(h.HttpListener, h.TlsCertFile, h.TlsKeyFile) + } else { + return httpSvr.Serve(h.HttpListener) + } +} + +func (s *Handler) WaitForReady(ctx context.Context) <-chan struct{} { + c := make(chan struct{}) + detect := func() bool { + u := (&url.URL{ + Scheme: "http", + Host: s.HttpListener.Addr().String(), + Path: "/api/v1/status", + }).String() + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) + if err != nil { + s.Log.Error("Failed to create detect request", zap.Error(err)) + return false + } + client := &http.Client{} + _, err = client.Do(req) + if err != nil { + s.Log.Error("Detect connection to server failed", zap.Error(err)) + return false + } + s.Log.Info("Server is ready", zap.String("url", u)) + return true + } + go func() { + defer close(c) + + if detect() { + return + } + // Try to connect to the server + for { + select { + case <-ctx.Done(): + return + case <-time.After(1 * time.Second): + if detect() { + return + } + } + } + }() + return c +} + +func (h *Handler) handleRestError(e error) { + if e == nil { + return + } + h.Log.Error("Error handling REST request", zap.Error(e)) +} + +func (h *Handler) Stop() error { + h.Log.Info("Stopping REST handler") + httpSvr := h.httpSvr.Load() + if httpSvr != nil { + return httpSvr.Close() + } + return nil +} diff --git a/server/rest/rest_test.go b/server/rest/rest_test.go new file mode 100644 index 00000000..7eb62026 --- /dev/null +++ b/server/rest/rest_test.go @@ -0,0 +1,161 @@ +package rest + +import ( + "bytes" + "context" + "encoding/json" + "github.com/functionstream/function-stream/fs/api" + "github.com/functionstream/function-stream/fs/model" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "net" + "net/http" + "sync" + "testing" + "time" +) + +func getListener(t *testing.T) net.Listener { + ln, err := net.Listen("tcp", "localhost:0") + if err != nil { + t.Fatalf("Failed to listen: %v", err) + } + t.Logf("Listening on %s\n", ln.Addr().String()) + return ln +} + +type MockManager struct { + functions sync.Map +} + +func (m *MockManager) Deploy(ctx context.Context, f *model.Function) error { + if _, ok := m.functions.Load(f.Name); ok { + return api.ErrResourceAlreadyExists + } + m.functions.Store(f.Name, f) + return nil +} + +func (m *MockManager) Delete(ctx context.Context, name string) error { + if _, ok := m.functions.Load(name); !ok { + return api.ErrResourceNotFound + } + m.functions.Delete(name) + return nil +} + +func (m *MockManager) List() []*model.Function { + var functions []*model.Function + m.functions.Range(func(key, value interface{}) bool { + functions = append(functions, value.(*model.Function)) + return true + }) + return functions +} + +func NewMockManager() *MockManager { + return &MockManager{ + functions: sync.Map{}, + } +} + +func TestRestAPI(t *testing.T) { + config := zap.NewDevelopmentConfig() + config.Level = zap.NewAtomicLevelAt(zap.DebugLevel) + log, err := config.Build() + require.NoError(t, err) + + mockMgr := NewMockManager() + h := &Handler{ + HttpListener: getListener(t), + EnableTls: false, + Log: log, + Manager: mockMgr, + } + + go func() { + _ = h.Run() + }() + defer h.Stop() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + <-h.WaitForReady(ctx) + + baseURL := "http://" + h.HttpListener.Addr().String() + "/apis/v1/function" + + t.Run("Deploy Function via HTTP", func(t *testing.T) { + function := model.Function{ + Name: "test-func", + Package: "test-pkg", + Module: "test-module", + Config: map[string]string{"test-config": "test-value"}, + } + + body, _ := json.Marshal(function) + resp, err := http.Post(baseURL+"/", "application/json", bytes.NewReader(body)) + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusOK, resp.StatusCode) + + v, ok := mockMgr.functions.Load(function.Name) + require.True(t, ok) + require.Equal(t, &function, v.(*model.Function)) + }) + + t.Run("List Functions via HTTP", func(t *testing.T) { + resp, err := http.Get(baseURL + "/") + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusOK, resp.StatusCode) + + var functions []*model.Function + require.NoError(t, json.NewDecoder(resp.Body).Decode(&functions)) + + require.Len(t, functions, 1) + require.Equal(t, "test-func", functions[0].Name) + }) + + t.Run("Delete Function via HTTP", func(t *testing.T) { + req, err := http.NewRequest(http.MethodDelete, baseURL+"/test-func", nil) + require.NoError(t, err) + + client := &http.Client{} + resp, err := client.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusOK, resp.StatusCode) + + _, ok := mockMgr.functions.Load("test-func") + require.False(t, ok) + }) + + t.Run("Delete Non-existent Function", func(t *testing.T) { + req, err := http.NewRequest(http.MethodDelete, baseURL+"/non-existent", nil) + require.NoError(t, err) + + client := &http.Client{} + resp, err := client.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusNotFound, resp.StatusCode) + }) + + t.Run("Create Duplicate Function", func(t *testing.T) { + function := model.Function{Name: "duplicate-func"} + + body, _ := json.Marshal(function) + resp, _ := http.Post(baseURL+"/", "application/json", bytes.NewReader(body)) + resp.Body.Close() + + resp, err := http.Post(baseURL+"/", "application/json", bytes.NewReader(body)) + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusConflict, resp.StatusCode) + }) +} diff --git a/server/server.go b/server/server.go index c316931a..578d611e 100644 --- a/server/server.go +++ b/server/server.go @@ -1,548 +1,190 @@ -/* - * Copyright 2024 Function Stream Org. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package server import ( "context" "fmt" - "net" - "net/http" - "net/url" - "strings" - "sync/atomic" - "time" - - "github.com/functionstream/function-stream/common/config" - - "github.com/functionstream/function-stream/fs/runtime/external" - - "github.com/go-logr/logr" - - restfulspec "github.com/emicklei/go-restful-openapi/v2" - "github.com/emicklei/go-restful/v3" - "github.com/functionstream/function-stream/common" "github.com/functionstream/function-stream/fs" "github.com/functionstream/function-stream/fs/api" - "github.com/functionstream/function-stream/fs/contube" - "github.com/functionstream/function-stream/fs/runtime/wazero" - "github.com/functionstream/function-stream/fs/statestore" - "github.com/go-openapi/spec" - "github.com/pkg/errors" -) - -var ( - ErrUnsupportedStateStore = errors.New("unsupported state store") - ErrUnsupportedQueueType = errors.New("unsupported queue type") + memory2 "github.com/functionstream/function-stream/fs/eventstorage/memory" + "github.com/functionstream/function-stream/fs/packagestorage/memory" + "github.com/functionstream/function-stream/fs/runtime/external" + memory3 "github.com/functionstream/function-stream/fs/statestore/memory" + "github.com/functionstream/function-stream/server/rest" + "go.uber.org/zap" + "net" ) -type Server struct { - options *serverOptions - httpSvr atomic.Pointer[http.Server] - log *common.Logger - Manager fs.FunctionManager - FunctionStore FunctionStore -} - -type TubeLoaderType func(c *FactoryConfig) (contube.TubeFactory, error) -type RuntimeLoaderType func(c *FactoryConfig) (api.FunctionRuntimeFactory, error) -type StateStoreProviderType func(c *StateStoreConfig) (api.StateStoreFactory, error) - -type serverOptions struct { - httpListener net.Listener - managerOpts []fs.ManagerOption - httpTubeFact *contube.HttpTubeFactory - stateStoreProvider StateStoreProviderType - functionStore string - enableTls bool - tlsCertFile string - tlsKeyFile string - tubeFactoryBuilders map[string]func(configMap config.ConfigMap) (contube.TubeFactory, error) - tubeConfig map[string]config.ConfigMap - runtimeFactoryBuilders map[string]func(configMap config.ConfigMap) (api.FunctionRuntimeFactory, error) - runtimeConfig map[string]config.ConfigMap - queueConfig QueueConfig - log *logr.Logger -} - -type ServerOption interface { - apply(option *serverOptions) (*serverOptions, error) -} - -type serverOptionFunc func(*serverOptions) (*serverOptions, error) - -func (f serverOptionFunc) apply(c *serverOptions) (*serverOptions, error) { - return f(c) -} - -// WithHttpListener sets the listener for the HTTP server. -// If not set, the server will listen on the Config.ListenAddr. -func WithHttpListener(listener net.Listener) ServerOption { - return serverOptionFunc(func(o *serverOptions) (*serverOptions, error) { - o.httpListener = listener - return o, nil - }) -} - -// WithHttpTubeFactory sets the factory for the HTTP tube. -// If not set, the server will use the default HTTP tube factory. -func WithHttpTubeFactory(factory *contube.HttpTubeFactory) ServerOption { - return serverOptionFunc(func(o *serverOptions) (*serverOptions, error) { - o.httpTubeFact = factory - return o, nil - }) -} - -func WithQueueConfig(config QueueConfig) ServerOption { - return serverOptionFunc(func(o *serverOptions) (*serverOptions, error) { - o.queueConfig = config - return o, nil - }) -} - -func WithTubeFactoryBuilder( - name string, - builder func(configMap config.ConfigMap) (contube.TubeFactory, error), -) ServerOption { - return serverOptionFunc(func(o *serverOptions) (*serverOptions, error) { - o.tubeFactoryBuilders[name] = builder - return o, nil - }) -} - -func WithTubeFactoryBuilders( - builder map[string]func(configMap config.ConfigMap, - ) (contube.TubeFactory, error)) ServerOption { - return serverOptionFunc(func(o *serverOptions) (*serverOptions, error) { - for n, b := range builder { - o.tubeFactoryBuilders[n] = b - } - return o, nil - }) -} - -func WithRuntimeFactoryBuilder( - name string, - builder func(configMap config.ConfigMap) (api.FunctionRuntimeFactory, error), -) ServerOption { - return serverOptionFunc(func(o *serverOptions) (*serverOptions, error) { - o.runtimeFactoryBuilders[name] = builder - return o, nil - }) -} - -func WithRuntimeFactoryBuilders( - builder map[string]func(configMap config.ConfigMap) (api.FunctionRuntimeFactory, error), -) ServerOption { - return serverOptionFunc(func(o *serverOptions) (*serverOptions, error) { - for n, b := range builder { - o.runtimeFactoryBuilders[n] = b - } - return o, nil - }) -} +type ServerOption struct { + PackageStorageFactory func(ctx context.Context, config *ComponentConfig) (api.PackageStorage, error) + EventStorageFactory func(ctx context.Context, config *ComponentConfig) (api.EventStorage, error) + StateStoreFactory func(ctx context.Context, config *ComponentConfig) (api.StateStore, error) + RuntimeFactory func(ctx context.Context, config *ComponentConfig) (api.RuntimeAdapter, error) -func WithStateStoreLoader(loader func(c *StateStoreConfig) (api.StateStoreFactory, error)) ServerOption { - return serverOptionFunc(func(o *serverOptions) (*serverOptions, error) { - o.stateStoreProvider = loader - return o, nil - }) + Logger *zap.Logger } -func WithPackageLoader(packageLoader api.PackageLoader) ServerOption { - return serverOptionFunc(func(o *serverOptions) (*serverOptions, error) { - o.managerOpts = append(o.managerOpts, fs.WithPackageLoader(packageLoader)) - return o, nil - }) +func (o *ServerOption) BuiltinPackageLoaderFactory(_ context.Context, config *ComponentConfig) (api.PackageStorage, error) { + switch config.Type { + case "": + case "memory": + return memory.NewMemoryPackageStorage(o.Logger), nil + } + return nil, fmt.Errorf("unknown package loader type: %s", config.Type) } -func WithLogger(log *logr.Logger) ServerOption { - return serverOptionFunc(func(c *serverOptions) (*serverOptions, error) { - c.log = log - return c, nil - }) +func (o *ServerOption) GetPackageStorageFactory() func(ctx context.Context, config *ComponentConfig) (api.PackageStorage, error) { + if o.PackageStorageFactory != nil { + return o.PackageStorageFactory + } + return o.BuiltinPackageLoaderFactory } -func GetBuiltinTubeFactoryBuilder() map[string]func(configMap config.ConfigMap) (contube.TubeFactory, error) { - return map[string]func(configMap config.ConfigMap) (contube.TubeFactory, error){ - common.PulsarTubeType: func(configMap config.ConfigMap) (contube.TubeFactory, error) { - return contube.NewPulsarEventQueueFactory(context.Background(), contube.ConfigMap(configMap)) - }, - //nolint:unparam - common.MemoryTubeType: func(_ config.ConfigMap) (contube.TubeFactory, error) { - return contube.NewMemoryQueueFactory(context.Background()), nil - }, - //nolint:unparam - common.EmptyTubeType: func(_ config.ConfigMap) (contube.TubeFactory, error) { - return contube.NewEmptyTubeFactory(), nil - }, +func (o *ServerOption) BuiltinEventStorageFactory(ctx context.Context, config *ComponentConfig) (api.EventStorage, error) { + switch config.Type { + case "": + case "memory": + return memory2.NewMemEs(ctx, o.Logger), nil } + return nil, fmt.Errorf("unknown event storage type: %s", config.Type) } -func GetBuiltinRuntimeFactoryBuilder() map[string]func(configMap config.ConfigMap) (api.FunctionRuntimeFactory, error) { - return map[string]func(configMap config.ConfigMap) (api.FunctionRuntimeFactory, error){ - //nolint:unparam - common.WASMRuntime: func(configMap config.ConfigMap) (api.FunctionRuntimeFactory, error) { - return wazero.NewWazeroFunctionRuntimeFactory(), nil - }, - common.ExternalRuntime: func(configMap config.ConfigMap) (api.FunctionRuntimeFactory, error) { - return external.NewFactoryWithConfig(configMap) - }, +func (o *ServerOption) GetEventStorageFactory() func(ctx context.Context, config *ComponentConfig) (api.EventStorage, error) { + if o.EventStorageFactory != nil { + return o.EventStorageFactory } + return o.BuiltinEventStorageFactory } -func setupFactories[T any](factoryBuilder map[string]func(configMap config.ConfigMap) (T, error), - config map[string]config.ConfigMap, -) (map[string]T, error) { - factories := make(map[string]T) - for name, builder := range factoryBuilder { - f, err := builder(config[name]) - if err != nil { - return nil, fmt.Errorf("error creating factory [%s] %w", name, err) - } - factories[name] = f +func (o *ServerOption) BuiltinStateStoreFactory(_ context.Context, config *ComponentConfig) (api.StateStore, error) { + switch config.Type { + case "": + case "memory": + return memory3.NewMemoryStateStore(), nil } - return factories, nil + return nil, fmt.Errorf("unknown state store type: %s", config.Type) } -func DefaultStateStoreProvider(c *StateStoreConfig) (api.StateStoreFactory, error) { - switch strings.ToLower(*c.Type) { - case common.StateStorePebble: - return statestore.NewPebbleStateStoreFactory(c.Config) +func (o *ServerOption) GetStateStoreFactory() func(ctx context.Context, config *ComponentConfig) (api.StateStore, error) { + if o.StateStoreFactory != nil { + return o.StateStoreFactory } - return statestore.NewDefaultPebbleStateStoreFactory() + return o.BuiltinStateStoreFactory } -func WithConfig(config *Config) ServerOption { - return serverOptionFunc(func(o *serverOptions) (*serverOptions, error) { - ln, err := net.Listen("tcp", config.ListenAddr) - if err != nil { +func (o *ServerOption) BuiltinRuntimeFactory(ctx context.Context, config *ComponentConfig) (api.RuntimeAdapter, error) { + switch config.Type { + case "": + case "external": + + c := &struct { + ListenAddress string `json:"address"` + }{} + if err := config.Config.ToConfigStruct(c); err != nil { return nil, err } - o.httpListener = ln - o.enableTls = config.EnableTLS - if o.enableTls { - if config.TLSCertFile == "" || config.TLSKeyFile == "" { - return nil, fmt.Errorf("TLS certificate and key file must be provided") - } - o.tlsCertFile = config.TLSCertFile - o.tlsKeyFile = config.TLSKeyFile + ln, err := net.Listen("tcp", c.ListenAddress) + if err != nil { + return nil, fmt.Errorf("failed to listen: %w", err) } - o.tubeConfig = config.TubeConfig - o.queueConfig = config.Queue - o.runtimeConfig = config.RuntimeConfig - if config.StateStore != nil { - stateStoreFactory, err := o.stateStoreProvider(config.StateStore) - if err != nil { - return nil, err - } - o.managerOpts = append(o.managerOpts, fs.WithStateStoreFactory(stateStoreFactory)) + eaCfg := &external.Config{ + Listener: ln, + Logger: o.Logger, } - o.functionStore = config.FunctionStore - return o, nil - }) + return external.NewExternalAdapter(ctx, config.Type, eaCfg) + } + return nil, fmt.Errorf("unknown runtime type: %s", config.Type) } -func NewServer(opts ...ServerOption) (*Server, error) { - options := &serverOptions{} - options.tubeFactoryBuilders = make(map[string]func(configMap config.ConfigMap) (contube.TubeFactory, error)) - options.tubeConfig = make(map[string]config.ConfigMap) - options.runtimeFactoryBuilders = make(map[string]func(configMap config.ConfigMap) (api.FunctionRuntimeFactory, error)) - options.runtimeConfig = make(map[string]config.ConfigMap) - options.stateStoreProvider = DefaultStateStoreProvider - options.managerOpts = []fs.ManagerOption{} - for _, o := range opts { - if o == nil { - continue - } - _, err := o.apply(options) - if err != nil { - return nil, err - } - } - var log *common.Logger - if options.log == nil { - log = common.NewDefaultLogger() - } else { - log = common.NewLogger(options.log) - } - if options.httpTubeFact == nil { - options.httpTubeFact = contube.NewHttpTubeFactory(context.Background()) - log.Info("Using the default HTTP tube factory") +func (o *ServerOption) GetRuntimeFactory() func(ctx context.Context, config *ComponentConfig) (api.RuntimeAdapter, error) { + if o.RuntimeFactory != nil { + return o.RuntimeFactory } - options.managerOpts = append(options.managerOpts, - fs.WithTubeFactory("http", options.httpTubeFact), - fs.WithLogger(log.Logger)) + return o.BuiltinRuntimeFactory +} - // Config Tube Factory - if tubeFactories, err := setupFactories(options.tubeFactoryBuilders, options.tubeConfig); err == nil { - for name, f := range tubeFactories { - options.managerOpts = append(options.managerOpts, fs.WithTubeFactory(name, f)) - } - } else { - return nil, err - } +type Server struct { + handler *rest.Handler + log *zap.Logger +} - // Config Runtime Factory - if runtimeFactories, err := setupFactories(options.runtimeFactoryBuilders, options.runtimeConfig); err == nil { - for name, f := range runtimeFactories { - options.managerOpts = append(options.managerOpts, fs.WithRuntimeFactory(name, f)) - } - } else { - return nil, err - } +func NewServer(svrCtx context.Context, config *Config, opt *ServerOption) (*Server, error) { + log := opt.Logger + mgrCfg := &fs.ManagerConfig{} - // Config Queue Factory - if options.queueConfig.Type != "" { - queueFactoryBuilder, ok := options.tubeFactoryBuilders[options.queueConfig.Type] - if !ok { - return nil, fmt.Errorf("%w, queueType: %s", ErrUnsupportedQueueType, options.queueConfig.Type) - } - queueFactory, err := queueFactoryBuilder(options.queueConfig.Config) - if err != nil { - return nil, fmt.Errorf("error creating queue factory %w", err) - } - options.managerOpts = append(options.managerOpts, fs.WithQueueFactory(queueFactory)) + if opt.Logger == nil { + opt.Logger = zap.NewNop() } - manager, err := fs.NewFunctionManager(options.managerOpts...) + ln, err := net.Listen("tcp", config.ListenAddress) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to listen: %w", err) } - if options.httpListener == nil { - options.httpListener, err = net.Listen("tcp", "localhost:7300") - if err != nil { - return nil, err - } - } - var functionStore FunctionStore - if options.functionStore != "" { - functionStore, err = NewFunctionStoreImpl(manager, options.functionStore) - if err != nil { - return nil, err - } - } else { - functionStore = NewFunctionStoreDisabled() + + pkgStorage, err := opt.GetPackageStorageFactory()(svrCtx, &config.PackageLoader) + if err != nil { + return nil, fmt.Errorf("failed to create package storage: %w", err) } - err = functionStore.Load() + mgrCfg.PackageLoader = pkgStorage + log.Info("Package storage loaded", zap.String("type", config.PackageLoader.Type)) + + eventStorage, err := opt.GetEventStorageFactory()(svrCtx, &config.EventStorage) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create event storage: %w", err) } - return &Server{ - options: options, - Manager: manager, - log: log, - FunctionStore: functionStore, - }, nil -} + mgrCfg.EventStorage = eventStorage + log.Info("Event storage loaded", zap.String("type", config.EventStorage.Type)) -func NewDefaultServer() (*Server, error) { - defaultConfig := &Config{ - ListenAddr: ":7300", - Queue: QueueConfig{ - Type: common.MemoryTubeType, - Config: config.ConfigMap{}, - }, - TubeConfig: map[string]config.ConfigMap{ - common.PulsarTubeType: { - contube.PulsarURLKey: "pulsar://localhost:6650", - }, - }, - RuntimeConfig: map[string]config.ConfigMap{}, + stateStore, err := opt.GetStateStoreFactory()(svrCtx, &config.StateStore) + if err != nil { + return nil, fmt.Errorf("failed to create state store: %w", err) } - return NewServer( - WithTubeFactoryBuilders(GetBuiltinTubeFactoryBuilder()), - WithRuntimeFactoryBuilders(GetBuiltinRuntimeFactoryBuilder()), - WithConfig(defaultConfig)) -} + mgrCfg.StateStore = stateStore + log.Info("State store loaded", zap.String("type", config.StateStore.Type)) -func (s *Server) Run(context context.Context) { - s.log.Info("Hello from the function stream server!") - go func() { - <-context.Done() - err := s.Close() + runtimeMap := make(map[string]api.RuntimeAdapter) + for _, rtCfg := range config.Runtimes { + runtime, err := opt.GetRuntimeFactory()(svrCtx, &rtCfg) if err != nil { - s.log.Error(err, "failed to shutdown server") - return + return nil, fmt.Errorf("failed to create runtime: %w", err) } - }() - err := s.startRESTHandlers() - if err != nil && !errors.Is(err, http.ErrServerClosed) { - s.log.Error(err, "Error starting REST handlers") + runtimeMap[rtCfg.Type] = runtime + log.Info("Runtime loaded", zap.String("type", rtCfg.Type)) } -} - -func (s *Server) startRESTHandlers() error { - - statusSvr := new(restful.WebService) - statusSvr.Path("/api/v1/status") - statusSvr.Route(statusSvr.GET("/").To(func(request *restful.Request, response *restful.Response) { - response.WriteHeader(http.StatusOK) - }). - Doc("Get the status of the Function Stream"). - Metadata(restfulspec.KeyOpenAPITags, []string{"status"}). - Operation("getStatus")) + mgrCfg.RuntimeMap = runtimeMap - container := restful.NewContainer() - container.Add(s.makeFunctionService()) - container.Add(s.makeTubeService()) - container.Add(s.makeStateService()) - container.Add(s.makeHttpTubeService()) - container.Add(s.makeFunctionStoreService()) - container.Add(statusSvr) - - cors := restful.CrossOriginResourceSharing{ - AllowedHeaders: []string{"Content-Type", "Accept"}, - AllowedMethods: []string{"GET", "POST", "OPTIONS", "PUT", "DELETE"}, - CookiesAllowed: false, - Container: container} - container.Filter(cors.Filter) - container.Filter(container.OPTIONSFilter) - - config := restfulspec.Config{ - WebServices: container.RegisteredWebServices(), - APIPath: "/apidocs", - PostBuildSwaggerObjectHandler: enrichSwaggerObject} - container.Add(restfulspec.NewOpenAPIService(config)) - - httpSvr := &http.Server{ - Handler: container.ServeMux, + mgr, err := fs.NewManager(*mgrCfg, log.Named("manager")) + if err != nil { + return nil, fmt.Errorf("failed to create manager: %w", err) } - s.httpSvr.Store(httpSvr) - if s.options.enableTls { - return httpSvr.ServeTLS(s.options.httpListener, s.options.tlsCertFile, s.options.tlsKeyFile) - } else { - return httpSvr.Serve(s.options.httpListener) + handler := &rest.Handler{ + Manager: mgr, + PackageStorage: pkgStorage, + EventStorage: eventStorage, + EnableTls: config.EnableTLS, + TlsCertFile: config.TLSCertFile, + TlsKeyFile: config.TLSKeyFile, + HttpListener: ln, + Log: opt.Logger, } -} -func enrichSwaggerObject(swo *spec.Swagger) { - swo.Info = &spec.Info{ - InfoProps: spec.InfoProps{ - Title: "Function Stream Service", - Description: "Manage Function Stream Resources", - Contact: &spec.ContactInfo{ - ContactInfoProps: spec.ContactInfoProps{ - Name: "Function Stream Org", - URL: "https://github.com/FunctionStream", - }, - }, - License: &spec.License{ - LicenseProps: spec.LicenseProps{ - Name: "Apache 2", - URL: "http://www.apache.org/licenses/", - }, - }, - Version: "1.0.0", - }, - } - swo.Host = "localhost:7300" - swo.Schemes = []string{"http"} - swo.Tags = []spec.Tag{ - { - TagProps: spec.TagProps{ - Name: "function", - Description: "Managing functions"}, - }, - { - TagProps: spec.TagProps{ - Name: "tube", - Description: "Managing tubes"}, - }, - { - TagProps: spec.TagProps{ - Name: "state", - Description: "Managing state"}, - }, - { - TagProps: spec.TagProps{ - Name: "http-tube", - Description: "Managing HTTP tubes"}, - }, - { - TagProps: spec.TagProps{ - Name: "function-store", - Description: "Managing function store"}, - }, - } + return &Server{ + handler: handler, + log: opt.Logger, + }, nil } -func (s *Server) WaitForReady(ctx context.Context) <-chan struct{} { - c := make(chan struct{}) - detect := func() bool { - u := (&url.URL{ - Scheme: "http", - Host: s.options.httpListener.Addr().String(), - Path: "/api/v1/status", - }).String() - req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) - if err != nil { - s.log.Error(err, "Failed to create detect request") - return false - } - client := &http.Client{} - _, err = client.Do(req) - if err != nil { - s.log.Info("Detect connection to server failed", "error", err) - } - s.log.Info("Server is ready", "address", s.options.httpListener.Addr().String()) - return true - } +func (s *Server) Run(ctx context.Context) error { go func() { - defer close(c) - - if detect() { - return + <-ctx.Done() + if err := s.handler.Stop(); err != nil { + s.log.Error("Failed to stop REST handler", zap.Error(err)) } - // Try to connect to the server - for { - select { - case <-ctx.Done(): - return - case <-time.After(1 * time.Second): - if detect() { - return - } - } - } - }() - return c -} -func (s *Server) Close() error { - s.log.Info("Shutting down function stream server") - if httpSvr := s.httpSvr.Load(); httpSvr != nil { - if err := httpSvr.Close(); err != nil { - return err - } - } - if s.Manager != nil { - err := s.Manager.Close() - if err != nil { - return err - } - } - s.log.Info("Function stream server is shut down") - return nil + }() + return s.handler.Run() } -func (s *Server) handleRestError(e error) { - if e == nil { - return - } - s.log.Error(e, "Error handling REST request") +func (s *Server) WaitForReady(ctx context.Context) <-chan struct{} { + return s.handler.WaitForReady(ctx) } diff --git a/serverold/config.go b/serverold/config.go new file mode 100644 index 00000000..8506f49a --- /dev/null +++ b/serverold/config.go @@ -0,0 +1,121 @@ +/* + * Copyright 2024 Function Stream Org. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package serverold + +import ( + "fmt" + "os" + "strings" + + "github.com/functionstream/function-stream/common/config" + + "github.com/go-playground/validator/v10" + + "github.com/spf13/viper" +) + +type FactoryConfig struct { + // Deprecate + Ref *string `mapstructure:"ref"` + Type *string `mapstructure:"type"` + Config *config.ConfigMap `mapstructure:"config"` +} + +type StateStoreConfig struct { + Type *string `mapstructure:"type"` + Config config.ConfigMap `mapstructure:"config"` +} + +type QueueConfig struct { + Type string `mapstructure:"type"` + Config config.ConfigMap `mapstructure:"config"` +} + +type Config struct { + // ListenAddr is the address that the function stream REST service will listen on. + ListenAddr string `mapstructure:"listen-addr"` + + Queue QueueConfig `mapstructure:"queue"` + + TubeConfig map[string]config.ConfigMap `mapstructure:"tube-config"` + + RuntimeConfig map[string]config.ConfigMap `mapstructure:"runtime-config"` + + // StateStore is the configuration for the state store that the function stream server will use. + // Optional + StateStore *StateStoreConfig `mapstructure:"state-store"` + + // FunctionStore is the path to the function store + FunctionStore string `mapstructure:"function-store"` + + EnableTLS bool `mapstructure:"enable-tls"` + TLSCertFile string `mapstructure:"tls-cert-file"` + TLSKeyFile string `mapstructure:"tls-key-file"` +} + +func init() { + viper.SetDefault("listen-addr", ":7300") + viper.SetDefault("function-store", "./functions") +} + +func (c *Config) PreprocessConfig() error { + if c.ListenAddr == "" { + return fmt.Errorf("ListenAddr shouldn't be empty") + } + validate := validator.New() + if err := validate.Struct(c); err != nil { + return err + } + return nil +} + +func loadConfig() (*Config, error) { + var c Config + if err := viper.Unmarshal(&c); err != nil { + return nil, err + } + if err := c.PreprocessConfig(); err != nil { + return nil, err + } + return &c, nil +} + +const envPrefix = "FS_" + +func LoadConfigFromFile(filePath string) (*Config, error) { + viper.SetConfigFile(filePath) + if err := viper.ReadInConfig(); err != nil { + return nil, err + } + return loadConfig() +} + +func LoadConfigFromEnv() (*Config, error) { + for _, env := range os.Environ() { + if strings.HasPrefix(env, envPrefix) { + parts := strings.SplitN(strings.TrimPrefix(env, envPrefix), "=", 2) + key := parts[0] + value := parts[1] + + key = strings.Replace(key, "__", ".", -1) + key = strings.Replace(key, "_", "-", -1) + viper.Set(key, value) + } + } + + return loadConfig() +} diff --git a/serverold/config_test.go b/serverold/config_test.go new file mode 100644 index 00000000..cf9fb9ee --- /dev/null +++ b/serverold/config_test.go @@ -0,0 +1,59 @@ +/* + * Copyright 2024 Function Stream Org. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package serverold + +import ( + "os" + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLoadConfigFromYaml(t *testing.T) { + c, err := LoadConfigFromFile("../tests/test_config.yaml") + require.Nil(t, err) + assertConfig(t, c) +} + +func TestLoadConfigFromJson(t *testing.T) { + c, err := LoadConfigFromFile("../tests/test_config.json") + require.Nil(t, err) + assertConfig(t, c) +} + +func TestLoadConfigFromEnv(t *testing.T) { + assert.Nil(t, os.Setenv("FS_LISTEN_ADDR", ":17300")) + assert.Nil(t, os.Setenv("FS_TUBE_CONFIG__MY_TUBE__KEY", "value")) + assert.Nil(t, os.Setenv("FS_RUNTIME_CONFIG__CUSTOM_RUNTIME__NAME", "test")) + + viper.AutomaticEnv() + + c, err := LoadConfigFromEnv() + require.Nil(t, err) + assertConfig(t, c) +} + +func assertConfig(t *testing.T, c *Config) { + assert.Equal(t, ":17300", c.ListenAddr) + require.Contains(t, c.TubeConfig, "my-tube") + assert.Equal(t, "value", c.TubeConfig["my-tube"]["key"]) + + require.Contains(t, c.RuntimeConfig, "custom-runtime") + assert.Equal(t, "test", c.RuntimeConfig["custom-runtime"]["name"]) +} diff --git a/server/function_service.go b/serverold/function_service.go similarity index 99% rename from server/function_service.go rename to serverold/function_service.go index 4168103b..baea6815 100644 --- a/server/function_service.go +++ b/serverold/function_service.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package server +package serverold import ( "errors" diff --git a/server/function_store.go b/serverold/function_store.go similarity index 95% rename from server/function_store.go rename to serverold/function_store.go index 1d8d609d..ff0964d0 100644 --- a/server/function_store.go +++ b/serverold/function_store.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package server +package serverold import ( "io" @@ -30,7 +30,7 @@ import ( restfulspec "github.com/emicklei/go-restful-openapi/v2" "github.com/emicklei/go-restful/v3" "github.com/functionstream/function-stream/common/model" - "github.com/functionstream/function-stream/fs" + "github.com/functionstream/function-stream/fsold" "github.com/pkg/errors" "gopkg.in/yaml.v3" ) @@ -43,7 +43,7 @@ type FunctionStore interface { type FunctionStoreImpl struct { mu sync.Mutex - fm fs.FunctionManager + fm fsold.FunctionManager path string loadedFunctions map[string]*model.Function loadingFunctions map[string]*model.Function @@ -134,7 +134,7 @@ func (f *FunctionStoreImpl) loadFile(path string) error { return nil } -func NewFunctionStoreImpl(fm fs.FunctionManager, path string) (FunctionStore, error) { +func NewFunctionStoreImpl(fm fsold.FunctionManager, path string) (FunctionStore, error) { return &FunctionStoreImpl{ fm: fm, path: path, diff --git a/server/function_store_test.go b/serverold/function_store_test.go similarity index 94% rename from server/function_store_test.go rename to serverold/function_store_test.go index 2651d114..5294d8d8 100644 --- a/server/function_store_test.go +++ b/serverold/function_store_test.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package server +package serverold import ( "os" @@ -22,9 +22,9 @@ import ( "github.com/functionstream/function-stream/common" "github.com/functionstream/function-stream/common/model" - "github.com/functionstream/function-stream/fs" - "github.com/functionstream/function-stream/fs/api" - "github.com/functionstream/function-stream/fs/contube" + "github.com/functionstream/function-stream/fsold" + "github.com/functionstream/function-stream/fsold/api" + "github.com/functionstream/function-stream/fsold/contube" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" ) @@ -63,7 +63,7 @@ func (t *testFunctionManagerImpl) Close() error { return nil } -func newTestFunctionManagerImpl() fs.FunctionManager { +func newTestFunctionManagerImpl() fsold.FunctionManager { return &testFunctionManagerImpl{ functions: make(map[common.NamespacedName]*model.Function), } diff --git a/server/http_tube_service.go b/serverold/http_tube_service.go similarity index 98% rename from server/http_tube_service.go rename to serverold/http_tube_service.go index 71fa75da..de942bf4 100644 --- a/server/http_tube_service.go +++ b/serverold/http_tube_service.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package server +package serverold import ( "net/http" diff --git a/serverold/server.go b/serverold/server.go new file mode 100644 index 00000000..40a3734e --- /dev/null +++ b/serverold/server.go @@ -0,0 +1,548 @@ +/* + * Copyright 2024 Function Stream Org. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package serverold + +import ( + "context" + "fmt" + "net" + "net/http" + "net/url" + "strings" + "sync/atomic" + "time" + + "github.com/functionstream/function-stream/common/config" + + "github.com/functionstream/function-stream/fsold/runtime/external" + + "github.com/go-logr/logr" + + restfulspec "github.com/emicklei/go-restful-openapi/v2" + "github.com/emicklei/go-restful/v3" + "github.com/functionstream/function-stream/common" + "github.com/functionstream/function-stream/fsold" + "github.com/functionstream/function-stream/fsold/api" + "github.com/functionstream/function-stream/fsold/contube" + "github.com/functionstream/function-stream/fsold/runtime/wazero" + "github.com/functionstream/function-stream/fsold/statestore" + "github.com/go-openapi/spec" + "github.com/pkg/errors" +) + +var ( + ErrUnsupportedStateStore = errors.New("unsupported state store") + ErrUnsupportedQueueType = errors.New("unsupported queue type") +) + +type Server struct { + options *serverOptions + httpSvr atomic.Pointer[http.Server] + log *common.Logger + Manager fsold.FunctionManager + FunctionStore FunctionStore +} + +type TubeLoaderType func(c *FactoryConfig) (contube.TubeFactory, error) +type RuntimeLoaderType func(c *FactoryConfig) (api.FunctionRuntimeFactory, error) +type StateStoreProviderType func(c *StateStoreConfig) (api.StateStoreFactory, error) + +type serverOptions struct { + httpListener net.Listener + managerOpts []fsold.ManagerOption + httpTubeFact *contube.HttpTubeFactory + stateStoreProvider StateStoreProviderType + functionStore string + enableTls bool + tlsCertFile string + tlsKeyFile string + tubeFactoryBuilders map[string]func(configMap config.ConfigMap) (contube.TubeFactory, error) + tubeConfig map[string]config.ConfigMap + runtimeFactoryBuilders map[string]func(configMap config.ConfigMap) (api.FunctionRuntimeFactory, error) + runtimeConfig map[string]config.ConfigMap + queueConfig QueueConfig + log *logr.Logger +} + +type ServerOption interface { + apply(option *serverOptions) (*serverOptions, error) +} + +type serverOptionFunc func(*serverOptions) (*serverOptions, error) + +func (f serverOptionFunc) apply(c *serverOptions) (*serverOptions, error) { + return f(c) +} + +// WithHttpListener sets the listener for the HTTP server. +// If not set, the server will listen on the Config.ListenAddr. +func WithHttpListener(listener net.Listener) ServerOption { + return serverOptionFunc(func(o *serverOptions) (*serverOptions, error) { + o.httpListener = listener + return o, nil + }) +} + +// WithHttpTubeFactory sets the factory for the HTTP tube. +// If not set, the server will use the default HTTP tube factory. +func WithHttpTubeFactory(factory *contube.HttpTubeFactory) ServerOption { + return serverOptionFunc(func(o *serverOptions) (*serverOptions, error) { + o.httpTubeFact = factory + return o, nil + }) +} + +func WithQueueConfig(config QueueConfig) ServerOption { + return serverOptionFunc(func(o *serverOptions) (*serverOptions, error) { + o.queueConfig = config + return o, nil + }) +} + +func WithTubeFactoryBuilder( + name string, + builder func(configMap config.ConfigMap) (contube.TubeFactory, error), +) ServerOption { + return serverOptionFunc(func(o *serverOptions) (*serverOptions, error) { + o.tubeFactoryBuilders[name] = builder + return o, nil + }) +} + +func WithTubeFactoryBuilders( + builder map[string]func(configMap config.ConfigMap, + ) (contube.TubeFactory, error)) ServerOption { + return serverOptionFunc(func(o *serverOptions) (*serverOptions, error) { + for n, b := range builder { + o.tubeFactoryBuilders[n] = b + } + return o, nil + }) +} + +func WithRuntimeFactoryBuilder( + name string, + builder func(configMap config.ConfigMap) (api.FunctionRuntimeFactory, error), +) ServerOption { + return serverOptionFunc(func(o *serverOptions) (*serverOptions, error) { + o.runtimeFactoryBuilders[name] = builder + return o, nil + }) +} + +func WithRuntimeFactoryBuilders( + builder map[string]func(configMap config.ConfigMap) (api.FunctionRuntimeFactory, error), +) ServerOption { + return serverOptionFunc(func(o *serverOptions) (*serverOptions, error) { + for n, b := range builder { + o.runtimeFactoryBuilders[n] = b + } + return o, nil + }) +} + +func WithStateStoreLoader(loader func(c *StateStoreConfig) (api.StateStoreFactory, error)) ServerOption { + return serverOptionFunc(func(o *serverOptions) (*serverOptions, error) { + o.stateStoreProvider = loader + return o, nil + }) +} + +func WithPackageLoader(packageLoader api.PackageLoader) ServerOption { + return serverOptionFunc(func(o *serverOptions) (*serverOptions, error) { + o.managerOpts = append(o.managerOpts, fsold.WithPackageLoader(packageLoader)) + return o, nil + }) +} + +func WithLogger(log *logr.Logger) ServerOption { + return serverOptionFunc(func(c *serverOptions) (*serverOptions, error) { + c.log = log + return c, nil + }) +} + +func GetBuiltinTubeFactoryBuilder() map[string]func(configMap config.ConfigMap) (contube.TubeFactory, error) { + return map[string]func(configMap config.ConfigMap) (contube.TubeFactory, error){ + common.PulsarTubeType: func(configMap config.ConfigMap) (contube.TubeFactory, error) { + return contube.NewPulsarEventQueueFactory(context.Background(), contube.ConfigMap(configMap)) + }, + //nolint:unparam + common.MemoryTubeType: func(_ config.ConfigMap) (contube.TubeFactory, error) { + return contube.NewMemoryQueueFactory(context.Background()), nil + }, + //nolint:unparam + common.EmptyTubeType: func(_ config.ConfigMap) (contube.TubeFactory, error) { + return contube.NewEmptyTubeFactory(), nil + }, + } +} + +func GetBuiltinRuntimeFactoryBuilder() map[string]func(configMap config.ConfigMap) (api.FunctionRuntimeFactory, error) { + return map[string]func(configMap config.ConfigMap) (api.FunctionRuntimeFactory, error){ + //nolint:unparam + common.WASMRuntime: func(configMap config.ConfigMap) (api.FunctionRuntimeFactory, error) { + return wazero.NewWazeroFunctionRuntimeFactory(), nil + }, + common.ExternalRuntime: func(configMap config.ConfigMap) (api.FunctionRuntimeFactory, error) { + return external.NewFactoryWithConfig(configMap) + }, + } +} + +func setupFactories[T any](factoryBuilder map[string]func(configMap config.ConfigMap) (T, error), + config map[string]config.ConfigMap, +) (map[string]T, error) { + factories := make(map[string]T) + for name, builder := range factoryBuilder { + f, err := builder(config[name]) + if err != nil { + return nil, fmt.Errorf("error creating factory [%s] %w", name, err) + } + factories[name] = f + } + return factories, nil +} + +func DefaultStateStoreProvider(c *StateStoreConfig) (api.StateStoreFactory, error) { + switch strings.ToLower(*c.Type) { + case common.StateStorePebble: + return statestore.NewPebbleStateStoreFactory(c.Config) + } + return statestore.NewDefaultPebbleStateStoreFactory() +} + +func WithConfig(config *Config) ServerOption { + return serverOptionFunc(func(o *serverOptions) (*serverOptions, error) { + ln, err := net.Listen("tcp", config.ListenAddr) + if err != nil { + return nil, err + } + o.httpListener = ln + o.enableTls = config.EnableTLS + if o.enableTls { + if config.TLSCertFile == "" || config.TLSKeyFile == "" { + return nil, fmt.Errorf("TLS certificate and key file must be provided") + } + o.tlsCertFile = config.TLSCertFile + o.tlsKeyFile = config.TLSKeyFile + } + o.tubeConfig = config.TubeConfig + o.queueConfig = config.Queue + o.runtimeConfig = config.RuntimeConfig + if config.StateStore != nil { + stateStoreFactory, err := o.stateStoreProvider(config.StateStore) + if err != nil { + return nil, err + } + o.managerOpts = append(o.managerOpts, fsold.WithStateStoreFactory(stateStoreFactory)) + } + o.functionStore = config.FunctionStore + return o, nil + }) +} + +func NewServer(opts ...ServerOption) (*Server, error) { + options := &serverOptions{} + options.tubeFactoryBuilders = make(map[string]func(configMap config.ConfigMap) (contube.TubeFactory, error)) + options.tubeConfig = make(map[string]config.ConfigMap) + options.runtimeFactoryBuilders = make(map[string]func(configMap config.ConfigMap) (api.FunctionRuntimeFactory, error)) + options.runtimeConfig = make(map[string]config.ConfigMap) + options.stateStoreProvider = DefaultStateStoreProvider + options.managerOpts = []fsold.ManagerOption{} + for _, o := range opts { + if o == nil { + continue + } + _, err := o.apply(options) + if err != nil { + return nil, err + } + } + var log *common.Logger + if options.log == nil { + log = common.NewDefaultLogger() + } else { + log = common.NewLogger(options.log) + } + if options.httpTubeFact == nil { + options.httpTubeFact = contube.NewHttpTubeFactory(context.Background()) + log.Info("Using the default HTTP tube factory") + } + options.managerOpts = append(options.managerOpts, + fsold.WithTubeFactory("http", options.httpTubeFact), + fsold.WithLogger(log.Logger)) + + // Config Tube Factory + if tubeFactories, err := setupFactories(options.tubeFactoryBuilders, options.tubeConfig); err == nil { + for name, f := range tubeFactories { + options.managerOpts = append(options.managerOpts, fsold.WithTubeFactory(name, f)) + } + } else { + return nil, err + } + + // Config Runtime Factory + if runtimeFactories, err := setupFactories(options.runtimeFactoryBuilders, options.runtimeConfig); err == nil { + for name, f := range runtimeFactories { + options.managerOpts = append(options.managerOpts, fsold.WithRuntimeFactory(name, f)) + } + } else { + return nil, err + } + + // Config Queue Factory + if options.queueConfig.Type != "" { + queueFactoryBuilder, ok := options.tubeFactoryBuilders[options.queueConfig.Type] + if !ok { + return nil, fmt.Errorf("%w, queueType: %s", ErrUnsupportedQueueType, options.queueConfig.Type) + } + queueFactory, err := queueFactoryBuilder(options.queueConfig.Config) + if err != nil { + return nil, fmt.Errorf("error creating queue factory %w", err) + } + options.managerOpts = append(options.managerOpts, fsold.WithQueueFactory(queueFactory)) + } + + manager, err := fsold.NewFunctionManager(options.managerOpts...) + if err != nil { + return nil, err + } + if options.httpListener == nil { + options.httpListener, err = net.Listen("tcp", "localhost:7300") + if err != nil { + return nil, err + } + } + var functionStore FunctionStore + if options.functionStore != "" { + functionStore, err = NewFunctionStoreImpl(manager, options.functionStore) + if err != nil { + return nil, err + } + } else { + functionStore = NewFunctionStoreDisabled() + } + err = functionStore.Load() + if err != nil { + return nil, err + } + return &Server{ + options: options, + Manager: manager, + log: log, + FunctionStore: functionStore, + }, nil +} + +func NewDefaultServer() (*Server, error) { + defaultConfig := &Config{ + ListenAddr: ":7300", + Queue: QueueConfig{ + Type: common.MemoryTubeType, + Config: config.ConfigMap{}, + }, + TubeConfig: map[string]config.ConfigMap{ + common.PulsarTubeType: { + contube.PulsarURLKey: "pulsar://localhost:6650", + }, + }, + RuntimeConfig: map[string]config.ConfigMap{}, + } + return NewServer( + WithTubeFactoryBuilders(GetBuiltinTubeFactoryBuilder()), + WithRuntimeFactoryBuilders(GetBuiltinRuntimeFactoryBuilder()), + WithConfig(defaultConfig)) +} + +func (s *Server) Run(context context.Context) { + s.log.Info("Hello from the function stream server!") + go func() { + <-context.Done() + err := s.Close() + if err != nil { + s.log.Error(err, "failed to shutdown server") + return + } + }() + err := s.startRESTHandlers() + if err != nil && !errors.Is(err, http.ErrServerClosed) { + s.log.Error(err, "Error starting REST handlers") + } +} + +func (s *Server) startRESTHandlers() error { + + statusSvr := new(restful.WebService) + statusSvr.Path("/api/v1/status"). + Route(statusSvr.GET("/").To(func(request *restful.Request, response *restful.Response) { + response.WriteHeader(http.StatusOK) + }). + Doc("Get the status of the Function Stream"). + Metadata(restfulspec.KeyOpenAPITags, []string{"status"}). + Operation("getStatus")) + + container := restful.NewContainer() + container.Add(s.makeFunctionService()) + container.Add(s.makeTubeService()) + container.Add(s.makeStateService()) + container.Add(s.makeHttpTubeService()) + container.Add(s.makeFunctionStoreService()) + container.Add(statusSvr) + + cors := restful.CrossOriginResourceSharing{ + AllowedHeaders: []string{"Content-Type", "Accept"}, + AllowedMethods: []string{"GET", "POST", "OPTIONS", "PUT", "DELETE"}, + CookiesAllowed: false, + Container: container} + container.Filter(cors.Filter) + container.Filter(container.OPTIONSFilter) + + config := restfulspec.Config{ + WebServices: container.RegisteredWebServices(), + APIPath: "/apidocs", + PostBuildSwaggerObjectHandler: enrichSwaggerObject} + container.Add(restfulspec.NewOpenAPIService(config)) + + httpSvr := &http.Server{ + Handler: container.ServeMux, + } + s.httpSvr.Store(httpSvr) + + if s.options.enableTls { + return httpSvr.ServeTLS(s.options.httpListener, s.options.tlsCertFile, s.options.tlsKeyFile) + } else { + return httpSvr.Serve(s.options.httpListener) + } +} + +func enrichSwaggerObject(swo *spec.Swagger) { + swo.Info = &spec.Info{ + InfoProps: spec.InfoProps{ + Title: "Function Stream Service", + Description: "Manage Function Stream Resources", + Contact: &spec.ContactInfo{ + ContactInfoProps: spec.ContactInfoProps{ + Name: "Function Stream Org", + URL: "https://github.com/FunctionStream", + }, + }, + License: &spec.License{ + LicenseProps: spec.LicenseProps{ + Name: "Apache 2", + URL: "http://www.apache.org/licenses/", + }, + }, + Version: "1.0.0", + }, + } + swo.Host = "localhost:7300" + swo.Schemes = []string{"http"} + swo.Tags = []spec.Tag{ + { + TagProps: spec.TagProps{ + Name: "function", + Description: "Managing functions"}, + }, + { + TagProps: spec.TagProps{ + Name: "tube", + Description: "Managing tubes"}, + }, + { + TagProps: spec.TagProps{ + Name: "state", + Description: "Managing state"}, + }, + { + TagProps: spec.TagProps{ + Name: "http-tube", + Description: "Managing HTTP tubes"}, + }, + { + TagProps: spec.TagProps{ + Name: "function-store", + Description: "Managing function store"}, + }, + } +} + +func (s *Server) WaitForReady(ctx context.Context) <-chan struct{} { + c := make(chan struct{}) + detect := func() bool { + u := (&url.URL{ + Scheme: "http", + Host: s.options.httpListener.Addr().String(), + Path: "/api/v1/status", + }).String() + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) + if err != nil { + s.log.Error(err, "Failed to create detect request") + return false + } + client := &http.Client{} + _, err = client.Do(req) + if err != nil { + s.log.Info("Detect connection to server failed", "error", err) + } + s.log.Info("Server is ready", "address", s.options.httpListener.Addr().String()) + return true + } + go func() { + defer close(c) + + if detect() { + return + } + // Try to connect to the server + for { + select { + case <-ctx.Done(): + return + case <-time.After(1 * time.Second): + if detect() { + return + } + } + } + }() + return c +} + +func (s *Server) Close() error { + s.log.Info("Shutting down function stream server") + if httpSvr := s.httpSvr.Load(); httpSvr != nil { + if err := httpSvr.Close(); err != nil { + return err + } + } + if s.Manager != nil { + err := s.Manager.Close() + if err != nil { + return err + } + } + s.log.Info("Function stream server is shut down") + return nil +} + +func (s *Server) handleRestError(e error) { + if e == nil { + return + } + s.log.Error(e, "Error handling REST request") +} diff --git a/server/server_test.go b/serverold/server_test.go similarity index 98% rename from server/server_test.go rename to serverold/server_test.go index 1cfb909a..251ae776 100644 --- a/server/server_test.go +++ b/serverold/server_test.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package server +package serverold import ( "context" @@ -32,8 +32,8 @@ import ( adminclient "github.com/functionstream/function-stream/admin/client" "github.com/functionstream/function-stream/common" "github.com/functionstream/function-stream/common/model" - "github.com/functionstream/function-stream/fs/api" - "github.com/functionstream/function-stream/fs/contube" + "github.com/functionstream/function-stream/fsold/api" + "github.com/functionstream/function-stream/fsold/contube" "github.com/functionstream/function-stream/tests" "github.com/stretchr/testify/assert" ) diff --git a/server/state_service.go b/serverold/state_service.go similarity index 99% rename from server/state_service.go rename to serverold/state_service.go index c942638f..00003a9c 100644 --- a/server/state_service.go +++ b/serverold/state_service.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package server +package serverold import ( "io" diff --git a/server/tube_service.go b/serverold/tube_service.go similarity index 97% rename from server/tube_service.go rename to serverold/tube_service.go index adc8ea7f..4ea8ee21 100644 --- a/server/tube_service.go +++ b/serverold/tube_service.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package server +package serverold import ( "io" @@ -22,7 +22,7 @@ import ( restfulspec "github.com/emicklei/go-restful-openapi/v2" "github.com/emicklei/go-restful/v3" - "github.com/functionstream/function-stream/fs/contube" + "github.com/functionstream/function-stream/fsold/contube" ) // Due to this issue: https://github.com/emicklei/go-restful-openapi/issues/115, diff --git a/tests/integration_test.go b/tests/integration_test.go index f594809f..a7e01e47 100644 --- a/tests/integration_test.go +++ b/tests/integration_test.go @@ -1,137 +1,211 @@ -/* - * Copyright 2024 Function Stream Org. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package tests import ( "context" "encoding/json" - "io" - "math/rand" - "strconv" - "testing" - - "github.com/apache/pulsar-client-go/pulsar" - adminclient "github.com/functionstream/function-stream/admin/client" - "github.com/functionstream/function-stream/admin/utils" - "github.com/functionstream/function-stream/common" + "github.com/functionstream/function-stream/apiclient" + "github.com/functionstream/function-stream/fs/model" + "github.com/functionstream/function-stream/pkg/testfunctions" + "github.com/functionstream/function-stream/pkg/testutil" "github.com/functionstream/function-stream/server" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + "net/http" + "testing" + "time" ) -func startServer() { - common.RunProcess(func() (io.Closer, error) { - s, err := server.NewDefaultServer() - if err != nil { - return nil, err - } - go s.Run(context.Background()) - return s, nil +func TestIntegration(t *testing.T) { + l := testutil.GetTestLogger(t) + cfgContent := ` +listen-address: ":17300" +package-loader: + type: memory +event-storage: + type: memory +state-store: + type: memory +runtimes: + - type: external + config: + address: ":17400" +` + cfgPath, cleanup := testutil.CreateTempFile(t, cfgContent, "*.yaml") + defer cleanup() + cfg, err := server.LoadConfigFromFile(cfgPath) + require.NoError(t, err) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + svr, err := server.NewServer(ctx, cfg, &server.ServerOption{ + Logger: l, }) -} + require.NoError(t, err) + go func() { + err := svr.Run(ctx) + if errors.Is(err, http.ErrServerClosed) { + return + } + require.NoError(t, err) + }() + <-svr.WaitForReady(ctx) -func init() { - go startServer() -} + apiCliCfg := apiclient.NewConfiguration() + apiCliCfg.Debug = true + apiCliCfg.Host = "localhost:17300" + apiCli := apiclient.NewAPIClient(apiCliCfg) -func TestBasicFunction(t *testing.T) { + pkg := &model.Package{ + Name: "test-pkg", + Type: "external", + Modules: map[string]model.ModuleConfig{ + "default": {}, + "counter": {}, + "test-source": {}, + "test-sink": {}, + }, + } - cfg := adminclient.NewConfiguration() - cli := adminclient.NewAPIClient(cfg) + err = apiCli.PackageService().Create(ctx, pkg) + require.NoError(t, err) + readPkg, err := apiCli.PackageService().Read(ctx, pkg.Name) + require.NoError(t, err) + require.Equal(t, pkg, readPkg) + testfunctions.SetupFSTarget(t, "dns:///localhost:17400") + go testfunctions.NewTestModules().Run(context.Background()) + time.Sleep(1 * time.Second) - client, err := pulsar.NewClient(pulsar.ClientOptions{ - URL: "pulsar://localhost:6650", - }) - if err != nil { - t.Fatalf(err.Error()) + consume := func(topic string) (<-chan string, context.CancelFunc) { + ctx, cancel := context.WithCancel(ctx) + recvCh, _ := apiCli.EventsService().Consume(ctx, "output") + return recvCh, cancel } - name := "func-" + strconv.Itoa(rand.Int()) - inputTopic := "test-input-" + strconv.Itoa(rand.Int()) - outputTopic := "test-output-" + strconv.Itoa(rand.Int()) - f := adminclient.ModelFunction{ - Name: name, - Runtime: adminclient.ModelRuntimeConfig{ - Type: common.WASMRuntime, - Config: map[string]interface{}{ - common.RuntimeArchiveConfigKey: "../bin/example_basic.wasm", + t.Run("Default Module", func(t *testing.T) { + f := model.Function{ + Name: "test-default", + Package: pkg.Name, + Module: "default", + Sources: []model.TopicConfig{ + { + Name: "input", + }, }, - }, - Source: utils.MakePulsarSourceTubeConfig(inputTopic), - Sink: *utils.MakePulsarSinkTubeConfig(outputTopic), - Replicas: 1, - } + Sink: model.TopicConfig{ + Name: "output", + }, + } + err = apiCli.FunctionService().Create(ctx, &f) + require.NoError(t, err) - producer, err := client.CreateProducer(pulsar.ProducerOptions{ - Topic: inputTopic, - }) - if err != nil { - t.Fatalf(err.Error()) - } + recvCh, cancel := consume("output") + defer cancel() + time.Sleep(1 * time.Second) + p := &testfunctions.Person{ + Name: "test", + Money: 1, + } + require.NoError(t, apiCli.EventsService().Produce(ctx, "input", p)) + output := <-recvCh + require.NoError(t, json.Unmarshal([]byte(output), p)) + require.Equal(t, 2, p.Money) - consumer, err := client.Subscribe(pulsar.ConsumerOptions{ - Topic: outputTopic, - SubscriptionName: "test-sub", + require.NoError(t, apiCli.FunctionService().Delete(ctx, f.Name)) }) - if err != nil { - t.Fatalf(err.Error()) - } - res, err := cli.FunctionAPI.CreateFunction(context.Background()).Body(f).Execute() - if err != nil && res == nil { - t.Errorf("failed to create function: %v", err) - } - if res.StatusCode != 200 { - body, _ := io.ReadAll(res.Body) - t.Fatalf("expected 200, got %d: %s", res.StatusCode, body) - return - } + t.Run("Counter Module", func(t *testing.T) { + f := model.Function{ + Name: "test-default", + Package: pkg.Name, + Module: "counter", + Sources: []model.TopicConfig{ + { + Name: "input", + }, + }, + Sink: model.TopicConfig{ + Name: "output", + }, + } + err = apiCli.FunctionService().Create(ctx, &f) + require.NoError(t, err) + time.Sleep(1 * time.Second) - for i := 0; i < 10; i++ { - p := Person{Name: "rbt", Money: 0} - jsonBytes, err := json.Marshal(p) - if err != nil { - t.Fatalf(err.Error()) + recvCh, cancel := consume("output") + defer cancel() + e := &testfunctions.Counter{ + Count: 1, } - _, err = producer.Send(context.Background(), &pulsar.ProducerMessage{ - Payload: jsonBytes, - }) - if err != nil { - return + require.NoError(t, apiCli.EventsService().Produce(ctx, "input", e)) + output := <-recvCh + require.NoError(t, json.Unmarshal([]byte(output), e)) + require.Equal(t, 2, e.Count) + + require.NoError(t, apiCli.FunctionService().Delete(ctx, f.Name)) + }) + + t.Run("Source Module", func(t *testing.T) { + f := model.Function{ + Name: "test-default", + Package: pkg.Name, + Module: "test-source", + Sources: []model.TopicConfig{ + { + Name: "input", + }, + }, + Sink: model.TopicConfig{ + Name: "output", + }, } + recvCh, cancel := consume("output") + err = apiCli.FunctionService().Create(ctx, &f) + require.NoError(t, err) + defer cancel() + time.Sleep(1 * time.Second) - msg, err := consumer.Receive(context.Background()) - if err != nil { - t.Fatalf(err.Error()) + for i := 0; i < 10; i++ { + output := <-recvCh + r := &testfunctions.TestRecord{} + require.NoError(t, json.Unmarshal([]byte(output), r)) + require.Equal(t, i, r.ID) + require.Equal(t, "test", r.Name) } - payload := msg.Payload() - var out Person - err = json.Unmarshal(payload, &out) - if err != nil { - t.Fatalf(err.Error()) + require.NoError(t, apiCli.FunctionService().Delete(ctx, f.Name)) + }) + + t.Run("Sink Module", func(t *testing.T) { + f := model.Function{ + Name: "test-default", + Package: pkg.Name, + Module: "test-sink", + Sources: []model.TopicConfig{ + { + Name: "input", + }, + }, + Sink: model.TopicConfig{ + Name: "output", + }, } - if out.Money != 1 { - t.Fatalf("expected 1, got %d", out.Money) + err = apiCli.FunctionService().Create(ctx, &f) + require.NoError(t, err) + sinkCh := make(chan testfunctions.Counter) + go func() { + for i := 0; i < 10; i++ { + e := <-sinkCh + require.Equal(t, i, e.Count) + } + }() + time.Sleep(1 * time.Second) + for i := 0; i < 10; i++ { + e := &testfunctions.Counter{ + Count: i, + } + require.NoError(t, apiCli.EventsService().Produce(ctx, "input", e)) + sinkCh <- *e + require.Equal(t, i, e.Count) } - } - - res, err = cli.FunctionAPI.DeleteFunction(context.Background(), name).Execute() - if err != nil { - t.Fatalf(err.Error()) - } - if res.StatusCode != 200 { - t.Fatalf("expected 200, got %d", res.StatusCode) - } + require.NoError(t, apiCli.FunctionService().Delete(ctx, f.Name)) + }) }