diff --git a/Gopkg.lock b/Gopkg.lock index 771ec20b4c40dd754c8118f561ee0fd077f87fd9..34a39f32455de71f655bdb9589d7fe8744c37a1d 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -260,22 +260,6 @@ revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7" version = "v1.0.0" -[[projects]] - digest = "1:33082c63746b464db3d1c2c07a1396d860484d97fe857ef9e8668a9b406db09f" - name = "github.com/go-redis/redis" - packages = [ - ".", - "internal", - "internal/consistenthash", - "internal/hashtag", - "internal/pool", - "internal/proto", - "internal/util", - ] - pruneopts = "UT" - revision = "d22fde8721cc915a55aeb6b00944a76a92bfeb6e" - version = "v6.15.2" - [[projects]] digest = "1:4d02824a56d268f74a6b6fdd944b20b58a77c3d70e81008b3ee0c4f1a6777340" name = "github.com/gogo/protobuf" @@ -536,14 +520,6 @@ revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4" version = "v0.8.1" -[[projects]] - digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" - name = "github.com/pmezard/go-difflib" - packages = ["difflib"] - pruneopts = "UT" - revision = "792786c7400a136282c1664665ae0a8db921c6c2" - version = "v1.0.0" - [[projects]] branch = "master" digest = "1:d6efa822e9b1e35da4653e912128933143d37d2358eebe2b35c1a5f60e028bd0" @@ -584,14 +560,6 @@ pruneopts = "UT" revision = "75d898a42a940fbc854dfd1a4199eabdc00cf024" -[[projects]] - digest = "1:972c2427413d41a1e06ca4897e8528e5a1622894050e2f527b38ddf0f343f759" - name = "github.com/stretchr/testify" - packages = ["assert"] - pruneopts = "UT" - revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053" - version = "v1.3.0" - [[projects]] digest = "1:c468422f334a6b46a19448ad59aaffdfc0a36b08fdcc1c749a0b29b6453d7e59" name = "github.com/valyala/bytebufferpool" @@ -1084,7 +1052,6 @@ "github.com/aws/aws-sdk-go/service/sqs", "github.com/eclipse/paho.mqtt.golang", "github.com/ghodss/yaml", - "github.com/go-redis/redis", "github.com/golang/protobuf/proto", "github.com/golang/protobuf/ptypes/any", "github.com/golang/protobuf/ptypes/empty", @@ -1100,12 +1067,12 @@ "github.com/qiangxue/fasthttp-routing", "github.com/satori/go.uuid", "github.com/streadway/amqp", - "github.com/stretchr/testify/assert", "github.com/valyala/fasthttp", "go.opencensus.io/trace", "google.golang.org/api/option", "google.golang.org/grpc", "google.golang.org/grpc/metadata", + "gopkg.in/yaml.v2", "k8s.io/api/apps/v1", "k8s.io/api/core/v1", "k8s.io/apimachinery/pkg/api/errors", diff --git a/Gopkg.toml b/Gopkg.toml index 29bf46c31ad7fb26f8b13c631bd328c788850a2a..c75a1046e2f9938295b97147b74d1c42f31dd600 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -46,10 +46,6 @@ ignored = ["github.com/a8m/documentdb-go"] name = "github.com/ghodss/yaml" version = "1.0.0" -[[constraint]] - name = "github.com/go-redis/redis" - version = "6.15.1" - [[constraint]] name = "github.com/gorilla/mux" version = "1.7.0" diff --git a/Makefile b/Makefile index 9a23e01f589909ca995acbc11c967482129a5278..6d1ca0ec4027e64ad741ab506606a329353b4f0d 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ GIT_VERSION = $(shell git describe --always --abbrev=7 --dirty) TARGETS ?= darwin linux windows ARCH ?= amd64 CGO ?= 0 -BINARIES ?= action placement controller +BINARIES ?= actionsrt placement controller ifdef REL_VERSION ACTIONS_VERSION := $(REL_VERSION) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4be1ae97f7e2e8be9d39cc4e2a51e2c5293a7ab9..60c6382d1232083d100283abfe9fa10f3530d1bf 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -16,7 +16,7 @@ jobs: poolImage: macOS-latest targetOS: darwin targetArch: amd64 - binaryName: action + binaryName: actionsrt - template: 'build-binary-template.yml' parameters: poolImage: macOS-latest @@ -34,7 +34,7 @@ jobs: poolImage: macOS-latest targetOS: linux targetArch: arm - binaryName: action + binaryName: actionsrt - template: 'build-binary-template.yml' parameters: poolImage: macOS-latest @@ -52,7 +52,7 @@ jobs: poolImage: ubuntu-latest targetOS: linux targetArch: amd64 - binaryName: action + binaryName: actionsrt - template: 'build-binary-template.yml' parameters: poolImage: ubuntu-latest @@ -70,7 +70,7 @@ jobs: poolImage: windows-2019 targetOS: windows targetArch: amd64 - binaryName: action + binaryName: actionsrt - template: 'build-binary-template.yml' parameters: poolImage: windows-2019 @@ -87,31 +87,31 @@ jobs: pool: vmImage: 'windows-2019' dependsOn: - - build_darwin_amd64_action + - build_darwin_amd64_actionsrt - build_darwin_amd64_placement - build_darwin_amd64_controller - - build_linux_amd64_action + - build_linux_amd64_actionsrt - build_linux_amd64_placement - build_linux_amd64_controller - - build_linux_arm_action + - build_linux_arm_actionsrt - build_linux_arm_placement - build_linux_arm_controller - - build_windows_amd64_action + - build_windows_amd64_actionsrt - build_windows_amd64_placement - build_windows_amd64_controller condition: | and ( - eq(dependencies.build_darwin_amd64_action.result, 'Succeeded'), + eq(dependencies.build_darwin_amd64_actionsrt.result, 'Succeeded'), eq(dependencies.build_darwin_amd64_placement.result, 'Succeeded'), eq(dependencies.build_darwin_amd64_controller.result, 'Succeeded'), - eq(dependencies.build_linux_amd64_action.result, 'Succeeded'), + eq(dependencies.build_linux_amd64_actionsrt.result, 'Succeeded'), eq(dependencies.build_linux_amd64_placement.result, 'Succeeded'), eq(dependencies.build_linux_amd64_controller.result, 'Succeeded'), - eq(dependencies.build_linux_arm_action.result, 'Succeeded'), + eq(dependencies.build_linux_arm_actionsrt.result, 'Succeeded'), eq(dependencies.build_linux_arm_placement.result, 'Succeeded'), eq(dependencies.build_linux_arm_controller.result, 'Succeeded'), - eq(dependencies.build_windows_amd64_action.result, 'Succeeded'), + eq(dependencies.build_windows_amd64_actionsrt.result, 'Succeeded'), eq(dependencies.build_windows_amd64_placement.result, 'Succeeded'), eq(dependencies.build_windows_amd64_controller.result, 'Succeeded') ) diff --git a/build-binary-template.yml b/build-binary-template.yml index 4619919d8ba53c1c4feec52416408c71f86d83ea..92ed829c9a34ba310d541814cc3aa6ee8f3c034f 100644 --- a/build-binary-template.yml +++ b/build-binary-template.yml @@ -2,7 +2,7 @@ parameters: poolImage: 'macOS-latest' targetOS: 'darwin' targetArch: 'amd64' - binaryName: 'action' + binaryName: 'actionsrt' jobs: - job: build_${{ parameters.targetOS }}_${{ parameters.targetArch }}_${{ parameters.binaryName }} diff --git a/cmd/action/main.go b/cmd/action/main.go deleted file mode 100644 index 19d64eccea3051c7bbc740876ead502fc7fc9855..0000000000000000000000000000000000000000 --- a/cmd/action/main.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "flag" - "os" - "os/signal" - "strconv" - - log "github.com/Sirupsen/logrus" - "github.com/actionscore/actions/pkg/action" - "github.com/actionscore/actions/pkg/version" -) - -func main() { - log.Infof("Starting Actions Runtime -- version %s -- commit %s", version.Version(), version.Commit()) - - mode := flag.String("mode", "standalone", "") - actionHTTPPort := flag.String("action-http-port", "3500", "") - actionGRPCPort := flag.String("action-grpc-port", "50001", "") - appPort := flag.String("app-port", "", "") - appProtocol := flag.String("protocol", "http", "") - eventSourcesPath := flag.String("event-sources-path", "./eventsources", "") - configurationName := flag.String("configuration-name", "", "") - actionID := flag.String("action-id", "", "") - apiAddress := flag.String("api-address", "", "") - placementServiceAddresss := flag.String("placement-address", "", "") - allowedOrigins := flag.String("allowed-origins", "*", "") - - flag.Parse() - - stop := make(chan os.Signal, 1) - signal.Notify(stop, os.Interrupt) - - actionHTTP, _ := strconv.Atoi(*actionHTTPPort) - actionGRPC, _ := strconv.Atoi(*actionGRPCPort) - - i := action.NewAction(*actionID, *appPort, *mode, *appProtocol, *eventSourcesPath, *configurationName, *apiAddress, *placementServiceAddresss, *allowedOrigins) - i.Run(actionHTTP, actionGRPC) - - <-stop -} diff --git a/cmd/actionsrt/main.go b/cmd/actionsrt/main.go new file mode 100644 index 0000000000000000000000000000000000000000..22a1089757ec64714250b46e3bbcc435db852e3f --- /dev/null +++ b/cmd/actionsrt/main.go @@ -0,0 +1,88 @@ +package main + +import ( + "flag" + "fmt" + "os" + "os/signal" + "strconv" + "syscall" + "time" + + log "github.com/Sirupsen/logrus" + global_config "github.com/actionscore/actions/pkg/config" + "github.com/actionscore/actions/pkg/modes" + actionsrt "github.com/actionscore/actions/pkg/runtime" + "github.com/actionscore/actions/pkg/version" +) + +func main() { + log.Infof("starting Actions Runtime -- version %s -- commit %s", version.Version(), version.Commit()) + + mode := flag.String("mode", string(modes.StandaloneMode), "Runtime mode for Actions") + actionHTTPPort := flag.String("actions-http-port", fmt.Sprintf("%v", actionsrt.DefaultActionsHTTPPort), "HTTP port for Actions to listen on") + actionGRPCPort := flag.String("actions-grpc-port", fmt.Sprintf("%v", actionsrt.DefaultActionsGRPCPort), "gRPC port for Actions to listen on") + appPort := flag.String("app-port", "", "The port the application is listening on") + appProtocol := flag.String("protocol", string(actionsrt.HTTPProtocol), "Protocol for the application: gRPC or http") + componentsPath := flag.String("components-path", actionsrt.DefaultComponentsPath, "Path for components directory. Standalone mode only") + config := flag.String("config", "", "Path to config file, or name of a configuration object") + actionsID := flag.String("actions-id", "", "A unique ID for Actions. Used for Service Discovery and state") + controlPlaneAddress := flag.String("control-plane-address", "", "Address for an Actions control plane") + placementServiceAddresss := flag.String("placement-address", "", "Address for the Actions placement service") + allowedOrigins := flag.String("allowed-origins", actionsrt.DefaultAllowedOrigins, "Allowed HTTP origins") + + flag.Parse() + + actionHTTP, err := strconv.Atoi(*actionHTTPPort) + if err != nil { + log.Fatalf("error parsing actions-http-port flag: %s", err) + } + + actionGRPC, err := strconv.Atoi(*actionGRPCPort) + if err != nil { + log.Fatalf("error parsing actions-grpc-port flag: %s", err) + } + + applicationPort := 0 + if *appPort != "" { + applicationPort, err = strconv.Atoi(*appPort) + if err != nil { + log.Fatalf("error parsing app-port: %s", err) + } + } + + runtimeConfig := actionsrt.NewRuntimeConfig(*actionsID, *placementServiceAddresss, *controlPlaneAddress, *allowedOrigins, *config, *componentsPath, + *appProtocol, *mode, actionHTTP, actionGRPC, applicationPort) + + var globalConfig *global_config.Configuration + + if *config != "" { + switch modes.ActionsMode(*mode) { + case modes.KubernetesMode: + globalConfig, err = global_config.LoadKubernetesConfiguration(*config, *controlPlaneAddress) + case modes.StandaloneMode: + globalConfig, err = global_config.LoadStandaloneConfiguration(*config) + } + } else { + globalConfig = global_config.LoadDefaultConfiguration() + } + + if err != nil { + log.Warnf("error loading config: %s. loading default config", err) + } + + stop := make(chan os.Signal, 1) + signal.Notify(stop, syscall.SIGTERM, os.Interrupt) + + rt := actionsrt.NewActionsRuntime(runtimeConfig, globalConfig) + err = rt.Run() + if err != nil { + log.Fatalf("error initializing Actions Runtime: %s", err) + } + + <-stop + gracefulShutdownDuration := 5 * time.Second + log.Info("actions shutting down. Waiting 5 seconds to finish outstanding operations") + rt.Stop() + <-time.After(gracefulShutdownDuration) +} diff --git a/cmd/placement/main.go b/cmd/placement/main.go index 222f45dd46dace146a66fa3c90b7c092e05b079c..4124cfbdf957aa6ae752f077d0f21b54710024ca 100644 --- a/cmd/placement/main.go +++ b/cmd/placement/main.go @@ -11,7 +11,7 @@ import ( ) func main() { - log.Infof("Starting Actions Placement Service -- version %s -- commit %s", version.Version(), version.Commit()) + log.Infof("starting Actions Placement Service -- version %s -- commit %s", version.Version(), version.Commit()) port := flag.String("port", "50005", "") flag.Parse() @@ -22,6 +22,6 @@ func main() { p := placement.NewPlacementService() go p.Run(*port) - log.Infof("Placement Service started on port %s", *port) + log.Infof("placement Service started on port %s", *port) <-stop } diff --git a/deploy/actions_v.0.2.0-alpha.yaml b/deploy/actions_v.0.2.0-alpha.yaml new file mode 100644 index 0000000000000000000000000000000000000000..58cb3e7959ef8a968eb019e00dccb1c245ad4efb --- /dev/null +++ b/deploy/actions_v.0.2.0-alpha.yaml @@ -0,0 +1,118 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: components.actions.io +spec: + group: actions.io + version: v1alpha1 + names: + kind: Component + plural: components + singular: components + categories: + - all + - actions + scope: Namespaced +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: actions-controller + namespace: default + +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: actions-controller +subjects: +- kind: ServiceAccount + name: actions-controller + namespace: default +roleRef: + kind: ClusterRole + name: cluster-admin + apiGroup: rbac.authorization.k8s.io + +--- +kind: Service +apiVersion: v1 +metadata: + name: actions-api +spec: + selector: + app: actions-controller + ports: + - protocol: TCP + port: 80 + targetPort: 6500 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: actions-controller + labels: + app: actions-controller +spec: + replicas: 1 + selector: + matchLabels: + app: actions-controller + template: + metadata: + labels: + app: actions-controller + spec: + containers: + - name: actions-controller + image: actionscore.azurecr.io/actions:merge + env: + - name: RUNTIME_IMAGE + value: actionscore.azurecr.io/actions:merge + imagePullPolicy: Always + ports: + - containerPort: 6500 + command: ["./controller"] + imagePullSecrets: + - name: actions-auth + serviceAccountName: actions-controller + +--- +kind: Service +apiVersion: v1 +metadata: + name: actions-placement +spec: + selector: + app: actions-placement + ports: + - protocol: TCP + port: 80 + targetPort: 50005 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: actions-placement + labels: + app: actions-placement +spec: + replicas: 1 + selector: + matchLabels: + app: actions-placement + template: + metadata: + labels: + app: actions-placement + spec: + containers: + - name: actions-placement + image: actionscore.azurecr.io/actions:merge + imagePullPolicy: Always + ports: + - containerPort: 50005 + command: ["./placement"] + imagePullSecrets: + - name: actions-auth + serviceAccountName: actions-controller diff --git a/pkg/action/action.go b/pkg/action/action.go deleted file mode 100644 index 4cd307a7eca78643f34c46b1cb57e0a718bc1649..0000000000000000000000000000000000000000 --- a/pkg/action/action.go +++ /dev/null @@ -1,1748 +0,0 @@ -package action - -import ( - "bytes" - "context" - "crypto/tls" - "encoding/json" - "fmt" - "io/ioutil" - "net" - "net/http" - "os" - "strings" - "sync" - "time" - - "github.com/golang/protobuf/ptypes/empty" - jsoniter "github.com/json-iterator/go" - "github.com/valyala/fasthttp" - - "github.com/golang/protobuf/ptypes/any" - - cors "github.com/AdhityaRamadhanus/fasthttpcors" - log "github.com/Sirupsen/logrus" - configuration_v1alpha1 "github.com/actionscore/actions/pkg/apis/configuration/v1alpha1" - eventing_v1alpha1 "github.com/actionscore/actions/pkg/apis/eventing/v1alpha1" - diag "github.com/actionscore/actions/pkg/diagnostics" - exporters "github.com/actionscore/actions/pkg/exporters" - "github.com/actionscore/actions/pkg/placement" - pb "github.com/actionscore/actions/pkg/proto" - "github.com/ghodss/yaml" - "github.com/gorilla/mux" - routing "github.com/qiangxue/fasthttp-routing" - "go.opencensus.io/trace" - "google.golang.org/grpc" - "google.golang.org/grpc/metadata" -) - -type actionMode string -type concurrencyMode string -type interactionMode string -type invokedHTTPMethod string -type Protocol string - -const ( - KubernetesMode actionMode = "kubernetes" - StandaloneMode actionMode = "standalone" - FireForgetCall interactionMode = "fireforget" - WaitCall interactionMode = "wait" - parallelMode concurrencyMode = "parallel" - HTTPProtocol Protocol = "http" - GRPCProtocol Protocol = "grpc" - stateStoreType = "statestore" - sender = "sender" - contextIDHeader = "actions.context-id" - contextIDPath = "contextid" - interactionModeHeader = "actions.interaction" - fromHeader = "actions.from" - actionIDHeader = "id" - invokedMethodHeader = "method" - invokedHTTPVerbHeader = "http.method" - actionTargetIDHeader = "actions.target-id" - addressHeader = "actions.action-address" - zipkinExporter = "zipkin" -) - -var ( - gRPCPort int -) - -type Action struct { - Router *mux.Router - ActionID string - ApplicationPort string - Mode string - Protocol Protocol - EventSources []EventSource - ActionSources map[string]ActionSource - StateStore ActionSource - EventSourcesPath string - ConfigurationName string - APIAddress string - PlacementAddress string - AppConfig ApplicationConfig - Sender Sender - StateWriteLock *sync.Mutex - HTTPClient *fasthttp.Client - GRPCClient *grpc.ClientConn - GRPCLock *sync.Mutex - GRPCConnectionPool map[string]*grpc.ClientConn - IPAddress string - PlacementTableLock *sync.RWMutex - PlacementTables *placement.PlacementTables - PlacementSignal chan struct{} - PlacementBlock bool - ActiveContextsLock *sync.RWMutex - ActiveContexts map[string]string - json jsoniter.API - Configuration Configuration - AllowedOrigins []string -} - -func NewAction(actionID string, applicationPort string, mode string, protocol string, eventSourcesPath string, configurationName string, apiAddress string, placementAddress string, allowedOrigins string) *Action { - http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - - return &Action{ - ActionID: actionID, - ApplicationPort: applicationPort, - Protocol: Protocol(protocol), - Router: mux.NewRouter(), - Mode: mode, - EventSourcesPath: eventSourcesPath, - ConfigurationName: configurationName, - APIAddress: apiAddress, - PlacementAddress: placementAddress, - StateWriteLock: &sync.Mutex{}, - HTTPClient: &fasthttp.Client{MaxConnsPerHost: 1000000, TLSConfig: &tls.Config{InsecureSkipVerify: true}}, - GRPCLock: &sync.Mutex{}, - GRPCConnectionPool: map[string]*grpc.ClientConn{}, - PlacementTableLock: &sync.RWMutex{}, - PlacementTables: &placement.PlacementTables{Entries: make(map[string]*placement.Consistent)}, - ActiveContextsLock: &sync.RWMutex{}, - ActiveContexts: map[string]string{}, - json: jsoniter.ConfigFastest, - AllowedOrigins: strings.Split(allowedOrigins, ","), - } -} - -func (i *Action) GetConfiguration(name string) (Configuration, error) { - ret := Configuration{ - Spec: ConfigurationSpec{ - TracingSpec: TracingSpec{ - Enabled: false, - }, - }, - } - if name == "" { - return ret, nil - } - if i.Mode == string(KubernetesMode) { - url := fmt.Sprintf("%s/configurations/%s", i.APIAddress, name) - res, err := http.DefaultClient.Get(url) - if err != nil { - log.Errorf("Error getting configuration: %s", err) - return ret, err - } - - err = i.json.NewDecoder(res.Body).Decode(&ret) - if err != nil { - log.Errorf("Error decoding configuration: %s", err) - return ret, err - } - return ret, nil - } else { - b, err := ioutil.ReadFile(i.ConfigurationName) - if err != nil { - return ret, nil - } - var cf configuration_v1alpha1.Configuration - err = yaml.Unmarshal(b, &cf) - if err != nil { - return ret, nil - } else { - return Configuration{ - Spec: ConfigurationSpec{ - TracingSpec: TracingSpec{ - Enabled: cf.Spec.TracingSpec.Enabled, - ExporterType: cf.Spec.TracingSpec.ExporterType, - ExporterAddress: cf.Spec.TracingSpec.ExporterAddress, - IncludeEvent: cf.Spec.TracingSpec.IncludeEvent, - IncludeEventBody: cf.Spec.TracingSpec.IncludeEventBody, - }, - }, - }, nil - } - } -} -func (i *Action) GetEventSources() []EventSource { - var eventSources []EventSource - - if i.Mode == string(KubernetesMode) { - url := fmt.Sprintf("%s/eventsources", i.APIAddress) - res, err := http.DefaultClient.Get(url) - if err != nil { - log.Errorf("Error getting event sources: %s", err) - return eventSources - } - - err = i.json.NewDecoder(res.Body).Decode(&eventSources) - if err != nil { - log.Errorf("Error decoding event sources: %s", err) - return eventSources - } - } else { - dir := i.EventSourcesPath - files, err := ioutil.ReadDir(dir) - if err != nil { - log.Error(err) - } else { - for _, f := range files { - b, err := ioutil.ReadFile(fmt.Sprintf("%s/%s", dir, f.Name())) - if err != nil { - log.Error(err) - } else { - var es eventing_v1alpha1.EventSource - err := yaml.Unmarshal(b, &es) - if err != nil { - log.Error(err) - } else { - eventSources = append(eventSources, EventSource{ - Name: es.ObjectMeta.Name, - Spec: EventSourceSpec{ - Type: es.Spec.Type, - ConnectionInfo: es.Spec.ConnectionInfo, - SenderOptions: es.Spec.SenderOptions, - }, - }) - } - } - } - } - } - - return eventSources -} -func (i *Action) ConfigureTracing() { - cfg, err := i.GetConfiguration(i.ConfigurationName) - if err != nil { - log.Errorf("Error configuring Actions: %s", err) - return - } - i.Configuration = cfg - switch cfg.Spec.TracingSpec.ExporterType { - case zipkinExporter: - ex := exporters.ZipkinExporter{} - ex.Init(i.ActionID, fmt.Sprintf("%s:%d", i.IPAddress, gRPCPort), cfg.Spec.TracingSpec.ExporterAddress) - default: - return - } -} -func (i *Action) SetupEventSources() { - i.EventSources = i.GetEventSources() - - for _, es := range i.EventSources { - log.Infof("Discovered EventSource %s (%s)", es.Name, es.Spec.Type) - - if es.Name == stateStoreType { - continue - } else if i.Protocol == GRPCProtocol { - continue - } - - endpoint := i.GetLocalAppEndpoint(es.Name) - req, _ := http.NewRequest(http.MethodOptions, endpoint, nil) - res, err := http.DefaultClient.Do(req) - if err == nil && res.StatusCode != 404 { - reader := i.GetActionSource(es.Spec.Type) - err := reader.Init(es.Spec) - if err != nil { - log.Errorf("Error from reader Init: %s", err) - return - } - if es.Spec.SenderOptions.QueueName != "" { - //TODO: allow different sender types - es.Sender = NewRedisSender() - - if err = es.Sender.Init(es.Spec); err != nil { - log.Errorf("Error from reader Init - %s", err) - return - } - es.Sender.StartLoop(i.PostEventToAppWithRetries, context.Background()) - } - if reader != nil { - i.ReadEvents(es.Name, reader, es.Sender) - } - } - } -} - -func (i *Action) StartGRPCServer(grpcPort int) { - gRPCPort = grpcPort - - log.Info(fmt.Sprintf("Starting gRPC server on port %v", grpcPort)) - go func() { - lis, err := net.Listen("tcp", fmt.Sprintf(":%v", grpcPort)) - if err != nil { - log.Fatalf("gRPC error: %s", err) - } - s := grpc.NewServer() - pb.RegisterActionsServer(s, i) - if err := s.Serve(lis); err != nil { - log.Fatalf("gRPC error: %v", err) - } - }() -} - -func (i *Action) ReadEvents(eventName string, reader ActionSource, sender Sender) { - go func() { - ctx, span := trace.StartSpan(context.Background(), "read-event") - if span != nil { - defer span.End() - } - //TBD: carry over context from message - - err := reader.ReadAsync(nil, func(data []byte) error { - var body interface{} - err := i.json.Unmarshal(data, &body) - if err != nil { - log.Errorf("Error Parsing event: %s", err) - return err - } - - event := Event{ - Data: body, - EventName: eventName, - CreatedAt: time.Now(), - } - if sender != nil { - return sender.Enqueue(event) - } - - return i.PostEventToAppWithRetries(&[]Event{event}, ctx) - }) - if err != nil { - log.Errorf("Error from event reader. event name: %s: %s", eventName, err) - } - }() -} - -func (i *Action) PostEventToAppWithRetries(events *[]Event, c context.Context) error { - - _, span, spanCtx := i.TraceSpanFromContext(c, *events, "PostEventToAppWithRetires") - if span != nil { - defer span.End() - } - - sent := false - timeStarted := time.Now() - timeoutSeconds := time.Second * 30 - - for !sent { - if time.Since(timeStarted).Seconds() >= timeoutSeconds.Seconds() { - - diag.SetSpanStatus(span, trace.StatusCodeDeadlineExceeded, fmt.Sprintf("failed to dispath message to application after %d seconds", 30)) - - return fmt.Errorf("Timed out while sending message to app") - } - hasError := false - for _, event := range *events { - _, err := i.PostEventToApp(Event{ - Data: event.Data, - CreatedAt: time.Now(), - EventName: event.EventName, - }, spanCtx) - if err != nil { - hasError = true - - diag.SetSpanStatus(span, trace.StatusCodeDataLoss, err.Error()) - - break - } - } - if !hasError { - sent = true - - diag.SetSpanStatus(span, trace.StatusCodeOK, "message dispatched to application") - - break - } - time.Sleep(time.Duration(500) * time.Millisecond) - } - return nil -} - -func (i *Action) GetEventSourceSpec(name string) (*EventSourceSpec, error) { - for _, es := range i.EventSources { - if es.Name == name { - return &es.Spec, nil - } - } - - return nil, nil -} - -func (i *Action) GetLocalAppEndpoint(path string) string { - base := "127.0.0.1" - if i.Protocol == GRPCProtocol { - return fmt.Sprintf("%s:%s", base, i.ApplicationPort) - } - - return fmt.Sprintf("http://%s:%s/%s", base, i.ApplicationPort, path) -} - -func (i *Action) PostEventToApp(event Event, ctx *trace.SpanContext) (*Event, error) { - url := i.GetLocalAppEndpoint(event.EventName) - res, err := i.SendEventViaHTTP(url, event, ctx) - if err != nil { - log.Errorf("Error sending data to app: %s", err) - return nil, err - } else if res != nil { - i.ActOnEventFromApp(*res, ctx) - return res, nil - } - return res, nil -} - -func (i *Action) SendStateViaHTTP(url string, state []KeyValState, ctx *trace.SpanContext) error { - b := new(bytes.Buffer) - i.json.NewEncoder(b).Encode(state) - - _, err := i.ExecuteHTTPCall(url, http.MethodPost, nil, b.Bytes(), ctx) - if err != nil { - return err - } - - return nil -} - -func (i *Action) ExecuteHTTPCall(url, httpMethod string, headers map[string]string, payload []byte, ctx *trace.SpanContext) ([]byte, error) { - req := fasthttp.AcquireRequest() - req.SetRequestURI(url) - req.SetBody(payload) - req.Header.SetContentType("application/json") - req.Header.SetMethod(strings.ToUpper(httpMethod)) - - if ctx != nil { - req.Header.Add(diag.CorrelationID, diag.SerializeSpanContext(*ctx)) - } - if headers != nil { - for k, v := range headers { - req.Header.Set(k, v) - } - } - - resp := fasthttp.AcquireResponse() - err := i.HTTPClient.Do(req, resp) - if err != nil { - return nil, err - } - - body := resp.Body() - arr := make([]byte, len(body)) - copy(arr, body) - fasthttp.ReleaseRequest(req) - fasthttp.ReleaseResponse(resp) - return arr, nil -} - -func (i *Action) SendEventViaHTTP(url string, event Event, ctx *trace.SpanContext) (*Event, error) { - b := new(bytes.Buffer) - i.json.NewEncoder(b).Encode(event) - - res, err := i.ExecuteHTTPCall(url, "POST", nil, b.Bytes(), ctx) - if err != nil { - return nil, err - } - if res == nil || len(res) == 0 { - return nil, nil - } - - var response Event - i.json.Unmarshal(res, &response) - if err != nil { - return nil, err - } - - return &response, nil -} - -func (i *Action) GetActionSource(sourceType string) ActionSource { - if val, ok := i.ActionSources[sourceType]; ok { - return val - } - - return nil -} - -func (i *Action) SaveActorStateToStore(state KeyValState) error { - if i.StateStore == nil { - return nil - } - - err := i.StateStore.Write(state) - if err != nil { - return err - } - - return nil -} - -func (i *Action) SaveStateToStore(state []KeyValState) error { - if i.StateStore != nil && len(state) > 0 { - for _, s := range state { - actionKey := fmt.Sprintf("%s-%s", i.ActionID, s.Key) - s.Key = actionKey - err := i.StateStore.Write(s) - if err != nil { - return err - } - } - } - - return nil -} - -func (i *Action) GetStateFromStore(key string) (interface{}, error) { - if i.StateStore != nil && key != "" { - data, err := i.StateStore.Read(key) - if err != nil { - return nil, err - } - - return data, nil - } - - return nil, nil -} - -func (i *Action) SendEventToTarget(target string, event Event, ctx *trace.SpanContext) error { - eventSourceSpec, err := i.GetEventSourceSpec(target) - if err == nil && eventSourceSpec != nil && eventSourceSpec.Type != "" { - writer := i.GetActionSource(eventSourceSpec.Type) - err := writer.Init(*eventSourceSpec) - if err != nil { - return fmt.Errorf("Error from writer Init: %s", err) - } - - if writer != nil { - err := writer.Write(event.Data) - if err != nil { - return fmt.Errorf("Error from event writer %s: %s", eventSourceSpec.Type, err) - } - } else { - log.Infof("Couldn't find event writer of type %s", target) - } - } else { - url := "" - if i.Mode == string(KubernetesMode) { - url = fmt.Sprintf("http://%s-action.default.svc.cluster.local/publish", target) - } else if i.Mode == string(StandaloneMode) { - url = fmt.Sprintf("http://%s/publish", target) - } - - event := Event{ - EventName: event.EventName, - Data: event.Data, - CreatedAt: time.Now(), - } - - _, err := i.SendEventViaHTTP(url, event, ctx) - if err != nil { - return fmt.Errorf("Error sending event %s to %s: %s", event.EventName, target, err) - } - } - - return nil -} - -func (i *Action) ActOnEventFromApp(response Event, ctx *trace.SpanContext) { - if len(response.State) > 0 { - go func() { - err := i.SaveStateToStore(response.State) - if err != nil { - log.Errorf("Error saving state: %s", err) - } - }() - } - - if response.Concurrency == string(parallelMode) { - for _, target := range response.To { - i.SendEventToTargetAsync(target, response, ctx) - } - } else { - for _, target := range response.To { - err := i.SendEventToTarget(target, response, ctx) - if err != nil { - log.Error(err) - } - } - } -} - -func (i *Action) SendEventToTargetAsync(target string, event Event, ctx *trace.SpanContext) { - go func() { - err := i.SendEventToTarget(target, event, ctx) - if err != nil { - log.Error(err) - } - }() -} - -// OnEventPublished is called when the user code publishes a message through Actions /publish route -func (i *Action) OnEventPublished(c *routing.Context) { - - ctx, span, spanCtx := i.TraceSpanFromRoutingContext(c, nil, "OnEventPublished") - if span != nil { - defer span.End() - } - - body := c.RequestCtx.PostBody() - if body != nil { - var event Event - err := i.json.Unmarshal(body, &event) - if err != nil { - log.Errorf("Error decoding body on event: %s", err) - - diag.SetSpanStatus(span, trace.StatusCodeDataLoss, err.Error()) - - c.RequestCtx.SetStatusCode(500) - c.RequestCtx.Write(nil) - return - } - - if len(event.To) > 0 { - i.ActOnEventFromApp(event, spanCtx) - } else { - if i.Sender != nil { - err = i.Sender.Enqueue(event) - } else { - err = i.PostEventToAppWithRetries(&[]Event{event}, ctx) - } - if err != nil { - - diag.SetSpanStatus(span, trace.StatusCodeDataLoss, err.Error()) - - c.RequestCtx.SetStatusCode(500) - c.RequestCtx.Write(nil) - return - } - } - } - - diag.SetSpanStatus(span, trace.StatusCodeOK, "message is dispatched to application") - - c.RequestCtx.SetStatusCode(200) - c.RequestCtx.Write(nil) -} - -func (i *Action) GetGRPCConnection(address string) (*grpc.ClientConn, error) { - if val, ok := i.GRPCConnectionPool[address]; ok { - return val, nil - } - - i.GRPCLock.Lock() - if val, ok := i.GRPCConnectionPool[address]; ok { - i.GRPCLock.Unlock() - return val, nil - } - - conn, err := grpc.Dial(address, grpc.WithInsecure()) - if err != nil { - i.GRPCLock.Unlock() - return nil, err - } - - i.GRPCConnectionPool[address] = conn - i.GRPCLock.Unlock() - - return conn, nil -} - -func (i *Action) OnLocalBatchActivation(c *routing.Context) { - actionID := c.Param(actionIDHeader) - payload := c.RequestCtx.PostBody() - var contextIDs []string - err := json.Unmarshal(payload, &contextIDs) - if err != nil { - RespondWithError(c.RequestCtx, 500, "Error deserializing context IDs") - return - } - - needActivation := []string{} - i.ActiveContextsLock.RLock() - for _, c := range contextIDs { - address := i.LookupContextAddress(actionID, c) - if address == fmt.Sprintf("%s:%v", i.IPAddress, gRPCPort) { - if _, ok := i.ActiveContexts[c]; !ok { - needActivation = append(needActivation, c) - } else { - //TODO: remote activation - } - } - } - i.ActiveContextsLock.RUnlock() - - if len(needActivation) == 0 { - RespondEmpty(c.RequestCtx, 200) - return - } - - contexts := []ContextActivation{} - for _, c := range needActivation { - contextKey := fmt.Sprintf("%s-%s", actionID, c) - state, _ := i.GetStateFromStore(contextKey) - payload := "{}" - if state != nil && state.(string) != "" { - payload = state.(string) - } - - contexts = append(contexts, ContextActivation{ - ID: c, - State: []byte(payload), - }) - } - - b, _ := i.json.Marshal(&contexts) - url := i.GetLocalAppEndpoint(fmt.Sprintf("actors/%s/batch", actionID)) - r, err := i.ExecuteHTTPCall(url, http.MethodPost, nil, b, nil) - if err != nil { - RespondWithError(c.RequestCtx, 500, "error activating batched contexts") - return - } - - activated := []string{} - err = json.Unmarshal(r, &activated) - if err != nil { - RespondWithError(c.RequestCtx, 500, "error activating batched contexts") - return - } - - i.ActiveContextsLock.Lock() - defer i.ActiveContextsLock.Unlock() - for _, a := range activated { - i.ActiveContexts[a] = actionID - } - - RespondEmpty(c.RequestCtx, 200) -} - -func (i *Action) OnInvokeAction(c *routing.Context) { - _, span, spanCtx := i.TraceSpanFromRoutingContext(c, nil, "OnInvokeAction") - if span != nil { - defer span.End() - } - - actionID := c.Param(actionIDHeader) - actionMethod := c.Param(invokedMethodHeader) - interaction := string(c.Request.Header.Peek(interactionModeHeader)) - contextID := c.Param(contextIDPath) - targetAddress := string(c.Request.Header.Peek(addressHeader)) - httpMethod := string(c.Method()) - - corID := "" - if span != nil { - corID = diag.SerializeSpanContext(*spanCtx) - } - - headers := map[string]string{ - fromHeader: i.ActionID, - invokedHTTPVerbHeader: httpMethod, - invokedMethodHeader: actionMethod, - actionTargetIDHeader: actionID, - diag.CorrelationID: corID, - } - - if contextID != "" { - headers[contextIDHeader] = contextID - // Only block if request is targeting a context and an placement table update is ongoing - if i.PlacementBlock { - <-i.PlacementSignal - } - } - - responseDelivered := false - if interaction == string(FireForgetCall) { - headers[interactionModeHeader] = interaction - - diag.SetSpanStatus(span, trace.StatusCodeOK, "fire and forget call dispatched") - - RespondEmpty(c.RequestCtx, 200) - responseDelivered = true - } - - payload := c.RequestCtx.PostBody() - address := "" - if targetAddress != "" { - address = targetAddress - } else { - if contextID != "" { - address = i.LookupContextAddress(actionID, contextID) - - if address == "" { - if !responseDelivered { - diag.SetSpanStatus(span, trace.StatusCodeUnavailable, fmt.Sprintf("could not locate host for: %s/%s", actionID, contextID)) - RespondWithError(c.RequestCtx, 500, fmt.Sprintf("could not locate host for: %s/%s", actionID, contextID)) - } - return - } - } else { - address = fmt.Sprintf("%s-action.default.svc.cluster.local:%v", actionID, gRPCPort) - } - } - - var resp []byte - var err error - - if i.ActionID == actionID || i.isTargetLocal(targetAddress) { - resp, err = i.invoke(actionMethod, actionID, contextID, "", httpMethod, string(c.URI().QueryString()), payload, spanCtx) - } else { - conn, err := i.GetGRPCConnection(address) - if err != nil { - log.Fatalf("gRPC connection failure: %s", err) - if !responseDelivered { - diag.SetSpanStatus(span, trace.StatusCodeInternal, fmt.Sprintf("Delivery to action id %s failed - %s", actionID, err.Error())) - RespondWithError(c.RequestCtx, 500, fmt.Sprintf("delivery to action id %s failed", actionID)) - } - return - } - - md := metadata.New(headers) - ctx, cancel := context.WithTimeout(context.Background(), time.Minute*1) - defer cancel() - - ctxMetadata := metadata.NewOutgoingContext(ctx, md) - client := pb.NewActionsClient(conn) - r, err := client.Invoke(ctxMetadata, &pb.InvokeEnvelope{Data: &any.Any{Value: payload}}) - if err != nil { - if !responseDelivered { - diag.SetSpanStatus(span, trace.StatusCodeInternal, fmt.Sprintf("Delivery to action id %s failed - %s", actionID, err.Error())) - RespondWithError(c.RequestCtx, 500, fmt.Sprintf("delivery to action id %s failed", actionID)) - } - return - } - - if r != nil && r.Data != nil { - resp = r.GetData().GetValue() - } - } - - if err != nil { - log.Error(err) - diag.SetSpanStatus(span, trace.StatusCodeInternal, fmt.Sprintf("Delivery to action id %s failed - %s", actionID, err.Error())) - RespondWithError(c.RequestCtx, 500, fmt.Sprintf("delivery to action id %s failed", actionID)) - return - } - - if resp != nil && len(resp) > 0 { - diag.SetSpanStatus(span, trace.StatusCodeOK, fmt.Sprintf("action was called and returned %s", string(resp))) - RespondWithJSON(c.RequestCtx, 200, resp) - } else { - diag.SetSpanStatus(span, trace.StatusCodeOK, fmt.Sprintf("action was called but returned no value")) - RespondEmpty(c.RequestCtx, 200) - } -} - -func (i *Action) SaveState(ctx context.Context, in *pb.SaveStateEnvelope) (*empty.Empty, error) { - states := []KeyValState{} - for _, s := range in.State { - var v interface{} - err := i.json.Unmarshal(s.Value.Value, &v) - if err != nil { - return nil, err - } - - k := KeyValState{ - Key: s.Key, - Value: v, - } - - states = append(states, k) - } - - err := i.SaveStateToStore(states) - if err != nil { - return nil, err - } - - return &empty.Empty{}, nil -} - -func (i *Action) OnStatePosted(c *routing.Context) { - var state []KeyValState - err := i.json.Unmarshal(c.RequestCtx.PostBody(), &state) - if err != nil { - log.Errorf("Error deserializing data from OnStatePosted: %s", err) - RespondWithError(c.RequestCtx, 500, "Error saving state") - return - } - - err = i.SaveStateToStore(state) - if err != nil { - log.Errorf("Error sending data from OnStatePosted: %s", err) - RespondWithError(c.RequestCtx, 500, "Error saving state") - return - } - - RespondEmpty(c.RequestCtx, 200) -} - -func (i *Action) OnActorStatePosted(c *routing.Context) { - targetID := c.Param(actionIDHeader) - contextID := c.Param(contextIDPath) - actorState := string(c.PostBody()) - - key := fmt.Sprintf("%s-%s", targetID, contextID) - err := i.SaveActorStateToStore(KeyValState{ - Key: key, - Value: actorState, - }) - if err != nil { - log.Errorf("Error from OnActorStatePosted: %s", err) - RespondWithError(c.RequestCtx, 500, "Error saving state") - return - } -} - -func (i *Action) GetStateKey(key string) string { - return fmt.Sprintf("%s-%s", i.ActionID, key) -} - -func (i *Action) GetState(ctx context.Context, in *pb.GetStateEnvelope) (*any.Any, error) { - actionKey := i.GetStateKey(in.Key) - state, err := i.GetStateFromStore(actionKey) - if err != nil { - log.Errorf("Error getting state: %s", err) - return nil, err - } - - if state == nil { - return &any.Any{}, nil - } - - return &any.Any{ - Value: []byte(state.(string)), - }, nil -} - -func (i *Action) OnActorGetState(c *routing.Context) { - id := c.Param(actionIDHeader) - contextID := c.Param(contextIDPath) - key := fmt.Sprintf("%s-%s", id, contextID) - - state, err := i.GetStateFromStore(key) - if err != nil { - log.Errorf("Error getting actor state: %s", err) - RespondWithError(c.RequestCtx, 500, "Error getting actor state") - return - } - - if state == nil { - RespondEmpty(c.RequestCtx, 200) - } else { - RespondWithString(c.RequestCtx, 200, state.(string)) - } -} - -func (i *Action) OnGetState(c *routing.Context) { - key := c.Param("key") - actionKey := i.GetStateKey(key) - state, err := i.GetStateFromStore(actionKey) - if err != nil { - log.Errorf("Error getting state: %s", err) - RespondWithError(c.RequestCtx, 500, "Error getting state") - return - } - - if state == nil { - RespondEmpty(c.RequestCtx, 200) - } else { - RespondWithString(c.RequestCtx, 200, state.(string)) - } -} - -func SerializeToJSON(obj interface{}) ([]byte, error) { - buffer := &bytes.Buffer{} - encoder := jsoniter.ConfigFastest.NewEncoder(buffer) - encoder.SetEscapeHTML(false) - err := encoder.Encode(obj) - if err != nil { - return nil, err - } - - bytes := buffer.Bytes() - return bytes, nil -} - -func RespondWithJSON(ctx *fasthttp.RequestCtx, code int, obj []byte) { - ctx.Response.Header.SetContentType("application/json") - ctx.Response.SetStatusCode(code) - ctx.Response.SetBody(obj) -} - -func RespondWithString(ctx *fasthttp.RequestCtx, code int, obj string) { - ctx.Response.Header.SetContentType("application/json") - ctx.Response.SetStatusCode(code) - ctx.Response.SetBodyString(obj) -} - -func RespondWithError(ctx *fasthttp.RequestCtx, code int, message string) { - json, _ := SerializeToJSON(map[string]string{"error": message}) - RespondWithJSON(ctx, code, json) -} - -func RespondEmpty(ctx *fasthttp.RequestCtx, code int) { - ctx.Response.SetBody(nil) - ctx.Response.SetStatusCode(code) -} - -func (i *Action) TraceSpanFromContext(c context.Context, events []Event, operation string) (context.Context, *trace.Span, *trace.SpanContext) { - if i.Configuration.Spec.TracingSpec.Enabled { - if i.Configuration.Spec.TracingSpec.IncludeEvent { - return diag.TraceSpanFromContext(c, projectEvents(events), operation, i.Configuration.Spec.TracingSpec.IncludeEvent, i.Configuration.Spec.TracingSpec.IncludeEventBody) - } else { - return diag.TraceSpanFromContext(c, nil, operation, i.Configuration.Spec.TracingSpec.IncludeEvent, i.Configuration.Spec.TracingSpec.IncludeEventBody) - } - } else { - return nil, nil, nil - } -} -func (i *Action) TraceSpanFromRoutingContext(c *routing.Context, events []Event, operation string) (context.Context, *trace.Span, *trace.SpanContext) { - if i.Configuration.Spec.TracingSpec.Enabled { - if i.Configuration.Spec.TracingSpec.IncludeEvent { - return diag.TraceSpanFromRoutingContext(c, projectEvents(events), operation, i.Configuration.Spec.TracingSpec.IncludeEvent, i.Configuration.Spec.TracingSpec.IncludeEventBody) - } else { - return diag.TraceSpanFromRoutingContext(c, nil, operation, i.Configuration.Spec.TracingSpec.IncludeEvent, i.Configuration.Spec.TracingSpec.IncludeEventBody) - } - } else { - return nil, nil, nil - } -} -func projectEvents(events []Event) *[]diag.Event { - bytes, _ := json.Marshal(events) - var ret []diag.Event = make([]diag.Event, len(events)) - json.Unmarshal(bytes, &ret) - return &ret -} -func (i *Action) InitRoutes() *routing.Router { - router := routing.New() - - router.Post("/publish", func(c *routing.Context) error { - i.OnEventPublished(c) - return nil - }) - - router.Get("/action//", func(c *routing.Context) error { - i.OnInvokeAction(c) - return nil - }) - - router.Post("/action//", func(c *routing.Context) error { - i.OnInvokeAction(c) - return nil - }) - - router.Delete("/action//", func(c *routing.Context) error { - i.OnInvokeAction(c) - return nil - }) - - router.Put("/action//", func(c *routing.Context) error { - i.OnInvokeAction(c) - return nil - }) - - router.Post("/action//activate/local", func(c *routing.Context) error { - i.OnLocalBatchActivation(c) - return nil - }) - - router.Get("/action///", func(c *routing.Context) error { - i.OnInvokeAction(c) - return nil - }) - - router.Post("/action///", func(c *routing.Context) error { - i.OnInvokeAction(c) - return nil - }) - - router.Delete("/action///", func(c *routing.Context) error { - i.OnInvokeAction(c) - return nil - }) - - router.Put("/action///", func(c *routing.Context) error { - i.OnInvokeAction(c) - return nil - }) - - router.Post("/state", func(c *routing.Context) error { - i.OnStatePosted(c) - return nil - }) - - router.Get("/state/", func(c *routing.Context) error { - i.OnGetState(c) - return nil - }) - - router.Get("/state//", func(c *routing.Context) error { - i.OnActorGetState(c) - return nil - }) - - router.Post("/state//", func(c *routing.Context) error { - i.OnActorStatePosted(c) - return nil - }) - - router.Put("/state//", func(c *routing.Context) error { - i.OnActorStatePosted(c) - return nil - }) - - router.Post("/invoke/*", func(c *routing.Context) error { - i.OnActionInvoked(c) - return nil - }) - - router.Get("/metadata", func(c *routing.Context) error { - i.OnGetMetadata(c) - return nil - }) - - return router -} - -func (i *Action) OnGetMetadata(c *routing.Context) { - m := ActionMetadata{ - ID: i.ActionID, - AppAddress: i.GetLocalAppEndpoint(""), - Protocol: string(i.Protocol), - Actors: []ActorMetadata{}, - Healthy: true, - } - - if i.StateStore != nil { - for _, es := range i.EventSources { - if es.Name == string(stateStoreType) { - m.StateStore = es.Spec.Type - break - } - } - } - - if len(i.AppConfig.Entities) > 0 { - for _, e := range i.AppConfig.Entities { - am := ActorMetadata{ - ActorType: e, - ActivatedContexts: []string{}, - } - - for k, v := range i.ActiveContexts { - if v == e { - am.ActivatedContexts = append(am.ActivatedContexts, k) - } - } - - m.Actors = append(m.Actors, am) - } - } - - b, err := i.json.Marshal(&m) - if err != nil { - log.Errorf("Error getting metadata: %s", err) - RespondWithError(c.RequestCtx, 500, "Error getting metadata") - return - } - - RespondWithJSON(c.RequestCtx, 200, b) -} - -func (i *Action) OnActionInvoked(c *routing.Context) { - - _, span, spanCtx := i.TraceSpanFromRoutingContext(c, nil, "OnActionInvoked") - if span != nil { - defer span.End() - } - - actionMethod := string(c.Path())[8:] - verbMethod := string(c.Method()) - contextID := string(c.Request.Header.Peek(contextIDHeader)) - from := string(c.Request.Header.Peek(fromHeader)) - targetID := string(c.Request.Header.Peek(actionTargetIDHeader)) - payload := c.RequestCtx.PostBody() - - headers := map[string]string{ - fromHeader: from, - actionTargetIDHeader: targetID, - } - if contextID != "" { - headers[contextIDHeader] = contextID - } - - var url string - if contextID != "" { - verbMethod = http.MethodPut - url = i.GetLocalAppEndpoint(fmt.Sprintf("actors/%s/%s/%s", targetID, contextID, actionMethod)) - } else { - url = i.GetLocalAppEndpoint(actionMethod) - } - - r, err := i.DispatchInvokeToApp(url, actionMethod, verbMethod, headers, payload, spanCtx) - if err != nil { - RespondWithError(c.RequestCtx, 500, err.Error()) - return - } - - RespondWithJSON(c.RequestCtx, 200, r) -} - -func (i *Action) invoke(actionMethod, targetID, contextID, fromID, verbMethod string, queryString string, payload []byte, ctx *trace.SpanContext) ([]byte, error) { - headers := map[string]string{ - fromHeader: fromID, - actionTargetIDHeader: targetID, - } - - if contextID != "" { - headers[contextIDHeader] = contextID - } - - var url string - if contextID != "" { - activated := i.ContextActivated(targetID, contextID) - if !activated { - err := i.ActivateContext(targetID, contextID) - if err != nil { - log.Errorf("Error activating %s/%s: %s", targetID, contextID, err) - } - } - - verbMethod = http.MethodPut - url = i.GetLocalAppEndpoint(fmt.Sprintf("actors/%s/%s/%s", targetID, contextID, actionMethod)) - } else { - url = i.GetLocalAppEndpoint(actionMethod) - } - if queryString != "" { - url = url + "?" + queryString - } - - r, err := i.DispatchInvokeToApp(url, actionMethod, verbMethod, headers, payload, ctx) - if err != nil { - return nil, err - } - - return r, nil -} - -func (i *Action) Invoke(ctx context.Context, in *pb.InvokeEnvelope) (*pb.InvokeEnvelope, error) { - - md, _ := metadata.FromIncomingContext(ctx) - actionMethod := md.Get(invokedMethodHeader)[0] - targetID := md.Get(actionTargetIDHeader)[0] - contextIDMetadata := md.Get(contextIDHeader) - from := md.Get(fromHeader)[0] - verbMethod := md.Get(invokedHTTPVerbHeader)[0] - - corId := md.Get(diag.CorrelationID)[0] - _, tSpan := diag.TraceSpanFromCorrelationId(corId, "Invoke", actionMethod, targetID, from, verbMethod) - if tSpan != nil { - defer tSpan.End() - } - - payload := in.GetData().GetValue() - contextID := "" - if len(contextIDMetadata) > 0 { - contextID = contextIDMetadata[0] - } - - spanContext := diag.DeserializeSpanContextPointer(corId) - - r, err := i.invoke(actionMethod, targetID, contextID, from, verbMethod, "", payload, spanContext) - if err != nil { - diag.SetSpanStatus(tSpan, trace.StatusCodeInternal, err.Error()) - return nil, err - } - - diag.SetSpanStatus(tSpan, trace.StatusCodeOK, fmt.Sprintf("action invocation succeeded with return value %s", string(r))) - return &pb.InvokeEnvelope{ - Data: &any.Any{ - Value: r, - }, - }, nil -} - -func (i *Action) SendStateViaGRPC(url string, state []KeyValState) error { - ctx, cancel := context.WithTimeout(context.Background(), time.Minute*1) - defer cancel() - - s := &pb.State{} - for _, item := range state { - b, err := i.json.Marshal(item.Value) - if err != nil { - continue - } - - s.State = append(s.State, &pb.KeyVal{ - Key: item.Key, - Value: &any.Any{ - Value: b, - }, - }) - } - - c := pb.NewAppClient(i.GRPCClient) - _, err := c.RestoreState(ctx, s) - if err != nil { - return err - } - - return nil -} - -func (i *Action) ExecuteGRPCInvokeCall(url, appMethod string, headers map[string]string, payload []byte) ([]byte, error) { - md := metadata.New(headers) - ctx, cancel := context.WithTimeout(context.Background(), time.Minute*1) - defer cancel() - - contextID := "" - if val, ok := headers[contextIDHeader]; ok { - contextID = val - } - - ctxMetadata := metadata.NewOutgoingContext(ctx, md) - c := pb.NewAppClient(i.GRPCClient) - resp, err := c.Invoke(ctxMetadata, &pb.InvokeMethod{ - MethodName: appMethod, - ContextId: contextID, - Data: &any.Any{Value: payload}, - }) - if err != nil { - return nil, err - } - - return resp.Value, nil -} - -func (i *Action) DispatchInvokeToApp(url, appMethod, verbMethod string, headers map[string]string, payload []byte, ctx *trace.SpanContext) ([]byte, error) { - if i.Protocol == GRPCProtocol { - r, err := i.ExecuteGRPCInvokeCall(url, appMethod, headers, payload) - if err != nil { - return nil, err - } - - return r, nil - } - - r, err := i.ExecuteHTTPCall(url, verbMethod, headers, payload, ctx) - if err != nil { - return nil, err - } - - return r, nil -} - -func (i *Action) UpdateEventSource(ctx context.Context, in *pb.EventSource) (*empty.Empty, error) { - e := EventSource{ - Name: in.Name, - Spec: EventSourceSpec{ - Type: in.Spec.Type, - }, - } - - var connectionInfo interface{} - err := i.json.Unmarshal(in.Spec.ConnectionInfo.Value, &connectionInfo) - if err != nil { - log.Errorf("Error updating event source: %s", err) - } else { - e.Spec.ConnectionInfo = connectionInfo - updated := false - for index, es := range i.EventSources { - if es.Name == e.Name { - i.EventSources[index] = e - updated = true - break - } - } - if !updated { - i.EventSources = append(i.EventSources, e) - } - } - - return &empty.Empty{}, nil -} - -func (i *Action) SetupStateStore() { - for _, es := range i.EventSources { - if es.Name == string(stateStoreType) { - stateStore := i.GetActionSource(es.Spec.Type) - err := stateStore.Init(es.Spec) - if err != nil { - log.Errorf("Error from state store Init: %s", err) - return - } - - if stateStore != nil { - i.StateStore = stateStore - log.Infof("State Store of type %s found and initialized", es.Spec.Type) - break - } - } - } -} - -func (i *Action) RegisterActionSources() { - i.ActionSources = make(map[string]ActionSource) - i.ActionSources["azure.messaging.eventhubs"] = NewAzureEventHubs() - i.ActionSources["aws.messaging.sns"] = NewAWSSns() - i.ActionSources["aws.messaging.sqs"] = NewAWSSQS() - i.ActionSources["gcp.storage.bucket"] = NewGCPStorage() - i.ActionSources["actions.state.local"] = NewMemoryStateStore() - i.ActionSources["actions.state.redis"] = NewRedisStateStore(i.json) - i.ActionSources["actions.state.cosmosdb"] = NewCosmosDBStateStore() - i.ActionSources["actions.test.mock"] = NewMockEventSource() - i.ActionSources["actions.sender.redis"] = NewRedisSender() - i.ActionSources["http"] = NewHttpSource() - i.ActionSources["azure.databases.cosmosdb"] = NewCosmosDB() - i.ActionSources["aws.databases.dynamodb"] = NewDynamoDB() - i.ActionSources["kafka"] = NewKafka() - i.ActionSources["redis"] = NewRedis() - i.ActionSources["mqtt"] = NewMQTT() - i.ActionSources["rabbitmq"] = NewRabbitMQ() -} - -func (i *Action) WaitUntilAppIsReady() { - if i.ApplicationPort == "" { - return - } - - log.Infof("Application protocol: %s", string(i.Protocol)) - log.Infof("Waiting for app on port %s", i.ApplicationPort) - - for { - conn, _ := net.DialTimeout("tcp", net.JoinHostPort("localhost", i.ApplicationPort), time.Millisecond*500) - if conn != nil { - conn.Close() - break - } - } - - log.Infof("Application discovered on port %s", i.ApplicationPort) -} - -func (i *Action) CreateAppChannels() { - if i.ApplicationPort != "" && i.Protocol == GRPCProtocol { - c, err := i.GetGRPCConnection(fmt.Sprintf("127.0.0.1:%s", i.ApplicationPort)) - if err != nil { - log.Errorf("Error establishing connection to app grpc on port %s: %s", i.ApplicationPort, err) - return - } - - i.GRPCClient = c - } -} - -func (i *Action) Run(httpPort int, grpcPort int) { - start := time.Now() - log.Infof("Action running in %s mode", i.Mode) - - i.ConfigureTracing() - i.WaitUntilAppIsReady() - i.CreateAppChannels() - i.GetApplicationConfig() - i.RegisterActionSources() - i.SetupEventSources() - i.SetupSender() - i.SetupStateStore() - i.TryRestoreState() - i.StartHTTPServer(httpPort) - i.StartGRPCServer(grpcPort) - i.SetIPAddress() - - d := time.Since(start).Seconds() * 1000 - log.Infof("Action initialized. Status: Running. Init Elapsed %vms", d) - - go i.ConnectToPlacementService() -} - -func (i *Action) SetupSender() { - for _, es := range i.EventSources { - if es.Name == sender { - sender := i.GetActionSource(es.Spec.Type) - err := sender.Init(es.Spec) - if err != nil { - log.Errorf("Error from sender Init - %s", err) - return - } - - if sender != nil { - i.Sender = sender.(Sender) - i.Sender.StartLoop(i.PostEventToAppWithRetries, context.Background()) - log.Infof("Sender of type %s found and initialized", es.Spec.Type) - break - } - } - } -} - -func (i *Action) StartHTTPServer(httpPort int) { - log.Info("Starting Action HTTP server") - - go func() { - router := i.InitRoutes() - withCors := cors.NewCorsHandler(cors.Options{ - AllowedOrigins: i.AllowedOrigins, - Debug: false, - }) - log.Fatal(fasthttp.ListenAndServe(fmt.Sprintf(":%v", httpPort), withCors.CorsMiddleware(router.HandleRequest))) - }() -} - -func (i *Action) TryRestoreState() { - if i.StateStore != nil && i.ApplicationPort != "" { - - _, span, spanCtx := i.TraceSpanFromContext(context.Background(), nil, "TryRestoreState") - if span != nil { - defer span.End() - } - - s := i.StateStore.(StateStore) - state, err := s.GetAll(i.ActionID) - if err == nil && len(state) > 0 { - url := i.GetLocalAppEndpoint("state") - - if i.Protocol == GRPCProtocol { - err = i.SendStateViaGRPC(url, state) - } else if i.Protocol == HTTPProtocol { - err = i.SendStateViaHTTP(url, state, spanCtx) - } - - if err != nil { - log.Errorf("Error restoring state to app: %s", err) - } else { - log.Info("State restored successfully") - } - } - } -} - -func (i *Action) SetIPAddress() { - if i.Mode == string(StandaloneMode) { - addrs, err := net.InterfaceAddrs() - if err != nil { - log.Errorf("Error getting interfaces: %s", err) - return - } - - for _, a := range addrs { - if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { - if ipnet.IP.To4() != nil { - i.IPAddress = ipnet.IP.String() - return - } - } - } - } else if i.Mode == string(KubernetesMode) { - i.IPAddress = os.Getenv("HOST_IP") - } -} - -func (i *Action) isTargetLocal(address string) bool { - return strings.Contains(address, "localhost") || strings.Contains(address, "127.0.0.1") || - address == fmt.Sprintf("%s:%v", i.IPAddress, gRPCPort) -} - -func (i *Action) GetApplicationConfig() { - if i.Protocol == GRPCProtocol { - client := pb.NewAppClient(i.GRPCClient) - resp, err := client.GetConfig(context.Background(), &empty.Empty{}) - if err != nil { - return - } - - if resp != nil { - i.AppConfig = ApplicationConfig{Entities: resp.Entities} - log.Info("Application config pulled successfully") - } - } else if i.Protocol == HTTPProtocol { - url := i.GetLocalAppEndpoint("actions/config") - resp, err := i.ExecuteHTTPCall(url, "GET", nil, nil, nil) - if err != nil { - return - } - - var config ApplicationConfig - err = i.json.Unmarshal(resp, &config) - if err != nil { - return - } - - i.AppConfig = config - log.Info("Application config pulled successfully") - } -} - -func (i *Action) ContextActivated(targetID, contextID string) bool { - i.ActiveContextsLock.RLock() - defer i.ActiveContextsLock.RUnlock() - _, exists := i.ActiveContexts[contextID] - return exists -} - -func (i *Action) ActivateContext(targetID, contextID string) error { - i.ActiveContextsLock.Lock() - defer i.ActiveContextsLock.Unlock() - - contextKey := fmt.Sprintf("%s-%s", targetID, contextID) - state, err := i.GetStateFromStore(contextKey) - if err != nil { - return err - } - - payload := "{}" - if state != nil && state.(string) != "" { - payload = state.(string) - } - - url := i.GetLocalAppEndpoint(fmt.Sprintf("actors/%s/%s", targetID, contextID)) - _, err = i.ExecuteHTTPCall(url, http.MethodPost, nil, []byte(payload), nil) - if err != nil { - return err - } - - i.ActiveContexts[contextID] = targetID - return nil -} - -func (i *Action) GetPlacementClientPersistently() pb.PlacementService_ReportActionStatusClient { - for { - conn, err := grpc.Dial(i.PlacementAddress, grpc.WithInsecure()) - if err != nil { - time.Sleep(time.Second * 1) - continue - } - header := metadata.New(map[string]string{"id": i.IPAddress}) - ctx := metadata.NewOutgoingContext(context.Background(), header) - client := pb.NewPlacementServiceClient(conn) - stream, err := client.ReportActionStatus(ctx) - if err != nil { - time.Sleep(time.Second * 1) - continue - } - - return stream - } -} - -func (i *Action) ConnectToPlacementService() { - if i.PlacementAddress == "" { - log.Info("No placement service discovered") - return - } - - log.Infof("Starting connection attempt to placement service at %s", i.PlacementAddress) - stream := i.GetPlacementClientPersistently() - - log.Infof("Established connection to placement service at %s", i.PlacementAddress) - - go func() { - for { - host := pb.Host{ - Name: i.IPAddress, - Load: 1, - Entities: i.AppConfig.Entities, - Port: int64(gRPCPort), - } - - if stream != nil { - if err := stream.Send(&host); err != nil { - log.Error("Connection failure to placement service: retrying") - stream = i.GetPlacementClientPersistently() - } - } - time.Sleep(time.Second * 1) - } - }() - - go func() { - for { - resp, err := stream.Recv() - if err != nil { - log.Error("Connection failure to placement service: retrying") - stream = i.GetPlacementClientPersistently() - } - if resp != nil { - i.OnPlacementOrder(resp) - } - } - }() -} - -func (i *Action) LookupContextAddress(actionID, contextID string) string { - i.PlacementTableLock.RLock() - defer i.PlacementTableLock.RUnlock() - - t := i.PlacementTables.Entries[actionID] - if t == nil { - return "" - } - a, _ := t.GetHost(contextID) - return fmt.Sprintf("%s:%v", a.Name, a.Port) -} - -func (i *Action) BlockPlacements() { - i.PlacementSignal = make(chan struct{}) - i.PlacementBlock = true -} - -func (i *Action) UnblockPlacements() { - if i.PlacementBlock { - i.PlacementBlock = false - close(i.PlacementSignal) - } -} - -func (i *Action) OnPlacementOrder(in *pb.PlacementOrder) { - log.Infof("Placement order received: %s", in.Operation) - - switch in.Operation { - case "lock": - { - i.BlockPlacements() - - go func() { - time.Sleep(time.Second * 5) - i.UnblockPlacements() - }() - } - case "unlock": - { - i.UnblockPlacements() - } - case "update": - { - i.UpdatePlacements(in.Tables) - } - } -} - -func (i *Action) UpdatePlacements(in *pb.PlacementTables) { - if in.Version != i.PlacementTables.Version { - i.PlacementTableLock.Lock() - defer i.PlacementTableLock.Unlock() - - for k, v := range in.Entries { - loadMap := map[string]*placement.Host{} - for lk, lv := range v.LoadMap { - loadMap[lk] = placement.NewHost(lv.Name, lv.Load, lv.Port) - } - c := placement.NewFromExisting(v.Hosts, v.SortedSet, loadMap) - i.PlacementTables.Entries[k] = c - } - - i.PlacementTables.Version = in.Version - - log.Info("Placement tables updated") - } -} diff --git a/pkg/action/action_source.go b/pkg/action/action_source.go deleted file mode 100644 index 5f4f145dccfaebd22d8ae2181e86dc87cac6d309..0000000000000000000000000000000000000000 --- a/pkg/action/action_source.go +++ /dev/null @@ -1,8 +0,0 @@ -package action - -type ActionSource interface { - Init(eventSourceSpec EventSourceSpec) error - ReadAsync(metadata interface{}, callback func([]byte) error) error - Read(metadata interface{}) (interface{}, error) - Write(data interface{}) error -} diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go deleted file mode 100644 index 9f3e098d5c05141ca5716ed6f834343669bf4cc3..0000000000000000000000000000000000000000 --- a/pkg/action/action_test.go +++ /dev/null @@ -1,108 +0,0 @@ -package action - -import ( - "net" - "os" - "strconv" - "testing" - "time" - - "github.com/actionscore/actions/e2e/mocks" - "github.com/stretchr/testify/assert" - "github.com/valyala/fasthttp" -) - -func TestInvokeActionGETWithOneParameter(t *testing.T) { - is := assert.New(t) - req := fasthttp.AcquireRequest() - req.SetRequestURI("http://localhost:3505/action/test-action/echo?param=1") - req.Header.Set("actions.action-address", "localhost:60006") - req.Header.SetMethod("GET") - resp := fasthttp.AcquireResponse() - client := &fasthttp.Client{} - client.Do(req, resp) - bodyBytes := resp.Body() - is.Equal(string(bodyBytes), "param=1;") -} -func TestInvokeActionGETWithTwoParameters(t *testing.T) { - is := assert.New(t) - req := fasthttp.AcquireRequest() - req.SetRequestURI("http://localhost:3505/action/test-action/echo?param1=1¶m2=2") - req.Header.Set("actions.action-address", "localhost:60006") - req.Header.SetMethod("GET") - resp := fasthttp.AcquireResponse() - client := &fasthttp.Client{} - client.Do(req, resp) - bodyBytes := resp.Body() - body := string(bodyBytes) - is.True(body == "param1=1;param2=2;" || body == "param2=2;param1=1;") -} - -func TestInvokeActionGETWithEncodedParameter(t *testing.T) { - is := assert.New(t) - req := fasthttp.AcquireRequest() - req.SetRequestURI("http://localhost:3505/action/test-action/echo?param=%24%26%2b%2c%2f%3a%3b%3d%3f%40") - req.Header.Set("actions.action-address", getIP()+":60006") - req.Header.SetMethod("GET") - resp := fasthttp.AcquireResponse() - client := &fasthttp.Client{} - client.Do(req, resp) - bodyBytes := resp.Body() - is.Equal(string(bodyBytes), "param=$&+,/:;=?@;") -} - -func TestInvokeActionGETWithNoParameters(t *testing.T) { - is := assert.New(t) - req := fasthttp.AcquireRequest() - req.SetRequestURI("http://localhost:3505/action/test-action/echo") - req.Header.Set("actions.action-address", "localhost:60006") - req.Header.SetMethod("GET") - resp := fasthttp.AcquireResponse() - client := &fasthttp.Client{} - client.Do(req, resp) - bodyBytes := resp.Body() - is.Equal(string(bodyBytes), "") -} - -func TestMain(m *testing.M) { - launchAndWait(func(port int, appPort string, grpcPort int) { - app := mocks.NewMockApp(true, 100, true) - app.Run(port) - }, 8089, "", 0) - - launchAndWait(func(port int, appPort string, grpcPort int) { - i := NewAction("test-action", appPort, "standalone", "http", "", "", "", "", "") - i.Run(port, grpcPort) - }, 3505, "8089", 60006) - os.Exit(m.Run()) -} - -type launcher func(int, string, int) - -func launchAndWait(fn launcher, port int, appPort string, grpcPort int) { - go func(port int, appPort string, grpcPort int) { - fn(port, appPort, grpcPort) - }(port, appPort, grpcPort) - timeout := time.Duration(2) * time.Second - for { - conn, _ := net.DialTimeout("tcp", net.JoinHostPort("", strconv.Itoa(port)), timeout) - if conn != nil { - conn.Close() - break - } - } -} -func getIP() string { - ifaces, _ := net.Interfaces() - for _, i := range ifaces { - addrs, _ := i.Addrs() - for _, a := range addrs { - if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { - if ipnet.IP.To4() != nil { - return ipnet.IP.String() - } - } - } - } - return "127.0.0.1" -} diff --git a/pkg/action/configuration.go b/pkg/action/configuration.go deleted file mode 100644 index 6b4bb385a3fdaa9b7302f3bd6c5d799f9d48c3e0..0000000000000000000000000000000000000000 --- a/pkg/action/configuration.go +++ /dev/null @@ -1,17 +0,0 @@ -package action - -type Configuration struct { - Spec ConfigurationSpec `json:"spec,omitempty"` -} - -type ConfigurationSpec struct { - TracingSpec TracingSpec `json:"tracing,omitempty"` -} - -type TracingSpec struct { - Enabled bool `json:"enabled"` - ExporterType string `json:"exporterType"` - ExporterAddress string `json:"exporterAddress"` - IncludeEvent bool `json:"includeEvent"` - IncludeEventBody bool `json:"includeEventBody"` -} diff --git a/pkg/action/context_activation.go b/pkg/action/context_activation.go deleted file mode 100644 index 9dd0abc3586c5c0555244b8b7e5c794b51ec6cbb..0000000000000000000000000000000000000000 --- a/pkg/action/context_activation.go +++ /dev/null @@ -1,6 +0,0 @@ -package action - -type ContextActivation struct { - State []byte `json:"state"` - ID string `json:"id"` -} diff --git a/pkg/action/event.go b/pkg/action/event.go deleted file mode 100644 index eebaee61bed892d74cef46672d8d83fe7ebdda55..0000000000000000000000000000000000000000 --- a/pkg/action/event.go +++ /dev/null @@ -1,12 +0,0 @@ -package action - -import "time" - -type Event struct { - EventName string `json:"eventName,omitempty"` - To []string `json:"to,omitempty"` - Concurrency string `json:"concurrency,omitempty"` - CreatedAt time.Time `json:"createdAt,omitempty"` - State []KeyValState `json:"state,omitempty"` - Data interface{} `json:"data,omitempty"` -} diff --git a/pkg/action/eventsource.go b/pkg/action/eventsource.go deleted file mode 100644 index 97ce31e291a78dd1695de2ec57c46f276707aa79..0000000000000000000000000000000000000000 --- a/pkg/action/eventsource.go +++ /dev/null @@ -1,17 +0,0 @@ -package action - -import ( - eventing_v1alpha1 "github.com/actionscore/actions/pkg/apis/eventing/v1alpha1" -) - -type EventSource struct { - Name string `json:"name"` - Spec EventSourceSpec `json:"spec"` - Sender Sender -} - -type EventSourceSpec struct { - Type string `json:"type"` - ConnectionInfo interface{} `json:"connectionInfo"` - SenderOptions eventing_v1alpha1.SenderOptions `json:"senderOptions,omitempty"` -} diff --git a/pkg/action/memory_state_store.go b/pkg/action/memory_state_store.go deleted file mode 100644 index 739daa111108ff27e68e7e7a90c1b825dcac8bcd..0000000000000000000000000000000000000000 --- a/pkg/action/memory_state_store.go +++ /dev/null @@ -1,29 +0,0 @@ -package action - -type MemoryStateStore struct { - State map[string]interface{} -} - -func NewMemoryStateStore() *MemoryStateStore { - return &MemoryStateStore{} -} - -func (m *MemoryStateStore) Init(eventSourceSpec EventSourceSpec) error { - m.State = make(map[string]interface{}) - return nil -} - -func (m *MemoryStateStore) ReadAsync(metadata interface{}, callback func([]byte) error) error { - return nil -} - -func (m *MemoryStateStore) Read(metadata interface{}) (interface{}, error) { - key := metadata.(string) - return m.State[key], nil -} - -func (m *MemoryStateStore) Write(data interface{}) error { - state := data.(KeyValState) - m.State[state.Key] = state.Value - return nil -} diff --git a/pkg/action/metadata.go b/pkg/action/metadata.go deleted file mode 100644 index ecea79c1068946bc39f16a0a1c29f3e340f9fa3b..0000000000000000000000000000000000000000 --- a/pkg/action/metadata.go +++ /dev/null @@ -1,16 +0,0 @@ -package action - -type ActionMetadata struct { - ID string `json:"id"` - Actors []ActorMetadata `json:"actors"` - Protocol string `json:"protocol"` - StateStore string `json:"stateStore"` - StateItemsCount int `json:"stateItemsCount"` - AppAddress string `json:"appAddress"` - Healthy bool `json:"healthy"` -} - -type ActorMetadata struct { - ActorType string `json:"actorType"` - ActivatedContexts []string `json:"activatedContexts"` -} diff --git a/pkg/action/mock_eventsource.go b/pkg/action/mock_eventsource.go deleted file mode 100644 index 09497941c654437874f318c1a9cb3d09d2a3c645..0000000000000000000000000000000000000000 --- a/pkg/action/mock_eventsource.go +++ /dev/null @@ -1,74 +0,0 @@ -package action - -import ( - "encoding/json" - "fmt" - "strconv" - "time" -) - -type MockEventSource struct { - Spec EventSourceSpec -} - -type MockMetadata struct { - MessageCount string `json:"messageCount,omitempty"` - Interval string `json:"interval,omitempty"` -} - -func NewMockEventSource() *MockEventSource { - return &MockEventSource{} -} - -func (a *MockEventSource) Init(eventSourceSpec EventSourceSpec) error { - a.Spec = eventSourceSpec - return nil -} - -func (a *MockEventSource) Write(data interface{}) error { - _, err := json.Marshal(a.Spec.ConnectionInfo) - if err != nil { - return err - } - - _, err = json.Marshal(data) - if err != nil { - return err - } - - return nil -} - -func (a *MockEventSource) Read(metadata interface{}) (interface{}, error) { - return nil, nil -} - -func (a *MockEventSource) ReadAsync(metadata interface{}, callback func([]byte) error) error { - b, err := json.Marshal(a.Spec.ConnectionInfo) - if err != nil { - return err - } - var mockmeta MockMetadata - err = json.Unmarshal(b, &mockmeta) - if err != nil { - return err - } - count := 0 - numInterval, _ := strconv.Atoi(mockmeta.Interval) - numMessageCount, _ := strconv.Atoi(mockmeta.MessageCount) - if numInterval == 0 { - numInterval = 50 - } - for { - body := fmt.Sprintf("{\"time\":\"%s\"}", time.Now()) - fmt.Println(body + "#" + time.Now().Format(time.RFC3339)) - callback([]byte(body)) - time.Sleep(time.Duration(numInterval) * time.Millisecond) - count++ - if numMessageCount > 0 && count >= numMessageCount { - break - } - } - - return nil -} diff --git a/pkg/action/redis_sender.go b/pkg/action/redis_sender.go deleted file mode 100644 index 87f3201cad4cb5959cbdec0cfab7e38b345a7a83..0000000000000000000000000000000000000000 --- a/pkg/action/redis_sender.go +++ /dev/null @@ -1,141 +0,0 @@ -package action - -import ( - "context" - "encoding/json" - "fmt" - "time" - - log "github.com/Sirupsen/logrus" - eventing_v1alpha1 "github.com/actionscore/actions/pkg/apis/eventing/v1alpha1" - "github.com/go-redis/redis" -) - -type RedisSender struct { - InboxQueue string - Client *redis.Client - Options eventing_v1alpha1.SenderOptions - BatchBuffer []Event - LastSentBatch time.Time -} - -func (s *RedisSender) MarshalEvents(events []Event) ([]byte, error) { - b, err := json.Marshal(events) - if err != nil { - return nil, err - } - return b, nil -} -func (s *RedisSender) MarshalEvent(event Event) ([]byte, error) { - return s.MarshalEvents([]Event{event}) -} -func (s *RedisSender) UnMarshalEvents(value string) ([]Event, error) { - var events []Event - if err := json.Unmarshal([]byte(value), &events); err != nil { - return nil, err - } - return events, nil -} -func (s *RedisSender) Enqueue(event Event) error { - s.BatchBuffer = append(s.BatchBuffer, event) - if len(s.BatchBuffer) >= s.Options.SendBufferSize { - b, err := s.MarshalEvents(s.BatchBuffer) - if err != nil { - return err - } - s.BatchBuffer = nil - s.LastSentBatch = time.Now() - return s.Client.LPush(s.InboxQueue, b).Err() - } - return nil -} - -func (s *RedisSender) Dequeue(workerIndex int, events []Event) error { - queueName := fmt.Sprintf("%s%s_%d", s.InboxQueue, "_processing", workerIndex) - b, err := s.MarshalEvents(events) - if err != nil { - return err - } - _, err = s.Client.LRem(queueName, -1, b).Result() - return err -} -func (s *RedisSender) PeekLock(workerIndex int) (*[]Event, error) { - queueName := fmt.Sprintf("%s%s_%d", s.InboxQueue, "_processing", workerIndex) - _, err := s.Client.RPopLPush(s.InboxQueue, queueName).Result() - v, err := s.Client.LRange(queueName, -1, -1).Result() - if err != nil { - return nil, err - } - if len(v) == 1 { - var events []Event - events, err = s.UnMarshalEvents(v[0]) - if err != nil { - return nil, err - } - return &events, nil - } else { - return nil, nil - } -} -func (s *RedisSender) Init(spec EventSourceSpec) error { - s.Options = spec.SenderOptions - s.InboxQueue = spec.SenderOptions.QueueName - connInfo := spec.ConnectionInfo - b, err := json.Marshal(connInfo) - if err != nil { - return err - } - - var redisCreds RedisCredentials - err = json.Unmarshal(b, &redisCreds) - if err != nil { - return err - } - - s.Client = redis.NewClient(&redis.Options{ - Addr: redisCreds.Host, - Password: redisCreds.Password, - DB: 0, - }) - _, err = s.Client.Ping().Result() - if err != nil { - return err - } - - return nil -} -func (b *RedisSender) StartLoop(postFunc func(events *[]Event, ctx1 context.Context) error, ctx context.Context) { - for i := 0; i < b.Options.NumWorkers; i++ { - go func(index int, ctx context.Context) { - for { - evts, err := b.PeekLock(index) - if err != nil { - log.Errorf("Error locking event - %s", err) - } - if evts != nil { - err := postFunc(evts, ctx) - if err == nil { - err = b.Dequeue(index, *evts) - if err != nil { - log.Errorf("Error dequeuing event - %s", err) - } - } else { - log.Errorf("Error posting event - %s", err) - } - } - time.Sleep(time.Millisecond * 50) - } - }(i, ctx) - } -} -func (m *RedisSender) ReadAsync(metadata interface{}, callback func([]byte) error) error { - return nil -} - -func (m *RedisSender) Read(metadata interface{}) (interface{}, error) { - return nil, nil -} - -func (m *RedisSender) Write(data interface{}) error { - return nil -} diff --git a/pkg/action/redis_state_store.go b/pkg/action/redis_state_store.go deleted file mode 100644 index 9abf6b076ae61e5c3c27f6489f9cf891470bcd1d..0000000000000000000000000000000000000000 --- a/pkg/action/redis_state_store.go +++ /dev/null @@ -1,116 +0,0 @@ -package action - -import ( - "context" - "encoding/json" - "fmt" - "math/rand" - "strings" - "time" - - "github.com/joomcode/redispipe/redis" - "github.com/joomcode/redispipe/redisconn" - jsoniter "github.com/json-iterator/go" -) - -type RedisStateStore struct { - Client *redis.SyncCtx - json jsoniter.API -} - -type RedisCredentials struct { - Host string `json:"redisHost"` - Password string `json:"redisPassword"` -} - -func NewRedisStateStore(j jsoniter.API) *RedisStateStore { - return &RedisStateStore{ - json: j, - } -} - -func (r *RedisStateStore) Init(eventSourceSpec EventSourceSpec) error { - rand.Seed(time.Now().Unix()) - - connInfo := eventSourceSpec.ConnectionInfo - b, err := json.Marshal(connInfo) - if err != nil { - return err - } - - var redisCreds RedisCredentials - err = json.Unmarshal(b, &redisCreds) - if err != nil { - return err - } - - ctx := context.Background() - opts := redisconn.Opts{ - DB: 0, - Password: redisCreds.Password, - } - conn, err := redisconn.Connect(ctx, redisCreds.Host, opts) - if err != nil { - return err - } - - r.Client = &redis.SyncCtx{ - S: conn, - } - - return nil -} - -func (r *RedisStateStore) ReadAsync(metadata interface{}, callback func([]byte) error) error { - return nil -} - -func (r *RedisStateStore) GetAll(keyMatch string) ([]KeyValState, error) { - state := []KeyValState{} - scanner := r.Client.Scanner(context.Background(), redis.ScanOpts{Match: fmt.Sprintf("*%s*", keyMatch)}) - for { - keys, err := scanner.Next() - if err != nil { - if err != redis.ScanEOF { - return state, err - } - break - } - - for _, k := range keys { - s, _ := r.Read(k) - state = append(state, KeyValState{ - Key: strings.Replace(k, fmt.Sprintf("%s-", keyMatch), "", -1), - Value: s, - }) - } - } - return state, nil -} - -func (r *RedisStateStore) Read(metadata interface{}) (interface{}, error) { - key := metadata.(string) - - res := r.Client.Do(context.Background(), "GET", key) - if err := redis.AsError(res); err != nil { - return nil, err - } - - if res == nil { - return "", nil - } - - return fmt.Sprintf("%q", res), nil -} - -func (r *RedisStateStore) Write(data interface{}) error { - state := data.(KeyValState) - - bytes, _ := jsoniter.Marshal(state.Value) - res := r.Client.Do(context.Background(), "SET", state.Key, bytes) - if err := redis.AsError(res); err != nil { - return err - } - - return nil -} diff --git a/pkg/action/sender.go b/pkg/action/sender.go deleted file mode 100644 index b85dfbcd23560f0dfff1c1a07358eb1893480fb0..0000000000000000000000000000000000000000 --- a/pkg/action/sender.go +++ /dev/null @@ -1,54 +0,0 @@ -package action - -import ( - "context" - "time" - - eventing_v1alpha1 "github.com/actionscore/actions/pkg/apis/eventing/v1alpha1" -) - -type Sender interface { - //Ensure makes sure a queue name exists - Init(spec EventSourceSpec) error - //Enqueue an event to the sending queue - Enqueue(event Event) error - //Dequeue an event from the sending queue - Dequeue(workerIndex int, events []Event) error - //Get a lock on queue header and return the header element - PeekLock(workerIndex int) (*[]Event, error) - //Start message sending loop - StartLoop(postFunc func(event *[]Event, context context.Context) error, context context.Context) - - ReadAsync(metadata interface{}, callback func([]byte) error) error - Read(metadata interface{}) (interface{}, error) - Write(data interface{}) error -} - -//batch modes -const WindowedBatch = "windowed" -const FixedSizeBatch = "fixsize" -const NoBatch = "no" - -//dedup modes -const NoDedup = "no" -const WindowedDedup = "windowed" - -//order modes -const NoOrder = "no" -const FIFO = "fifo" -const Sorted = "sorted" - -func NewRedisSender() Sender { - return &RedisSender{ - LastSentBatch: time.Time{}, - } -} -func DefaultSenderOptions() eventing_v1alpha1.SenderOptions { - return eventing_v1alpha1.SenderOptions{ - NumWorkers: 10, - BatchOptions: eventing_v1alpha1.BatchOptions{ - BatchMode: FixedSizeBatch, - BatchSize: 10, - }, - } -} diff --git a/pkg/action/state_store.go b/pkg/action/state_store.go deleted file mode 100644 index 5e868360f61040bef2f045e1bb7172f33d7a7766..0000000000000000000000000000000000000000 --- a/pkg/action/state_store.go +++ /dev/null @@ -1,10 +0,0 @@ -package action - -type StateStore interface { - GetAll(keyMatch string) ([]KeyValState, error) -} - -type KeyValState struct { - Key string `json:"key"` - Value interface{} `json:"value"` -} diff --git a/pkg/actors/actors.go b/pkg/actors/actors.go new file mode 100644 index 0000000000000000000000000000000000000000..bb10f7ad82b9664f8af945d578f1a9fbc318d37d --- /dev/null +++ b/pkg/actors/actors.go @@ -0,0 +1,339 @@ +package actors + +import ( + "context" + "errors" + "fmt" + "strings" + "sync" + "time" + + "github.com/actionscore/actions/pkg/channel/http" + + "github.com/golang/protobuf/ptypes/any" + + log "github.com/Sirupsen/logrus" + "github.com/actionscore/actions/pkg/channel" + "github.com/actionscore/actions/pkg/components/state" + "github.com/actionscore/actions/pkg/placement" + pb "github.com/actionscore/actions/pkg/proto" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +type Actors interface { + Call(req *CallRequest) (*CallResponse, error) + Init() error + GetState(req *GetStateRequest) (*StateResponse, error) + SaveState(req *SaveStateRequest) error +} + +type actors struct { + appChannel channel.AppChannel + store state.StateStore + activeActors map[string]string + actorLock *sync.RWMutex + placementTableLock *sync.RWMutex + placementTables *placement.PlacementTables + placementSignal chan struct{} + placementBlock bool + operationUpdateLock *sync.Mutex + grpcConnectionFn func(address string) (*grpc.ClientConn, error) + config Config +} + +const ( + idHeader = "id" + lockOperation = "lock" + unlockOperation = "unlock" + updateOperation = "update" +) + +func NewActors(stateStore state.StateStore, appChannel channel.AppChannel, grpcConnectionFn func(address string) (*grpc.ClientConn, error), config Config) Actors { + return &actors{ + appChannel: appChannel, + config: config, + store: stateStore, + actorLock: &sync.RWMutex{}, + activeActors: map[string]string{}, + placementTableLock: &sync.RWMutex{}, + placementTables: &placement.PlacementTables{Entries: make(map[string]*placement.Consistent)}, + operationUpdateLock: &sync.Mutex{}, + grpcConnectionFn: grpcConnectionFn, + } +} + +func (a *actors) Init() error { + if a.config.PlacementServiceAddress == "" { + return errors.New("couldn't connect to placement service: address is empty") + } + + go a.connectToPlacementService(a.config.PlacementServiceAddress, a.config.HostAddress, a.config.HeartbeatInterval) + return nil +} + +func (a *actors) Call(req *CallRequest) (*CallResponse, error) { + if a.placementBlock { + <-a.placementSignal + } + + targetActorAddress := a.lookupActorAddress(req.ActorType, req.ActorID) + if targetActorAddress == "" { + return nil, fmt.Errorf("error finding address for actor type %s with id %s", req.ActorType, req.ActorID) + } + + var resp []byte + var err error + + if a.isActorLocal(targetActorAddress, a.config.HostAddress, a.config.Port) { + err := a.tryActivateActor(req.ActorType, req.ActorID) + if err != nil { + return nil, err + } + + resp, err = a.callLocalActor(req.ActorType, req.ActorID, req.Method, req.Data) + } else { + resp, err = a.callRemoteActor(targetActorAddress, req.ActorType, req.ActorID, req.Method, req.Data) + } + + if err != nil { + return nil, err + } + + return &CallResponse{ + Data: resp, + }, nil +} + +func (a *actors) callLocalActor(actorType, actorID, actorMethod string, data []byte) ([]byte, error) { + method := fmt.Sprintf("actors/%s/%s/%s", actorType, actorID, actorMethod) + req := channel.InvokeRequest{ + Method: method, + Payload: data, + Metadata: map[string]string{http.HTTPVerb: http.Put}, + } + + resp, err := a.appChannel.InvokeMethod(&req) + if err != nil { + return nil, err + } + + return resp.Data, nil +} + +func (a *actors) callRemoteActor(targetAddress, actorType, actorID, actorMethod string, data []byte) ([]byte, error) { + req := pb.CallActorEnvelope{ + ActorType: actorType, + ActorID: actorID, + Method: actorMethod, + Data: &any.Any{Value: data}, + } + + conn, err := a.grpcConnectionFn(targetAddress) + if err != nil { + return nil, err + } + + client := pb.NewActionsClient(conn) + resp, err := client.CallActor(context.Background(), &req) + if err != nil { + return nil, err + } + + return resp.Data.Value, nil +} + +func (a *actors) tryActivateActor(actorType, actorID string) error { + // read lock actor for read confirmation + a.actorLock.RLock() + _, exists := a.activeActors[actorID] + a.actorLock.RUnlock() + + if !exists { + // lock for actor activation + a.actorLock.Lock() + defer a.actorLock.Unlock() + + key := a.constructActorStateKey(actorType, actorID) + resp, err := a.store.Get(&state.GetRequest{ + Key: key, + }) + + req := channel.InvokeRequest{ + Method: fmt.Sprintf("actors/%s/%s", actorType, actorID), + Metadata: map[string]string{http.HTTPVerb: http.Post}, + Payload: resp.Data, + } + + _, err = a.appChannel.InvokeMethod(&req) + if err != nil { + return fmt.Errorf("error activating actor type %s with id %s: %s", actorType, actorID, err) + } + + a.activeActors[actorID] = actorType + } + + return nil +} + +func (a *actors) isActorLocal(targetActorAddress, hostAddress string, grpcPort int) bool { + return strings.Contains(targetActorAddress, "localhost") || strings.Contains(targetActorAddress, "127.0.0.1") || + targetActorAddress == fmt.Sprintf("%s:%v", hostAddress, grpcPort) +} + +func (a *actors) GetState(req *GetStateRequest) (*StateResponse, error) { + key := a.constructActorStateKey(req.ActorType, req.ActorID) + resp, err := a.store.Get(&state.GetRequest{ + Key: key, + }) + if err != nil { + return nil, err + } + + return &StateResponse{ + Data: resp.Data, + }, nil +} + +func (a *actors) SaveState(req *SaveStateRequest) error { + key := a.constructActorStateKey(req.ActorType, req.ActorID) + err := a.store.Set(&state.SetRequest{ + Value: req.Data, + Key: key, + }) + return err +} + +func (a *actors) constructActorStateKey(actorType, actorID string) string { + return fmt.Sprintf("%s-%s-%s", a.config.ActionsID, actorType, actorID) +} + +func (a *actors) connectToPlacementService(placementAddress, hostAddress string, heartbeatInterval time.Duration) { + log.Infof("actors: starting connection attempt to placement service at %s", placementAddress) + stream := a.getPlacementClientPersistently(placementAddress, hostAddress) + + log.Infof("actors: established connection to placement service at %s", placementAddress) + + go func() { + for { + host := pb.Host{ + Name: hostAddress, + Load: 1, + Entities: a.config.HostedActorTypes, + Port: int64(a.config.Port), + } + + if stream != nil { + if err := stream.Send(&host); err != nil { + log.Error("actors: connection failure to placement service: retrying") + stream = a.getPlacementClientPersistently(placementAddress, hostAddress) + } + } + time.Sleep(heartbeatInterval) + } + }() + + go func() { + for { + resp, err := stream.Recv() + if err != nil { + log.Error("actors: connection failure to placement service: retrying") + stream = a.getPlacementClientPersistently(placementAddress, hostAddress) + } + if resp != nil { + a.onPlacementOrder(resp) + } + } + }() +} + +func (a *actors) getPlacementClientPersistently(placementAddress, hostAddress string) pb.PlacementService_ReportActionStatusClient { + for { + retryInterval := time.Millisecond * 250 + + conn, err := grpc.Dial(placementAddress, grpc.WithInsecure()) + if err != nil { + time.Sleep(retryInterval) + continue + } + header := metadata.New(map[string]string{idHeader: hostAddress}) + ctx := metadata.NewOutgoingContext(context.Background(), header) + client := pb.NewPlacementServiceClient(conn) + stream, err := client.ReportActionStatus(ctx) + if err != nil { + time.Sleep(retryInterval) + continue + } + + return stream + } +} + +func (a *actors) onPlacementOrder(in *pb.PlacementOrder) { + log.Infof("actors: placement order received: %s", in.Operation) + + // lock all incoming calls when an updated table arrives + a.operationUpdateLock.Lock() + defer a.operationUpdateLock.Unlock() + + switch in.Operation { + case lockOperation: + { + a.blockPlacements() + + go func() { + time.Sleep(time.Second * 5) + a.unblockPlacements() + }() + } + case unlockOperation: + { + a.unblockPlacements() + } + case updateOperation: + { + a.updatePlacements(in.Tables) + } + } +} + +func (a *actors) blockPlacements() { + a.placementSignal = make(chan struct{}) + a.placementBlock = true +} + +func (a *actors) unblockPlacements() { + if a.placementBlock { + a.placementBlock = false + close(a.placementSignal) + } +} + +func (a *actors) updatePlacements(in *pb.PlacementTables) { + if in.Version != a.placementTables.Version { + for k, v := range in.Entries { + loadMap := map[string]*placement.Host{} + for lk, lv := range v.LoadMap { + loadMap[lk] = placement.NewHost(lv.Name, lv.Load, lv.Port) + } + c := placement.NewFromExisting(v.Hosts, v.SortedSet, loadMap) + a.placementTables.Entries[k] = c + } + + a.placementTables.Version = in.Version + log.Info("actors: placement tables updated") + } +} + +func (a *actors) lookupActorAddress(actorType, actorID string) string { + // read lock for table map + a.placementTableLock.RLock() + defer a.placementTableLock.RUnlock() + + t := a.placementTables.Entries[actorType] + if t == nil { + return "" + } + host, _ := t.GetHost(actorID) + return fmt.Sprintf("%s:%v", host.Name, host.Port) +} diff --git a/pkg/actors/call_request.go b/pkg/actors/call_request.go new file mode 100644 index 0000000000000000000000000000000000000000..f85e7b9d6606548d4619d25a4c51ba5eb304d4b5 --- /dev/null +++ b/pkg/actors/call_request.go @@ -0,0 +1,9 @@ +package actors + +type CallRequest struct { + ActorType string `json:"actorType"` + ActorID string `json:"actorId"` + Method string `json:"method"` + Data []byte `json:"data"` + Metadata map[string]string `json:"metadata"` +} diff --git a/pkg/actors/call_response.go b/pkg/actors/call_response.go new file mode 100644 index 0000000000000000000000000000000000000000..cae6980dcda0fbe37371e719ec56002eb107aaca --- /dev/null +++ b/pkg/actors/call_response.go @@ -0,0 +1,6 @@ +package actors + +type CallResponse struct { + Data []byte `json:"data"` + Metadata map[string]string `json:"metadata"` +} diff --git a/pkg/actors/config.go b/pkg/actors/config.go new file mode 100644 index 0000000000000000000000000000000000000000..c291efd11587d10f06f42d99393f52184b3176b6 --- /dev/null +++ b/pkg/actors/config.go @@ -0,0 +1,12 @@ +package actors + +import "time" + +type Config struct { + HostAddress string + ActionsID string + PlacementServiceAddress string + HostedActorTypes []string + Port int + HeartbeatInterval time.Duration +} diff --git a/pkg/actors/get_state_request.go b/pkg/actors/get_state_request.go new file mode 100644 index 0000000000000000000000000000000000000000..89c716347167221c046b7baecd47acd4b14495bf --- /dev/null +++ b/pkg/actors/get_state_request.go @@ -0,0 +1,6 @@ +package actors + +type GetStateRequest struct { + ActorID string `json:"actorId"` + ActorType string `json:"actorType"` +} diff --git a/pkg/actors/save_state_request.go b/pkg/actors/save_state_request.go new file mode 100644 index 0000000000000000000000000000000000000000..9ddb2f55be8fb53307129d210f71be318456d76d --- /dev/null +++ b/pkg/actors/save_state_request.go @@ -0,0 +1,7 @@ +package actors + +type SaveStateRequest struct { + ActorID string `json:"actorId"` + ActorType string `json:"actorType"` + Data []byte `json:"data"` +} diff --git a/pkg/actors/state_response.go b/pkg/actors/state_response.go new file mode 100644 index 0000000000000000000000000000000000000000..c79b841b64232f4bd3ad2b4005f2aeb213651ab0 --- /dev/null +++ b/pkg/actors/state_response.go @@ -0,0 +1,5 @@ +package actors + +type StateResponse struct { + Data []byte `json:"data"` +} diff --git a/pkg/apis/eventing/register.go b/pkg/apis/components/register.go similarity index 66% rename from pkg/apis/eventing/register.go rename to pkg/apis/components/register.go index b1b9f5c21389dbbc43e034afcc72b4126975ad9c..c1d614b8f8dfc87d066c07b2c6dc74193a390031 100644 --- a/pkg/apis/eventing/register.go +++ b/pkg/apis/components/register.go @@ -1,4 +1,4 @@ -package eventing +package components const ( GroupName = "actions.io" diff --git a/pkg/apis/eventing/v1alpha1/doc.go b/pkg/apis/components/v1alpha1/doc.go similarity index 56% rename from pkg/apis/eventing/v1alpha1/doc.go rename to pkg/apis/components/v1alpha1/doc.go index dbd7e9b5379616fbeaeb7dc1a73254e7c3cfc20f..39c0c7906900ef75d36da2ad867b2c0f753f0b7c 100644 --- a/pkg/apis/eventing/v1alpha1/doc.go +++ b/pkg/apis/components/v1alpha1/doc.go @@ -1,3 +1,3 @@ // +k8s:deepcopy-gen=package -// +groupName=eventing.actions.io +// +groupName=components.actions.io package v1alpha1 diff --git a/pkg/apis/eventing/v1alpha1/register.go b/pkg/apis/components/v1alpha1/register.go similarity index 84% rename from pkg/apis/eventing/v1alpha1/register.go rename to pkg/apis/components/v1alpha1/register.go index dba3ee940d4cd35c548ad580f0699b5261d5fb20..fa6a81add3a62147aeb2bbb0f46168536afc9f95 100644 --- a/pkg/apis/eventing/v1alpha1/register.go +++ b/pkg/apis/components/v1alpha1/register.go @@ -1,14 +1,14 @@ package v1alpha1 import ( - "github.com/actionscore/actions/pkg/apis/eventing" + "github.com/actionscore/actions/pkg/apis/components" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) // SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: eventing.GroupName, Version: "v1alpha1"} +var SchemeGroupVersion = schema.GroupVersion{Group: components.GroupName, Version: "v1alpha1"} // Kind takes an unqualified kind and returns back a Group qualified GroupKind func Kind(kind string) schema.GroupKind { @@ -29,8 +29,8 @@ var ( func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes( SchemeGroupVersion, - &EventSource{}, - &EventSourceList{}, + &Component{}, + &ComponentList{}, ) scheme.AddKnownTypes(SchemeGroupVersion) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) diff --git a/pkg/apis/components/v1alpha1/types.go b/pkg/apis/components/v1alpha1/types.go new file mode 100644 index 0000000000000000000000000000000000000000..b93c451235cabb486310f0133519fc475ce8dfdc --- /dev/null +++ b/pkg/apis/components/v1alpha1/types.go @@ -0,0 +1,35 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +genclient:noStatus +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Component describes an Actions component type +type Component struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + // +optional + Spec ComponentSpec `json:"spec,omitempty"` +} + +// ComponentSpec is the spec for a component +type ComponentSpec struct { + Type string `json:"type"` + ConnectionInfo map[string]string `json:"connectionInfo"` + Properties map[string]string `json:"properties"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ComponentList is a list of Actions components +type ComponentList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []Component `json:"items"` +} diff --git a/pkg/apis/components/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/components/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 0000000000000000000000000000000000000000..713aaa2fbcca33f53f0792ab6f72a12fbb332321 --- /dev/null +++ b/pkg/apis/components/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,115 @@ +// +build !ignore_autogenerated + +/* +Copyright The Kubernetes Authors. + +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 deepcopy-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Component) DeepCopyInto(out *Component) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Component. +func (in *Component) DeepCopy() *Component { + if in == nil { + return nil + } + out := new(Component) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Component) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ComponentList) DeepCopyInto(out *ComponentList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Component, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentList. +func (in *ComponentList) DeepCopy() *ComponentList { + if in == nil { + return nil + } + out := new(ComponentList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ComponentList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ComponentSpec) DeepCopyInto(out *ComponentSpec) { + *out = *in + if in.ConnectionInfo != nil { + in, out := &in.ConnectionInfo, &out.ConnectionInfo + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Properties != nil { + in, out := &in.Properties, &out.Properties + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentSpec. +func (in *ComponentSpec) DeepCopy() *ComponentSpec { + if in == nil { + return nil + } + out := new(ComponentSpec) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/eventing/v1alpha1/types.go b/pkg/apis/eventing/v1alpha1/types.go deleted file mode 100644 index f46fcc75e0bb75c8407a385bd8775a849000426d..0000000000000000000000000000000000000000 --- a/pkg/apis/eventing/v1alpha1/types.go +++ /dev/null @@ -1,63 +0,0 @@ -package v1alpha1 - -import ( - "time" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// +genclient -// +genclient:noStatus -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// EventSource describes an Action event source -type EventSource struct { - metav1.TypeMeta `json:",inline"` - // +optional - metav1.ObjectMeta `json:"metadata,omitempty"` - // +optional - Spec EventSourceSpec `json:"spec,omitempty"` -} - -// EventSourceSpec is the spec for an EventSource -type EventSourceSpec struct { - Type string `json:"type"` - // +k8s:deepcopy-gen=false - ConnectionInfo map[string]string `json:"connectionInfo"` - SenderOptions SenderOptions `json:"senderOptions,omitempty"` -} - -type SenderOptions struct { - BatchOptions BatchOptions `json:"batchOptions,omitempty"` - DedupOptions DedupOptions `json:"dedupOptions,omitempty"` - NumWorkers int `json:"numWorkers,omitempty"` - SendBufferSize int `json:"sendBufferSize,omitempty"` - QueueName string `json:"queueName,omitempty"` -} - -type BatchOptions struct { - BatchMode string `json:"mode"` - BatchSize int `json:"size,omitempty"` - WindowSize time.Duration `json:"window,omitempty"` -} - -type DedupOptions struct { - DedupMode string `json:"mode"` - DedupWindow time.Duration `json:"window,omitempty"` - DedupField string `json:"field,omitempty"` -} -type OrderOptions struct { - OrderMode string `json:"mode"` - SortWindow time.Duration `json:"window,omitempty"` - SortField string `json:"field,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// EventSourceList is a list of Action event sources -type EventSourceList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []EventSource `json:"items"` -} diff --git a/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go deleted file mode 100644 index 731cee45e600a2bf7e4f4dfa5aee3ff6872f6d9b..0000000000000000000000000000000000000000 --- a/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go +++ /dev/null @@ -1,175 +0,0 @@ -// +build !ignore_autogenerated - -/* -Copyright The Kubernetes Authors. - -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 deepcopy-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *BatchOptions) DeepCopyInto(out *BatchOptions) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BatchOptions. -func (in *BatchOptions) DeepCopy() *BatchOptions { - if in == nil { - return nil - } - out := new(BatchOptions) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DedupOptions) DeepCopyInto(out *DedupOptions) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DedupOptions. -func (in *DedupOptions) DeepCopy() *DedupOptions { - if in == nil { - return nil - } - out := new(DedupOptions) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *EventSource) DeepCopyInto(out *EventSource) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventSource. -func (in *EventSource) DeepCopy() *EventSource { - if in == nil { - return nil - } - out := new(EventSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *EventSource) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *EventSourceList) DeepCopyInto(out *EventSourceList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]EventSource, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventSourceList. -func (in *EventSourceList) DeepCopy() *EventSourceList { - if in == nil { - return nil - } - out := new(EventSourceList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *EventSourceList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *EventSourceSpec) DeepCopyInto(out *EventSourceSpec) { - *out = *in - if in.ConnectionInfo != nil { - in, out := &in.ConnectionInfo, &out.ConnectionInfo - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - out.SenderOptions = in.SenderOptions - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventSourceSpec. -func (in *EventSourceSpec) DeepCopy() *EventSourceSpec { - if in == nil { - return nil - } - out := new(EventSourceSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OrderOptions) DeepCopyInto(out *OrderOptions) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrderOptions. -func (in *OrderOptions) DeepCopy() *OrderOptions { - if in == nil { - return nil - } - out := new(OrderOptions) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SenderOptions) DeepCopyInto(out *SenderOptions) { - *out = *in - out.BatchOptions = in.BatchOptions - out.DedupOptions = in.DedupOptions - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SenderOptions. -func (in *SenderOptions) DeepCopy() *SenderOptions { - if in == nil { - return nil - } - out := new(SenderOptions) - in.DeepCopyInto(out) - return out -} diff --git a/pkg/bindings/bindings.go b/pkg/bindings/bindings.go new file mode 100644 index 0000000000000000000000000000000000000000..8814c5ee79ef0dd25ac6e4329d377d1b2e28b686 --- /dev/null +++ b/pkg/bindings/bindings.go @@ -0,0 +1,37 @@ +package bindings + +import ( + "github.com/actionscore/actions/pkg/bindings/cosmosdb" + "github.com/actionscore/actions/pkg/bindings/dynamodb" + "github.com/actionscore/actions/pkg/bindings/eventhubs" + "github.com/actionscore/actions/pkg/bindings/gcpbucket" + "github.com/actionscore/actions/pkg/bindings/http" + "github.com/actionscore/actions/pkg/bindings/kafka" + "github.com/actionscore/actions/pkg/bindings/mqtt" + "github.com/actionscore/actions/pkg/bindings/rabbitmq" + "github.com/actionscore/actions/pkg/bindings/redis" + "github.com/actionscore/actions/pkg/bindings/sns" + "github.com/actionscore/actions/pkg/bindings/sqs" + "github.com/actionscore/actions/pkg/components/bindings" +) + +// Load input/output bindings +func Load() { + bindings.RegisterInputBinding("aws.sqs", sqs.NewAWSSQS()) + bindings.RegisterOutputBinding("aws.sqs", sqs.NewAWSSQS()) + bindings.RegisterOutputBinding("aws.sns", sns.NewAWSSns()) + bindings.RegisterInputBinding("azure.eventhubs", eventhubs.NewAzureEventHubs()) + bindings.RegisterOutputBinding("azure.eventhubs", eventhubs.NewAzureEventHubs()) + bindings.RegisterOutputBinding("aws.dynamodb", dynamodb.NewDynamoDB()) + bindings.RegisterOutputBinding("azure.cosmosdb", cosmosdb.NewCosmosDB()) + bindings.RegisterOutputBinding("gcp.storage", gcpbucket.NewGCPStorage()) + bindings.RegisterInputBinding("http", http.NewHTTP()) + bindings.RegisterOutputBinding("http", http.NewHTTP()) + bindings.RegisterInputBinding("kafka", kafka.NewKafka()) + bindings.RegisterOutputBinding("kafka", kafka.NewKafka()) + bindings.RegisterInputBinding("mqtt", mqtt.NewMQTT()) + bindings.RegisterOutputBinding("mqtt", mqtt.NewMQTT()) + bindings.RegisterInputBinding("rabbitmq", rabbitmq.NewRabbitMQ()) + bindings.RegisterOutputBinding("rabbitmq", rabbitmq.NewRabbitMQ()) + bindings.RegisterOutputBinding("redis", redis.NewRedis()) +} diff --git a/pkg/action/cosmosdb.go b/pkg/bindings/cosmosdb/cosmosdb.go similarity index 66% rename from pkg/action/cosmosdb.go rename to pkg/bindings/cosmosdb/cosmosdb.go index 7a156c28b86661e1afe9c9887d1fa08d256e2e3e..1b46fc5b685e77f38843cf184ba1c87576430074 100644 --- a/pkg/action/cosmosdb.go +++ b/pkg/bindings/cosmosdb/cosmosdb.go @@ -1,4 +1,4 @@ -package action +package cosmosdb import ( "encoding/json" @@ -6,6 +6,7 @@ import ( _ "github.com/a8m/documentdb" // documentdb go pkg fix documentdb "github.com/a8m/documentdb-go" + "github.com/actionscore/actions/pkg/components/bindings" "github.com/google/uuid" ) @@ -15,12 +16,25 @@ type CosmosDB struct { db *documentdb.Database } +type CosmosDBCredentials struct { + URL string `json:"url"` + MasterKey string `json:"masterKey"` + Database string `json:"database"` + Collection string `json:"collection"` +} + +type CosmosItem struct { + documentdb.Document + ID string `json:"id"` + Value interface{} `json:"value"` +} + func NewCosmosDB() *CosmosDB { return &CosmosDB{} } -func (c *CosmosDB) Init(eventSourceSpec EventSourceSpec) error { - connInfo := eventSourceSpec.ConnectionInfo +func (c *CosmosDB) Init(metadata bindings.Metadata) error { + connInfo := metadata.ConnectionInfo b, err := json.Marshal(connInfo) if err != nil { return err @@ -63,23 +77,21 @@ func (c *CosmosDB) Init(eventSourceSpec EventSourceSpec) error { return nil } -func (c *CosmosDB) Read(metadata interface{}) (interface{}, error) { - return nil, nil -} - -func (c *CosmosDB) ReadAsync(metadata interface{}, callback func([]byte) error) error { - return nil -} +func (c *CosmosDB) Write(req *bindings.WriteRequest) error { + var obj interface{} + err := json.Unmarshal(req.Data, &obj) + if err != nil { + return err + } -func (c *CosmosDB) Write(data interface{}) error { key := uuid.New() i := CosmosItem{ ID: key.String(), - Value: data, + Value: obj, } - _, err := c.client.CreateDocument(c.collection.Self, i, documentdb.PartitionKey(key)) + _, err = c.client.CreateDocument(c.collection.Self, i, documentdb.PartitionKey(key)) if err != nil { return err } diff --git a/pkg/action/aws_dynamodb.go b/pkg/bindings/dynamodb/dynamodb.go similarity index 77% rename from pkg/action/aws_dynamodb.go rename to pkg/bindings/dynamodb/dynamodb.go index 0d2dd6436d0441656d58b43c3edd256818236c5f..5ee497552b2cc1722248c60964feddbefacdffc5 100644 --- a/pkg/action/aws_dynamodb.go +++ b/pkg/bindings/dynamodb/dynamodb.go @@ -1,9 +1,10 @@ -package action +package dynamodb import ( "encoding/json" "os" + "github.com/actionscore/actions/pkg/components/bindings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/dynamodb" @@ -32,8 +33,8 @@ func NewDynamoDB() *DynamoDB { return &DynamoDB{} } -func (d *DynamoDB) Init(eventSourceSpec EventSourceSpec) error { - meta, err := d.GetDynamoDBMetadata(eventSourceSpec) +func (d *DynamoDB) Init(metadata bindings.Metadata) error { + meta, err := d.GetDynamoDBMetadata(metadata) if err != nil { return err } @@ -49,10 +50,16 @@ func (d *DynamoDB) Init(eventSourceSpec EventSourceSpec) error { return nil } -func (d *DynamoDB) Write(data interface{}) error { +func (d *DynamoDB) Write(req *bindings.WriteRequest) error { + var obj interface{} + err := json.Unmarshal(req.Data, &obj) + if err != nil { + return err + } + i := DynamoItem{ ID: uuid.New().String(), - Value: data, + Value: obj, } item, err := dynamodbattribute.MarshalMap(i) @@ -73,11 +80,7 @@ func (d *DynamoDB) Write(data interface{}) error { return nil } -func (d *DynamoDB) Read(metadata interface{}) (interface{}, error) { - return nil, nil -} - -func (d *DynamoDB) GetDynamoDBMetadata(spec EventSourceSpec) (*DynamoDBMetadata, error) { +func (d *DynamoDB) GetDynamoDBMetadata(spec bindings.Metadata) (*DynamoDBMetadata, error) { b, err := json.Marshal(spec.ConnectionInfo) if err != nil { return nil, err @@ -102,7 +105,3 @@ func (d *DynamoDB) getClient(awsMeta *DynamoDBMetadata) (*dynamodb.DynamoDB, err return c, nil } - -func (d *DynamoDB) ReadAsync(metadata interface{}, callback func([]byte) error) error { - return nil -} diff --git a/pkg/action/azure_event_hubs.go b/pkg/bindings/eventhubs/eventhubs.go similarity index 75% rename from pkg/action/azure_event_hubs.go rename to pkg/bindings/eventhubs/eventhubs.go index 0d9a839c12aa4c6fdc8e376ba1ea2ef8f7e56ce9..caf7101ac8ff3c1e654ef6e93e57ae5214e56fdf 100644 --- a/pkg/action/azure_event_hubs.go +++ b/pkg/bindings/eventhubs/eventhubs.go @@ -1,4 +1,4 @@ -package action +package eventhubs import ( "bytes" @@ -12,10 +12,11 @@ import ( eventhub "github.com/Azure/azure-event-hubs-go" log "github.com/Sirupsen/logrus" + "github.com/actionscore/actions/pkg/components/bindings" ) type AzureEventHubs struct { - Spec EventSourceSpec + Spec bindings.Metadata } type AzureEventHubsMetadata struct { @@ -26,8 +27,8 @@ func NewAzureEventHubs() *AzureEventHubs { return &AzureEventHubs{} } -func (a *AzureEventHubs) Init(eventSourceSpec EventSourceSpec) error { - a.Spec = eventSourceSpec +func (a *AzureEventHubs) Init(metadata bindings.Metadata) error { + a.Spec = metadata return nil } @@ -41,7 +42,7 @@ func GetBytes(key interface{}) ([]byte, error) { return buf.Bytes(), nil } -func (a *AzureEventHubs) Write(data interface{}) error { +func (a *AzureEventHubs) Write(req *bindings.WriteRequest) error { http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} b, err := json.Marshal(a.Spec.ConnectionInfo) @@ -62,13 +63,8 @@ func (a *AzureEventHubs) Write(data interface{}) error { return err } - dataBytes, err := json.Marshal(data) - if err != nil { - return err - } - err = hub.Send(context.Background(), &eventhub.Event{ - Data: dataBytes, + Data: req.Data, }) if err != nil { return err @@ -78,11 +74,7 @@ func (a *AzureEventHubs) Write(data interface{}) error { return nil } -func (a *AzureEventHubs) Read(metadata interface{}) (interface{}, error) { - return nil, nil -} - -func (a *AzureEventHubs) ReadAsync(metadata interface{}, callback func([]byte) error) error { +func (a *AzureEventHubs) Read(handler func(*bindings.ReadResponse) error) error { http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} b, err := json.Marshal(a.Spec.ConnectionInfo) @@ -103,8 +95,14 @@ func (a *AzureEventHubs) ReadAsync(metadata interface{}, callback func([]byte) e return err } - handler := func(c context.Context, event *eventhub.Event) error { - return callback(event.Data) + callback := func(c context.Context, event *eventhub.Event) error { + if event != nil { + handler(&bindings.ReadResponse{ + Data: event.Data, + }) + } + + return nil } ctx := context.Background() @@ -114,7 +112,7 @@ func (a *AzureEventHubs) ReadAsync(metadata interface{}, callback func([]byte) e } for _, partitionID := range runtimeInfo.PartitionIDs { - _, err := hub.Receive(ctx, partitionID, handler, eventhub.ReceiveWithLatestOffset()) + _, err := hub.Receive(ctx, partitionID, callback, eventhub.ReceiveWithLatestOffset()) if err != nil { return err } diff --git a/pkg/action/gcp_storage.go b/pkg/bindings/gcpbucket/bucket.go similarity index 73% rename from pkg/action/gcp_storage.go rename to pkg/bindings/gcpbucket/bucket.go index bb9866461f4513dd3d98d5176d3a6a69dcee1952..5facbeb422727dcc29f3a69fe59ad6b9cebbfad7 100644 --- a/pkg/action/gcp_storage.go +++ b/pkg/bindings/gcpbucket/bucket.go @@ -1,4 +1,4 @@ -package action +package gcpbucket import ( "context" @@ -8,12 +8,13 @@ import ( "cloud.google.com/go/storage" log "github.com/Sirupsen/logrus" + "github.com/actionscore/actions/pkg/components/bindings" uuid "github.com/satori/go.uuid" "google.golang.org/api/option" ) type GCPStorage struct { - Spec EventSourceSpec + Spec bindings.Metadata } type GCPMetadata struct { @@ -34,20 +35,12 @@ func NewGCPStorage() *GCPStorage { return &GCPStorage{} } -func (g *GCPStorage) Init(eventSourceSpec EventSourceSpec) error { - g.Spec = eventSourceSpec +func (g *GCPStorage) Init(metadata bindings.Metadata) error { + g.Spec = metadata return nil } -func (g *GCPStorage) Read(metadata interface{}) (interface{}, error) { - return nil, nil -} - -func (g *GCPStorage) ReadAsync(metadata interface{}, callback func([]byte) error) error { - return nil -} - -func (g *GCPStorage) Write(data interface{}) error { +func (g *GCPStorage) Write(req *bindings.WriteRequest) error { http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} b, err := json.Marshal(g.Spec.ConnectionInfo) @@ -72,12 +65,7 @@ func (g *GCPStorage) Write(data interface{}) error { name := uuid.NewV4() wc := client.Bucket(metadata.Bucket).Object(name.String()).NewWriter(ctx) - dataBytes, err := json.Marshal(data) - if err != nil { - return err - } - - if _, err := wc.Write(dataBytes); err != nil { + if _, err := wc.Write(req.Data); err != nil { return err } @@ -86,7 +74,5 @@ func (g *GCPStorage) Write(data interface{}) error { return err } - log.Info("Written data to GCP Storage successfully") - return nil } diff --git a/pkg/action/http.go b/pkg/bindings/http/http.go similarity index 61% rename from pkg/action/http.go rename to pkg/bindings/http/http.go index 2f24d6b20efd9adcf76f973a2b68f86cc40e5f76..6ed8701bd76b29b24ed7919efd25dd7c00862dc0 100644 --- a/pkg/action/http.go +++ b/pkg/bindings/http/http.go @@ -1,4 +1,4 @@ -package action +package http import ( "bytes" @@ -6,6 +6,8 @@ import ( "io/ioutil" "net/http" "time" + + "github.com/actionscore/actions/pkg/components/bindings" ) type HttpSource struct { @@ -17,12 +19,12 @@ type HttpMetadata struct { Method string `json:"method"` } -func NewHttpSource() *HttpSource { +func NewHTTP() *HttpSource { return &HttpSource{} } -func (h *HttpSource) Init(eventSourceSpec EventSourceSpec) error { - b, err := json.Marshal(eventSourceSpec.ConnectionInfo) +func (h *HttpSource) Init(metadata bindings.Metadata) error { + b, err := json.Marshal(metadata.ConnectionInfo) if err != nil { return err } @@ -57,36 +59,22 @@ func (h *HttpSource) HttpGet(url string) ([]byte, error) { return b, nil } -func (h *HttpSource) Read(metadata interface{}) (interface{}, error) { - var data interface{} +func (h *HttpSource) Read(handler func(*bindings.ReadResponse) error) error { b, err := h.HttpGet(h.Spec.URL) - if err != nil { - return nil, err - } - - err = json.Unmarshal(b, &data) - if err != nil { - return nil, err - } - - return data, nil -} - -func (h *HttpSource) ReadAsync(metadata interface{}, callback func([]byte) error) error { - data, err := h.HttpGet(h.Spec.URL) if err != nil { return err } - return callback(data) -} + handler(&bindings.ReadResponse{ + Data: b, + }) -func (h *HttpSource) Write(data interface{}) error { - b := new(bytes.Buffer) - json.NewEncoder(b).Encode(data) + return nil +} +func (h *HttpSource) Write(req *bindings.WriteRequest) error { client := http.Client{Timeout: time.Second * 5} - resp, err := client.Post(h.Spec.URL, "application/json; charset=utf-8", b) + resp, err := client.Post(h.Spec.URL, "application/json; charset=utf-8", bytes.NewBuffer(req.Data)) if err != nil { return err } diff --git a/pkg/action/kafka.go b/pkg/bindings/kafka/kafka.go similarity index 77% rename from pkg/action/kafka.go rename to pkg/bindings/kafka/kafka.go index f7c4a481235c9569678de8a6eb75eac04bbb06f2..c9492fcc5496ad178b51582c510038cc1dde1734 100644 --- a/pkg/action/kafka.go +++ b/pkg/bindings/kafka/kafka.go @@ -1,14 +1,14 @@ -package action +package kafka import ( "context" "encoding/json" - "fmt" "os" "os/signal" "syscall" "github.com/Shopify/sarama" + "github.com/actionscore/actions/pkg/components/bindings" ) type Kafka struct { @@ -28,19 +28,19 @@ type KafkaMetadata struct { type consumer struct { ready chan bool - callback func([]byte) error -} - -func (consumer *consumer) Cleanup(sarama.ConsumerGroupSession) error { - return nil + callback func(*bindings.ReadResponse) error } func (consumer *consumer) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error { for message := range claim.Messages() { if consumer.callback != nil { - consumer.callback(message.Value) + err := consumer.callback(&bindings.ReadResponse{ + Data: message.Value, + }) + if err == nil { + session.MarkMessage(message, "") + } } - session.MarkMessage(message, "") } return nil @@ -55,8 +55,8 @@ func NewKafka() *Kafka { return &Kafka{} } -func (k *Kafka) Init(eventSourceSpec EventSourceSpec) error { - meta, err := k.GetKafkaMetadata(eventSourceSpec) +func (k *Kafka) Init(metadata bindings.Metadata) error { + meta, err := k.GetKafkaMetadata(metadata) if err != nil { return err } @@ -74,10 +74,10 @@ func (k *Kafka) Init(eventSourceSpec EventSourceSpec) error { return nil } -func (k *Kafka) Write(data interface{}) error { +func (k *Kafka) Write(req *bindings.WriteRequest) error { _, _, err := k.producer.SendMessage(&sarama.ProducerMessage{ Topic: k.publishTopic, - Value: sarama.StringEncoder(fmt.Sprintf("%v", data)), + Value: sarama.ByteEncoder(req.Data), }) if err != nil { return err @@ -86,12 +86,8 @@ func (k *Kafka) Write(data interface{}) error { return nil } -func (k *Kafka) Read(metadata interface{}) (interface{}, error) { - return nil, nil -} - -func (k *Kafka) GetKafkaMetadata(spec EventSourceSpec) (*KafkaMetadata, error) { - b, err := json.Marshal(spec.ConnectionInfo) +func (k *Kafka) GetKafkaMetadata(metadata bindings.Metadata) (*KafkaMetadata, error) { + b, err := json.Marshal(metadata.ConnectionInfo) if err != nil { return nil, err } @@ -119,12 +115,12 @@ func (k *Kafka) getSyncProducer(meta *KafkaMetadata) (sarama.SyncProducer, error return producer, nil } -func (k *Kafka) ReadAsync(metadata interface{}, callback func([]byte) error) error { +func (k *Kafka) Read(handler func(*bindings.ReadResponse) error) error { config := sarama.NewConfig() config.Version = sarama.V1_0_0_0 consumer := consumer{ - callback: callback, + callback: handler, } ctx := context.Background() @@ -153,3 +149,7 @@ func (k *Kafka) ReadAsync(metadata interface{}, callback func([]byte) error) err return nil } + +func (consumer *consumer) Cleanup(sarama.ConsumerGroupSession) error { + return nil +} diff --git a/pkg/action/mqtt.go b/pkg/bindings/mqtt/mqtt.go similarity index 76% rename from pkg/action/mqtt.go rename to pkg/bindings/mqtt/mqtt.go index ad916d5932d69af41d0d4d005f8811334c9d89b4..33a01903a2792ad0814a74fa3a149aede6fcfdcd 100644 --- a/pkg/action/mqtt.go +++ b/pkg/bindings/mqtt/mqtt.go @@ -1,4 +1,4 @@ -package action +package mqtt import ( "crypto/tls" @@ -12,6 +12,8 @@ import ( "syscall" "time" + "github.com/actionscore/actions/pkg/components/bindings" + mqtt "github.com/eclipse/paho.mqtt.golang" ) @@ -29,15 +31,15 @@ func NewMQTT() *MQTT { return &MQTT{} } -func (m *MQTT) Init(eventSourceSpec EventSourceSpec) error { +func (m *MQTT) Init(metadata bindings.Metadata) error { http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - metadata, err := m.GetMQTTMetadata(eventSourceSpec) + mqttMeta, err := m.GetMQTTMetadata(metadata) if err != nil { return err } - m.Metadata = metadata + m.Metadata = mqttMeta if m.Metadata.URL == "" { return errors.New("MQTT Error: URL required") @@ -50,8 +52,8 @@ func (m *MQTT) Init(eventSourceSpec EventSourceSpec) error { return nil } -func (m *MQTT) GetMQTTMetadata(eventSourceSpec EventSourceSpec) (*MQTTMetadata, error) { - b, err := json.Marshal(eventSourceSpec.ConnectionInfo) +func (m *MQTT) GetMQTTMetadata(metadata bindings.Metadata) (*MQTTMetadata, error) { + b, err := json.Marshal(metadata.ConnectionInfo) if err != nil { return nil, err } @@ -65,7 +67,7 @@ func (m *MQTT) GetMQTTMetadata(eventSourceSpec EventSourceSpec) (*MQTTMetadata, return &mqttMetadata, nil } -func (m *MQTT) Write(data interface{}) error { +func (m *MQTT) Write(req *bindings.WriteRequest) error { uri, err := url.Parse(m.Metadata.URL) if err != nil { return err @@ -76,17 +78,13 @@ func (m *MQTT) Write(data interface{}) error { return err } - client.Publish(m.Metadata.Topic, 0, false, fmt.Sprintf("%s", data)) + client.Publish(m.Metadata.Topic, 0, false, string(req.Data)) client.Disconnect(0) return nil } -func (m *MQTT) Read(metadata interface{}) (interface{}, error) { - return nil, nil -} - -func (m *MQTT) ReadAsync(metadata interface{}, callback func([]byte) error) error { +func (m *MQTT) Read(handler func(*bindings.ReadResponse) error) error { uri, err := url.Parse(m.Metadata.URL) if err != nil { return err @@ -102,7 +100,9 @@ func (m *MQTT) ReadAsync(metadata interface{}, callback func([]byte) error) erro client.Subscribe(m.Metadata.Topic, 0, func(client mqtt.Client, msg mqtt.Message) { if len(msg.Payload()) > 0 { - callback(msg.Payload()) + handler(&bindings.ReadResponse{ + Data: msg.Payload(), + }) } }) diff --git a/pkg/action/rabbitmq.go b/pkg/bindings/rabbitmq/rabbitmq.go similarity index 65% rename from pkg/action/rabbitmq.go rename to pkg/bindings/rabbitmq/rabbitmq.go index 66ed561d386ae9b73597b16b5a1c8108f506c3b1..9344062938a014cb2d6e437996ba92c16e1afac3 100644 --- a/pkg/action/rabbitmq.go +++ b/pkg/bindings/rabbitmq/rabbitmq.go @@ -1,8 +1,9 @@ -package action +package rabbitmq import ( "encoding/json" + "github.com/actionscore/actions/pkg/components/bindings" "github.com/streadway/amqp" ) @@ -23,8 +24,8 @@ func NewRabbitMQ() *RabbitMQ { return &RabbitMQ{} } -func (r *RabbitMQ) Init(eventSourceSpec EventSourceSpec) error { - meta, err := r.GetRabbitMQMetadata(eventSourceSpec) +func (r *RabbitMQ) Init(metadata bindings.Metadata) error { + meta, err := r.GetRabbitMQMetadata(metadata) if err != nil { return err } @@ -47,20 +48,15 @@ func (r *RabbitMQ) Init(eventSourceSpec EventSourceSpec) error { return nil } -func (r *RabbitMQ) Write(data interface{}) error { +func (r *RabbitMQ) Write(req *bindings.WriteRequest) error { q, err := r.channel.QueueDeclare(r.metadata.QueueName, r.metadata.Durable, r.metadata.DeleteWhenUnused, false, false, nil) if err != nil { return err } - b, err := json.Marshal(data) - if err != nil { - return err - } - err = r.channel.Publish("", q.Name, false, false, amqp.Publishing{ ContentType: "text/plain", - Body: b, + Body: req.Data, }) if err != nil { return err @@ -69,26 +65,22 @@ func (r *RabbitMQ) Write(data interface{}) error { return nil } -func (r *RabbitMQ) Read(metadata interface{}) (interface{}, error) { - return nil, nil -} - -func (r *RabbitMQ) GetRabbitMQMetadata(eventSourceSpec EventSourceSpec) (*RabbitMQMetadata, error) { - b, err := json.Marshal(eventSourceSpec.ConnectionInfo) +func (r *RabbitMQ) GetRabbitMQMetadata(metadata bindings.Metadata) (*RabbitMQMetadata, error) { + b, err := json.Marshal(metadata.ConnectionInfo) if err != nil { return nil, err } - var metadata RabbitMQMetadata - err = json.Unmarshal(b, &metadata) + var rabbitMQMeta RabbitMQMetadata + err = json.Unmarshal(b, &rabbitMQMeta) if err != nil { return nil, err } - return &metadata, nil + return &rabbitMQMeta, nil } -func (r *RabbitMQ) ReadAsync(metadata interface{}, callback func([]byte) error) error { +func (r *RabbitMQ) Read(handler func(*bindings.ReadResponse) error) error { q, err := r.channel.QueueDeclare(r.metadata.QueueName, r.metadata.Durable, r.metadata.DeleteWhenUnused, false, false, nil) if err != nil { return err @@ -111,7 +103,14 @@ func (r *RabbitMQ) ReadAsync(metadata interface{}, callback func([]byte) error) go func() { for d := range msgs { - callback(d.Body) + if len(d.Body) > 0 { + err := handler(&bindings.ReadResponse{ + Data: d.Body, + }) + if err == nil { + r.channel.Ack(d.DeliveryTag, false) + } + } } }() diff --git a/pkg/action/redis.go b/pkg/bindings/redis/redis.go similarity index 64% rename from pkg/action/redis.go rename to pkg/bindings/redis/redis.go index fd9033b62dbb146442289d6961b67134f8864ca8..d6aa6586959b4b3310b4d60da640b35e18b1c247 100644 --- a/pkg/action/redis.go +++ b/pkg/bindings/redis/redis.go @@ -1,10 +1,12 @@ -package action +package redis import ( "context" "encoding/json" "fmt" + "github.com/actionscore/actions/pkg/components/bindings" + "github.com/google/uuid" "github.com/joomcode/redispipe/redis" "github.com/joomcode/redispipe/redisconn" @@ -14,12 +16,17 @@ type Redis struct { client *redis.SyncCtx } +type RedisCredentials struct { + Host string `json:"redisHost"` + Password string `json:"redisPassword"` +} + func NewRedis() *Redis { return &Redis{} } -func (r *Redis) Init(eventSourceSpec EventSourceSpec) error { - connInfo := eventSourceSpec.ConnectionInfo +func (r *Redis) Init(metadata bindings.Metadata) error { + connInfo := metadata.ConnectionInfo b, err := json.Marshal(connInfo) if err != nil { return err @@ -48,22 +55,12 @@ func (r *Redis) Init(eventSourceSpec EventSourceSpec) error { return nil } -func (r *Redis) Write(data interface{}) error { +func (r *Redis) Write(req *bindings.WriteRequest) error { key := fmt.Sprintf("es_%s", uuid.New().String()) - value := fmt.Sprintf("%v", data) - - res := r.client.Do(context.Background(), "SET", key, value) + res := r.client.Do(context.Background(), "SET", key, req.Data) if err := redis.AsError(res); err != nil { return err } return nil } - -func (r *Redis) Read(metadata interface{}) (interface{}, error) { - return nil, nil -} - -func (r *Redis) ReadAsync(metadata interface{}, callback func([]byte) error) error { - return nil -} diff --git a/pkg/action/aws_sns.go b/pkg/bindings/sns/sns.go similarity index 72% rename from pkg/action/aws_sns.go rename to pkg/bindings/sns/sns.go index 287d6a0faa1513e1f3ddc4599e97052e8590c7c7..252a15df3dc3c5c5484a5938a221c27bb594d030 100644 --- a/pkg/action/aws_sns.go +++ b/pkg/bindings/sns/sns.go @@ -1,16 +1,17 @@ -package action +package sns import ( "encoding/json" "fmt" "os" + "github.com/actionscore/actions/pkg/components/bindings" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/sns" ) type AWSSns struct { - Spec EventSourceSpec + Spec bindings.Metadata } type AWSSnsMetadata struct { @@ -29,12 +30,12 @@ func NewAWSSns() *AWSSns { return &AWSSns{} } -func (a *AWSSns) Init(eventSourceSpec EventSourceSpec) error { - a.Spec = eventSourceSpec +func (a *AWSSns) Init(metadata bindings.Metadata) error { + a.Spec = metadata return nil } -func (a *AWSSns) Write(data interface{}) error { +func (a *AWSSns) Write(req *bindings.WriteRequest) error { b, err := json.Marshal(a.Spec.ConnectionInfo) if err != nil { return err @@ -53,13 +54,8 @@ func (a *AWSSns) Write(data interface{}) error { s := session.Must(session.NewSession()) c := sns.New(s) - b, err = json.Marshal(data) - if err != nil { - return err - } - var payload SNSDataPayload - err = json.Unmarshal(b, &payload) + err = json.Unmarshal(req.Data, &payload) if err != nil { return err } @@ -80,11 +76,3 @@ func (a *AWSSns) Write(data interface{}) error { return nil } - -func (a *AWSSns) Read(metadata interface{}) (interface{}, error) { - return nil, nil -} - -func (a *AWSSns) ReadAsync(metadata interface{}, callback func([]byte) error) error { - return nil -} diff --git a/pkg/action/aws_sqs.go b/pkg/bindings/sqs/sqs.go similarity index 80% rename from pkg/action/aws_sqs.go rename to pkg/bindings/sqs/sqs.go index e71b6b020f80f5613bfdee0bb70634eff513809e..3403af9980c1011db87466e29b0c079154311059 100644 --- a/pkg/action/aws_sqs.go +++ b/pkg/bindings/sqs/sqs.go @@ -1,4 +1,4 @@ -package action +package sqs import ( "crypto/tls" @@ -8,13 +8,14 @@ import ( "time" log "github.com/Sirupsen/logrus" + "github.com/actionscore/actions/pkg/components/bindings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/sqs" ) type AWSSQS struct { - Spec EventSourceSpec + Spec bindings.Metadata Client *sqs.SQS QueueURL *string } @@ -35,10 +36,9 @@ func NewAWSSQS() *AWSSQS { return &AWSSQS{} } -func (a *AWSSQS) Init(eventSourceSpec EventSourceSpec) error { +func (a *AWSSQS) Init(metadata bindings.Metadata) error { http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - - a.Spec = eventSourceSpec + a.Spec = metadata awsMeta, err := a.GetAWSMetadata() if err != nil { @@ -65,60 +65,18 @@ func (a *AWSSQS) Init(eventSourceSpec EventSourceSpec) error { return nil } -func (a *AWSSQS) Write(data interface{}) error { +func (a *AWSSQS) Write(req *bindings.WriteRequest) error { http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - dataBytes, err := json.Marshal(data) - if err != nil { - return err - } - - msgBody := string(dataBytes) - - _, err = a.Client.SendMessage(&sqs.SendMessageInput{ + msgBody := string(req.Data) + _, err := a.Client.SendMessage(&sqs.SendMessageInput{ MessageBody: &msgBody, QueueUrl: a.QueueURL, }) - if err != nil { - return err - } - - log.Info("SQS event sent successfully") - - return nil -} - -func (a *AWSSQS) Read(metadata interface{}) (interface{}, error) { - return nil, nil -} - -func (a *AWSSQS) GetAWSMetadata() (*AWSSQSMetadata, error) { - b, err := json.Marshal(a.Spec.ConnectionInfo) - if err != nil { - return nil, err - } - - var awsMeta AWSSQSMetadata - err = json.Unmarshal(b, &awsMeta) - if err != nil { - return nil, err - } - - return &awsMeta, nil + return err } -func (a *AWSSQS) getClient(awsMeta *AWSSQSMetadata) (*sqs.SQS, error) { - os.Setenv("AWS_ACCESS_KEY_ID", awsMeta.AccessKey) - os.Setenv("AWS_SECRET_ACCESS_KEY", awsMeta.SecretKey) - os.Setenv("AWS_REGION", awsMeta.Region) - - s := session.Must(session.NewSession()) - c := sqs.New(s) - - return c, nil -} - -func (a *AWSSQS) ReadAsync(metadata interface{}, callback func([]byte) error) error { +func (a *AWSSQS) Read(handler func(*bindings.ReadResponse) error) error { http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} for { @@ -140,7 +98,10 @@ func (a *AWSSQS) ReadAsync(metadata interface{}, callback func([]byte) error) er if len(result.Messages) > 0 { for _, m := range result.Messages { body := m.Body - err := callback([]byte(*body)) + res := bindings.ReadResponse{ + Data: []byte(*body), + } + err := handler(&res) if err == nil { msgHandle := m.ReceiptHandle @@ -155,3 +116,29 @@ func (a *AWSSQS) ReadAsync(metadata interface{}, callback func([]byte) error) er time.Sleep(time.Millisecond * 50) } } + +func (a *AWSSQS) GetAWSMetadata() (*AWSSQSMetadata, error) { + b, err := json.Marshal(a.Spec.ConnectionInfo) + if err != nil { + return nil, err + } + + var awsMeta AWSSQSMetadata + err = json.Unmarshal(b, &awsMeta) + if err != nil { + return nil, err + } + + return &awsMeta, nil +} + +func (a *AWSSQS) getClient(awsMeta *AWSSQSMetadata) (*sqs.SQS, error) { + os.Setenv("AWS_ACCESS_KEY_ID", awsMeta.AccessKey) + os.Setenv("AWS_SECRET_ACCESS_KEY", awsMeta.SecretKey) + os.Setenv("AWS_REGION", awsMeta.Region) + + s := session.Must(session.NewSession()) + c := sqs.New(s) + + return c, nil +} diff --git a/pkg/channel/channel.go b/pkg/channel/channel.go new file mode 100644 index 0000000000000000000000000000000000000000..a261c9395f46a902697d3dc3b469ab7daaca9994 --- /dev/null +++ b/pkg/channel/channel.go @@ -0,0 +1,5 @@ +package channel + +type AppChannel interface { + InvokeMethod(req *InvokeRequest) (*InvokeResponse, error) +} diff --git a/pkg/channel/grpc/grpc_channel.go b/pkg/channel/grpc/grpc_channel.go new file mode 100644 index 0000000000000000000000000000000000000000..e8058eff847b2bc8295c8af2233d87e7ef22d2f5 --- /dev/null +++ b/pkg/channel/grpc/grpc_channel.go @@ -0,0 +1,46 @@ +package grpc + +import ( + "context" + "fmt" + "time" + + "github.com/golang/protobuf/ptypes/any" + "google.golang.org/grpc" + + "github.com/actionscore/actions/pkg/channel" + pb "github.com/actionscore/actions/pkg/proto" +) + +type GRPCChannel struct { + client *grpc.ClientConn + baseAddress string +} + +func CreateLocalChannel(port int, conn *grpc.ClientConn) *GRPCChannel { + return &GRPCChannel{ + client: conn, + baseAddress: fmt.Sprintf("127.0.0.1:%v", port), + } +} + +func (g *GRPCChannel) InvokeMethod(req *channel.InvokeRequest) (*channel.InvokeResponse, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*1) + defer cancel() + + c := pb.NewAppClient(g.client) + msg := pb.AppMethodCallEnvelope{ + Data: &any.Any{Value: req.Payload}, + Method: req.Method, + } + + resp, err := c.OnMethodCall(ctx, &msg) + if err != nil { + return nil, err + } + + return &channel.InvokeResponse{ + Data: resp.Value, + Metadata: map[string]string{}, + }, nil +} diff --git a/pkg/channel/http/http_channel.go b/pkg/channel/http/http_channel.go new file mode 100644 index 0000000000000000000000000000000000000000..4a3a4980aa56ecada8c3e84bd4fcd809ab29e8a5 --- /dev/null +++ b/pkg/channel/http/http_channel.go @@ -0,0 +1,65 @@ +package http + +import ( + "crypto/tls" + "fmt" + "time" + + "github.com/actionscore/actions/pkg/channel" + "github.com/valyala/fasthttp" +) + +const ( + HTTPVerb = "http.verb" + HTTPStatusCode = "http.status_code" + Get = "GET" + Post = "POST" + Delete = "DELETE" + Put = "PUT" + Options = "OPTIONS" +) + +type HTTPChannel struct { + client *fasthttp.Client + baseAddress string +} + +func (h *HTTPChannel) InvokeMethod(invokeRequest *channel.InvokeRequest) (*channel.InvokeResponse, error) { + req := fasthttp.AcquireRequest() + uri := fmt.Sprintf("%s/%s", h.baseAddress, invokeRequest.Method) + req.SetRequestURI(uri) + req.SetBody(invokeRequest.Payload) + req.Header.SetContentType("application/json") + + method := invokeRequest.Metadata[HTTPVerb] + if method == "" { + method = Post + } + req.Header.SetMethod(method) + + resp := fasthttp.AcquireResponse() + err := h.client.Do(req, resp) + if err != nil { + return nil, err + } + + body := resp.Body() + arr := make([]byte, len(body)) + copy(arr, body) + + statusCode := resp.StatusCode() + fasthttp.ReleaseRequest(req) + fasthttp.ReleaseResponse(resp) + + return &channel.InvokeResponse{ + Metadata: map[string]string{HTTPStatusCode: fmt.Sprintf("%v", statusCode)}, + Data: arr, + }, nil +} + +func CreateLocalChannel(port int) (channel.AppChannel, error) { + return &HTTPChannel{ + client: &fasthttp.Client{MaxConnsPerHost: 1000000, TLSConfig: &tls.Config{InsecureSkipVerify: true}, ReadTimeout: time.Second * 60}, + baseAddress: fmt.Sprintf("http://127.0.0.1:%v", port), + }, nil +} diff --git a/pkg/channel/request.go b/pkg/channel/request.go new file mode 100644 index 0000000000000000000000000000000000000000..f57df10cdadf510b0bf7dd341c81ebdb2f905e90 --- /dev/null +++ b/pkg/channel/request.go @@ -0,0 +1,7 @@ +package channel + +type InvokeRequest struct { + Method string `json:"method"` + Payload []byte `json:"payload"` + Metadata map[string]string `json:"metadata"` +} diff --git a/pkg/channel/response.go b/pkg/channel/response.go new file mode 100644 index 0000000000000000000000000000000000000000..66aa750a79ccae18d2b9a80bc76245fae48ffa1b --- /dev/null +++ b/pkg/channel/response.go @@ -0,0 +1,6 @@ +package channel + +type InvokeResponse struct { + Data []byte `json:"data"` + Metadata map[string]string `json:"metadata"` +} diff --git a/pkg/client/clientset/versioned/clientset.go b/pkg/client/clientset/versioned/clientset.go index 679bec3d4c77ed8b79bfad4f0565b594bb5081c1..156f9eb4ff808a9b8237df9bf34d0e4633efc58b 100644 --- a/pkg/client/clientset/versioned/clientset.go +++ b/pkg/client/clientset/versioned/clientset.go @@ -19,8 +19,10 @@ limitations under the License. package versioned import ( + "fmt" + + componentsv1alpha1 "github.com/actionscore/actions/pkg/client/clientset/versioned/typed/components/v1alpha1" configurationv1alpha1 "github.com/actionscore/actions/pkg/client/clientset/versioned/typed/configuration/v1alpha1" - eventingv1alpha1 "github.com/actionscore/actions/pkg/client/clientset/versioned/typed/eventing/v1alpha1" discovery "k8s.io/client-go/discovery" rest "k8s.io/client-go/rest" flowcontrol "k8s.io/client-go/util/flowcontrol" @@ -28,16 +30,21 @@ import ( type Interface interface { Discovery() discovery.DiscoveryInterface + ComponentsV1alpha1() componentsv1alpha1.ComponentsV1alpha1Interface ConfigurationV1alpha1() configurationv1alpha1.ConfigurationV1alpha1Interface - EventingV1alpha1() eventingv1alpha1.EventingV1alpha1Interface } // Clientset contains the clients for groups. Each group has exactly one // version included in a Clientset. type Clientset struct { *discovery.DiscoveryClient + componentsV1alpha1 *componentsv1alpha1.ComponentsV1alpha1Client configurationV1alpha1 *configurationv1alpha1.ConfigurationV1alpha1Client - eventingV1alpha1 *eventingv1alpha1.EventingV1alpha1Client +} + +// ComponentsV1alpha1 retrieves the ComponentsV1alpha1Client +func (c *Clientset) ComponentsV1alpha1() componentsv1alpha1.ComponentsV1alpha1Interface { + return c.componentsV1alpha1 } // ConfigurationV1alpha1 retrieves the ConfigurationV1alpha1Client @@ -45,11 +52,6 @@ func (c *Clientset) ConfigurationV1alpha1() configurationv1alpha1.ConfigurationV return c.configurationV1alpha1 } -// EventingV1alpha1 retrieves the EventingV1alpha1Client -func (c *Clientset) EventingV1alpha1() eventingv1alpha1.EventingV1alpha1Interface { - return c.eventingV1alpha1 -} - // Discovery retrieves the DiscoveryClient func (c *Clientset) Discovery() discovery.DiscoveryInterface { if c == nil { @@ -59,18 +61,23 @@ func (c *Clientset) Discovery() discovery.DiscoveryInterface { } // NewForConfig creates a new Clientset for the given config. +// If config's RateLimiter is not set and QPS and Burst are acceptable, +// NewForConfig will generate a rate-limiter in configShallowCopy. func NewForConfig(c *rest.Config) (*Clientset, error) { configShallowCopy := *c if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { + if configShallowCopy.Burst <= 0 { + return nil, fmt.Errorf("Burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") + } configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) } var cs Clientset var err error - cs.configurationV1alpha1, err = configurationv1alpha1.NewForConfig(&configShallowCopy) + cs.componentsV1alpha1, err = componentsv1alpha1.NewForConfig(&configShallowCopy) if err != nil { return nil, err } - cs.eventingV1alpha1, err = eventingv1alpha1.NewForConfig(&configShallowCopy) + cs.configurationV1alpha1, err = configurationv1alpha1.NewForConfig(&configShallowCopy) if err != nil { return nil, err } @@ -86,8 +93,8 @@ func NewForConfig(c *rest.Config) (*Clientset, error) { // panics if there is an error in the config. func NewForConfigOrDie(c *rest.Config) *Clientset { var cs Clientset + cs.componentsV1alpha1 = componentsv1alpha1.NewForConfigOrDie(c) cs.configurationV1alpha1 = configurationv1alpha1.NewForConfigOrDie(c) - cs.eventingV1alpha1 = eventingv1alpha1.NewForConfigOrDie(c) cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c) return &cs @@ -96,8 +103,8 @@ func NewForConfigOrDie(c *rest.Config) *Clientset { // New creates a new Clientset for the given RESTClient. func New(c rest.Interface) *Clientset { var cs Clientset + cs.componentsV1alpha1 = componentsv1alpha1.New(c) cs.configurationV1alpha1 = configurationv1alpha1.New(c) - cs.eventingV1alpha1 = eventingv1alpha1.New(c) cs.DiscoveryClient = discovery.NewDiscoveryClient(c) return &cs diff --git a/pkg/client/clientset/versioned/fake/clientset_generated.go b/pkg/client/clientset/versioned/fake/clientset_generated.go index c228a01a178f639a6bf1a6164309b7a9d92cc682..e7740ef812859d93d5a3c7f64766dd19788deb67 100644 --- a/pkg/client/clientset/versioned/fake/clientset_generated.go +++ b/pkg/client/clientset/versioned/fake/clientset_generated.go @@ -20,10 +20,10 @@ package fake import ( clientset "github.com/actionscore/actions/pkg/client/clientset/versioned" + componentsv1alpha1 "github.com/actionscore/actions/pkg/client/clientset/versioned/typed/components/v1alpha1" + fakecomponentsv1alpha1 "github.com/actionscore/actions/pkg/client/clientset/versioned/typed/components/v1alpha1/fake" configurationv1alpha1 "github.com/actionscore/actions/pkg/client/clientset/versioned/typed/configuration/v1alpha1" fakeconfigurationv1alpha1 "github.com/actionscore/actions/pkg/client/clientset/versioned/typed/configuration/v1alpha1/fake" - eventingv1alpha1 "github.com/actionscore/actions/pkg/client/clientset/versioned/typed/eventing/v1alpha1" - fakeeventingv1alpha1 "github.com/actionscore/actions/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/discovery" @@ -78,12 +78,12 @@ func (c *Clientset) Tracker() testing.ObjectTracker { var _ clientset.Interface = &Clientset{} +// ComponentsV1alpha1 retrieves the ComponentsV1alpha1Client +func (c *Clientset) ComponentsV1alpha1() componentsv1alpha1.ComponentsV1alpha1Interface { + return &fakecomponentsv1alpha1.FakeComponentsV1alpha1{Fake: &c.Fake} +} + // ConfigurationV1alpha1 retrieves the ConfigurationV1alpha1Client func (c *Clientset) ConfigurationV1alpha1() configurationv1alpha1.ConfigurationV1alpha1Interface { return &fakeconfigurationv1alpha1.FakeConfigurationV1alpha1{Fake: &c.Fake} } - -// EventingV1alpha1 retrieves the EventingV1alpha1Client -func (c *Clientset) EventingV1alpha1() eventingv1alpha1.EventingV1alpha1Interface { - return &fakeeventingv1alpha1.FakeEventingV1alpha1{Fake: &c.Fake} -} diff --git a/pkg/client/clientset/versioned/fake/register.go b/pkg/client/clientset/versioned/fake/register.go index aae80c29a785bba21f37d0a800c0cb5007c289b3..50a4f6e4ecee55bd982c5bbbf60c184e4308f800 100644 --- a/pkg/client/clientset/versioned/fake/register.go +++ b/pkg/client/clientset/versioned/fake/register.go @@ -19,8 +19,8 @@ limitations under the License. package fake import ( + componentsv1alpha1 "github.com/actionscore/actions/pkg/apis/components/v1alpha1" configurationv1alpha1 "github.com/actionscore/actions/pkg/apis/configuration/v1alpha1" - eventingv1alpha1 "github.com/actionscore/actions/pkg/apis/eventing/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" @@ -32,8 +32,8 @@ var scheme = runtime.NewScheme() var codecs = serializer.NewCodecFactory(scheme) var parameterCodec = runtime.NewParameterCodec(scheme) var localSchemeBuilder = runtime.SchemeBuilder{ + componentsv1alpha1.AddToScheme, configurationv1alpha1.AddToScheme, - eventingv1alpha1.AddToScheme, } // AddToScheme adds all types of this clientset into the given scheme. This allows composition diff --git a/pkg/client/clientset/versioned/scheme/register.go b/pkg/client/clientset/versioned/scheme/register.go index 19117034ab00b4eaa81d0fe46dac7a3486128116..23b52bdc2dacbbd80c2d3d33a16c7e04625c2675 100644 --- a/pkg/client/clientset/versioned/scheme/register.go +++ b/pkg/client/clientset/versioned/scheme/register.go @@ -19,8 +19,8 @@ limitations under the License. package scheme import ( + componentsv1alpha1 "github.com/actionscore/actions/pkg/apis/components/v1alpha1" configurationv1alpha1 "github.com/actionscore/actions/pkg/apis/configuration/v1alpha1" - eventingv1alpha1 "github.com/actionscore/actions/pkg/apis/eventing/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" @@ -32,8 +32,8 @@ var Scheme = runtime.NewScheme() var Codecs = serializer.NewCodecFactory(Scheme) var ParameterCodec = runtime.NewParameterCodec(Scheme) var localSchemeBuilder = runtime.SchemeBuilder{ + componentsv1alpha1.AddToScheme, configurationv1alpha1.AddToScheme, - eventingv1alpha1.AddToScheme, } // AddToScheme adds all types of this clientset into the given scheme. This allows composition diff --git a/pkg/client/clientset/versioned/typed/components/v1alpha1/component.go b/pkg/client/clientset/versioned/typed/components/v1alpha1/component.go new file mode 100644 index 0000000000000000000000000000000000000000..c420b8009770316685fe3e0aa9fe2852d2d9715e --- /dev/null +++ b/pkg/client/clientset/versioned/typed/components/v1alpha1/component.go @@ -0,0 +1,174 @@ +/* +Copyright The Kubernetes Authors. + +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 client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "time" + + v1alpha1 "github.com/actionscore/actions/pkg/apis/components/v1alpha1" + scheme "github.com/actionscore/actions/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// ComponentsGetter has a method to return a ComponentInterface. +// A group's client should implement this interface. +type ComponentsGetter interface { + Components(namespace string) ComponentInterface +} + +// ComponentInterface has methods to work with Component resources. +type ComponentInterface interface { + Create(*v1alpha1.Component) (*v1alpha1.Component, error) + Update(*v1alpha1.Component) (*v1alpha1.Component, error) + Delete(name string, options *v1.DeleteOptions) error + DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error + Get(name string, options v1.GetOptions) (*v1alpha1.Component, error) + List(opts v1.ListOptions) (*v1alpha1.ComponentList, error) + Watch(opts v1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Component, err error) + ComponentExpansion +} + +// components implements ComponentInterface +type components struct { + client rest.Interface + ns string +} + +// newComponents returns a Components +func newComponents(c *ComponentsV1alpha1Client, namespace string) *components { + return &components{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the component, and returns the corresponding component object, and an error if there is any. +func (c *components) Get(name string, options v1.GetOptions) (result *v1alpha1.Component, err error) { + result = &v1alpha1.Component{} + err = c.client.Get(). + Namespace(c.ns). + Resource("components"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Components that match those selectors. +func (c *components) List(opts v1.ListOptions) (result *v1alpha1.ComponentList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.ComponentList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("components"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested components. +func (c *components) Watch(opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("components"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch() +} + +// Create takes the representation of a component and creates it. Returns the server's representation of the component, and an error, if there is any. +func (c *components) Create(component *v1alpha1.Component) (result *v1alpha1.Component, err error) { + result = &v1alpha1.Component{} + err = c.client.Post(). + Namespace(c.ns). + Resource("components"). + Body(component). + Do(). + Into(result) + return +} + +// Update takes the representation of a component and updates it. Returns the server's representation of the component, and an error, if there is any. +func (c *components) Update(component *v1alpha1.Component) (result *v1alpha1.Component, err error) { + result = &v1alpha1.Component{} + err = c.client.Put(). + Namespace(c.ns). + Resource("components"). + Name(component.Name). + Body(component). + Do(). + Into(result) + return +} + +// Delete takes name of the component and deletes it. Returns an error if one occurs. +func (c *components) Delete(name string, options *v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("components"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *components) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + var timeout time.Duration + if listOptions.TimeoutSeconds != nil { + timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("components"). + VersionedParams(&listOptions, scheme.ParameterCodec). + Timeout(timeout). + Body(options). + Do(). + Error() +} + +// Patch applies the patch and returns the patched component. +func (c *components) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Component, err error) { + result = &v1alpha1.Component{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("components"). + SubResource(subresources...). + Name(name). + Body(data). + Do(). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/eventing_client.go b/pkg/client/clientset/versioned/typed/components/v1alpha1/components_client.go similarity index 61% rename from pkg/client/clientset/versioned/typed/eventing/v1alpha1/eventing_client.go rename to pkg/client/clientset/versioned/typed/components/v1alpha1/components_client.go index 3e63d34b132a1d8be3a49fba3e5a188c39311e92..c616cce1c6fa066fd730066473af15795694ff9e 100644 --- a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/eventing_client.go +++ b/pkg/client/clientset/versioned/typed/components/v1alpha1/components_client.go @@ -19,27 +19,27 @@ limitations under the License. package v1alpha1 import ( - v1alpha1 "github.com/actionscore/actions/pkg/apis/eventing/v1alpha1" + v1alpha1 "github.com/actionscore/actions/pkg/apis/components/v1alpha1" "github.com/actionscore/actions/pkg/client/clientset/versioned/scheme" rest "k8s.io/client-go/rest" ) -type EventingV1alpha1Interface interface { +type ComponentsV1alpha1Interface interface { RESTClient() rest.Interface - EventSourcesGetter + ComponentsGetter } -// EventingV1alpha1Client is used to interact with features provided by the eventing.actions.io group. -type EventingV1alpha1Client struct { +// ComponentsV1alpha1Client is used to interact with features provided by the components.actions.io group. +type ComponentsV1alpha1Client struct { restClient rest.Interface } -func (c *EventingV1alpha1Client) EventSources(namespace string) EventSourceInterface { - return newEventSources(c, namespace) +func (c *ComponentsV1alpha1Client) Components(namespace string) ComponentInterface { + return newComponents(c, namespace) } -// NewForConfig creates a new EventingV1alpha1Client for the given config. -func NewForConfig(c *rest.Config) (*EventingV1alpha1Client, error) { +// NewForConfig creates a new ComponentsV1alpha1Client for the given config. +func NewForConfig(c *rest.Config) (*ComponentsV1alpha1Client, error) { config := *c if err := setConfigDefaults(&config); err != nil { return nil, err @@ -48,12 +48,12 @@ func NewForConfig(c *rest.Config) (*EventingV1alpha1Client, error) { if err != nil { return nil, err } - return &EventingV1alpha1Client{client}, nil + return &ComponentsV1alpha1Client{client}, nil } -// NewForConfigOrDie creates a new EventingV1alpha1Client for the given config and +// NewForConfigOrDie creates a new ComponentsV1alpha1Client for the given config and // panics if there is an error in the config. -func NewForConfigOrDie(c *rest.Config) *EventingV1alpha1Client { +func NewForConfigOrDie(c *rest.Config) *ComponentsV1alpha1Client { client, err := NewForConfig(c) if err != nil { panic(err) @@ -61,9 +61,9 @@ func NewForConfigOrDie(c *rest.Config) *EventingV1alpha1Client { return client } -// New creates a new EventingV1alpha1Client for the given RESTClient. -func New(c rest.Interface) *EventingV1alpha1Client { - return &EventingV1alpha1Client{c} +// New creates a new ComponentsV1alpha1Client for the given RESTClient. +func New(c rest.Interface) *ComponentsV1alpha1Client { + return &ComponentsV1alpha1Client{c} } func setConfigDefaults(config *rest.Config) error { @@ -81,7 +81,7 @@ func setConfigDefaults(config *rest.Config) error { // RESTClient returns a RESTClient that is used to communicate // with API server by this client implementation. -func (c *EventingV1alpha1Client) RESTClient() rest.Interface { +func (c *ComponentsV1alpha1Client) RESTClient() rest.Interface { if c == nil { return nil } diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/doc.go b/pkg/client/clientset/versioned/typed/components/v1alpha1/doc.go similarity index 100% rename from pkg/client/clientset/versioned/typed/eventing/v1alpha1/doc.go rename to pkg/client/clientset/versioned/typed/components/v1alpha1/doc.go diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/doc.go b/pkg/client/clientset/versioned/typed/components/v1alpha1/fake/doc.go similarity index 100% rename from pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/doc.go rename to pkg/client/clientset/versioned/typed/components/v1alpha1/fake/doc.go diff --git a/pkg/client/clientset/versioned/typed/components/v1alpha1/fake/fake_component.go b/pkg/client/clientset/versioned/typed/components/v1alpha1/fake/fake_component.go new file mode 100644 index 0000000000000000000000000000000000000000..3512b073b04d9eba85f756392d02f8a298d55780 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/components/v1alpha1/fake/fake_component.go @@ -0,0 +1,128 @@ +/* +Copyright The Kubernetes Authors. + +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 client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/actionscore/actions/pkg/apis/components/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeComponents implements ComponentInterface +type FakeComponents struct { + Fake *FakeComponentsV1alpha1 + ns string +} + +var componentsResource = schema.GroupVersionResource{Group: "components.actions.io", Version: "v1alpha1", Resource: "components"} + +var componentsKind = schema.GroupVersionKind{Group: "components.actions.io", Version: "v1alpha1", Kind: "Component"} + +// Get takes name of the component, and returns the corresponding component object, and an error if there is any. +func (c *FakeComponents) Get(name string, options v1.GetOptions) (result *v1alpha1.Component, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(componentsResource, c.ns, name), &v1alpha1.Component{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Component), err +} + +// List takes label and field selectors, and returns the list of Components that match those selectors. +func (c *FakeComponents) List(opts v1.ListOptions) (result *v1alpha1.ComponentList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(componentsResource, componentsKind, c.ns, opts), &v1alpha1.ComponentList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.ComponentList{ListMeta: obj.(*v1alpha1.ComponentList).ListMeta} + for _, item := range obj.(*v1alpha1.ComponentList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested components. +func (c *FakeComponents) Watch(opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(componentsResource, c.ns, opts)) + +} + +// Create takes the representation of a component and creates it. Returns the server's representation of the component, and an error, if there is any. +func (c *FakeComponents) Create(component *v1alpha1.Component) (result *v1alpha1.Component, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(componentsResource, c.ns, component), &v1alpha1.Component{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Component), err +} + +// Update takes the representation of a component and updates it. Returns the server's representation of the component, and an error, if there is any. +func (c *FakeComponents) Update(component *v1alpha1.Component) (result *v1alpha1.Component, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(componentsResource, c.ns, component), &v1alpha1.Component{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Component), err +} + +// Delete takes name of the component and deletes it. Returns an error if one occurs. +func (c *FakeComponents) Delete(name string, options *v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(componentsResource, c.ns, name), &v1alpha1.Component{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeComponents) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(componentsResource, c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1alpha1.ComponentList{}) + return err +} + +// Patch applies the patch and returns the patched component. +func (c *FakeComponents) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Component, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(componentsResource, c.ns, name, pt, data, subresources...), &v1alpha1.Component{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Component), err +} diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_eventing_client.go b/pkg/client/clientset/versioned/typed/components/v1alpha1/fake/fake_components_client.go similarity index 77% rename from pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_eventing_client.go rename to pkg/client/clientset/versioned/typed/components/v1alpha1/fake/fake_components_client.go index c2373aa17003ca165010a26601cd44e1e1c273e6..92968057b93d89c695a4189348dab4f9b4c6a8f6 100644 --- a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_eventing_client.go +++ b/pkg/client/clientset/versioned/typed/components/v1alpha1/fake/fake_components_client.go @@ -19,22 +19,22 @@ limitations under the License. package fake import ( - v1alpha1 "github.com/actionscore/actions/pkg/client/clientset/versioned/typed/eventing/v1alpha1" + v1alpha1 "github.com/actionscore/actions/pkg/client/clientset/versioned/typed/components/v1alpha1" rest "k8s.io/client-go/rest" testing "k8s.io/client-go/testing" ) -type FakeEventingV1alpha1 struct { +type FakeComponentsV1alpha1 struct { *testing.Fake } -func (c *FakeEventingV1alpha1) EventSources(namespace string) v1alpha1.EventSourceInterface { - return &FakeEventSources{c, namespace} +func (c *FakeComponentsV1alpha1) Components(namespace string) v1alpha1.ComponentInterface { + return &FakeComponents{c, namespace} } // RESTClient returns a RESTClient that is used to communicate // with API server by this client implementation. -func (c *FakeEventingV1alpha1) RESTClient() rest.Interface { +func (c *FakeComponentsV1alpha1) RESTClient() rest.Interface { var ret *rest.RESTClient return ret } diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/generated_expansion.go b/pkg/client/clientset/versioned/typed/components/v1alpha1/generated_expansion.go similarity index 94% rename from pkg/client/clientset/versioned/typed/eventing/v1alpha1/generated_expansion.go rename to pkg/client/clientset/versioned/typed/components/v1alpha1/generated_expansion.go index 3b8dd80b45500549281480850eda142eb93eb2c6..cc46f9594dc29f089cf06fb939349c8f33f2f818 100644 --- a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/components/v1alpha1/generated_expansion.go @@ -18,4 +18,4 @@ limitations under the License. package v1alpha1 -type EventSourceExpansion interface{} +type ComponentExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/eventsource.go b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/eventsource.go deleted file mode 100644 index 9fb1eba59a20dce01ddd244475dff918d3b5ddc9..0000000000000000000000000000000000000000 --- a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/eventsource.go +++ /dev/null @@ -1,174 +0,0 @@ -/* -Copyright The Kubernetes Authors. - -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 client-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - "time" - - v1alpha1 "github.com/actionscore/actions/pkg/apis/eventing/v1alpha1" - scheme "github.com/actionscore/actions/pkg/client/clientset/versioned/scheme" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" -) - -// EventSourcesGetter has a method to return a EventSourceInterface. -// A group's client should implement this interface. -type EventSourcesGetter interface { - EventSources(namespace string) EventSourceInterface -} - -// EventSourceInterface has methods to work with EventSource resources. -type EventSourceInterface interface { - Create(*v1alpha1.EventSource) (*v1alpha1.EventSource, error) - Update(*v1alpha1.EventSource) (*v1alpha1.EventSource, error) - Delete(name string, options *v1.DeleteOptions) error - DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*v1alpha1.EventSource, error) - List(opts v1.ListOptions) (*v1alpha1.EventSourceList, error) - Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.EventSource, err error) - EventSourceExpansion -} - -// eventSources implements EventSourceInterface -type eventSources struct { - client rest.Interface - ns string -} - -// newEventSources returns a EventSources -func newEventSources(c *EventingV1alpha1Client, namespace string) *eventSources { - return &eventSources{ - client: c.RESTClient(), - ns: namespace, - } -} - -// Get takes name of the eventSource, and returns the corresponding eventSource object, and an error if there is any. -func (c *eventSources) Get(name string, options v1.GetOptions) (result *v1alpha1.EventSource, err error) { - result = &v1alpha1.EventSource{} - err = c.client.Get(). - Namespace(c.ns). - Resource("eventsources"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of EventSources that match those selectors. -func (c *eventSources) List(opts v1.ListOptions) (result *v1alpha1.EventSourceList, err error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - result = &v1alpha1.EventSourceList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("eventsources"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Do(). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested eventSources. -func (c *eventSources) Watch(opts v1.ListOptions) (watch.Interface, error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("eventsources"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Watch() -} - -// Create takes the representation of a eventSource and creates it. Returns the server's representation of the eventSource, and an error, if there is any. -func (c *eventSources) Create(eventSource *v1alpha1.EventSource) (result *v1alpha1.EventSource, err error) { - result = &v1alpha1.EventSource{} - err = c.client.Post(). - Namespace(c.ns). - Resource("eventsources"). - Body(eventSource). - Do(). - Into(result) - return -} - -// Update takes the representation of a eventSource and updates it. Returns the server's representation of the eventSource, and an error, if there is any. -func (c *eventSources) Update(eventSource *v1alpha1.EventSource) (result *v1alpha1.EventSource, err error) { - result = &v1alpha1.EventSource{} - err = c.client.Put(). - Namespace(c.ns). - Resource("eventsources"). - Name(eventSource.Name). - Body(eventSource). - Do(). - Into(result) - return -} - -// Delete takes name of the eventSource and deletes it. Returns an error if one occurs. -func (c *eventSources) Delete(name string, options *v1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("eventsources"). - Name(name). - Body(options). - Do(). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *eventSources) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - var timeout time.Duration - if listOptions.TimeoutSeconds != nil { - timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second - } - return c.client.Delete(). - Namespace(c.ns). - Resource("eventsources"). - VersionedParams(&listOptions, scheme.ParameterCodec). - Timeout(timeout). - Body(options). - Do(). - Error() -} - -// Patch applies the patch and returns the patched eventSource. -func (c *eventSources) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.EventSource, err error) { - result = &v1alpha1.EventSource{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("eventsources"). - SubResource(subresources...). - Name(name). - Body(data). - Do(). - Into(result) - return -} diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_eventsource.go b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_eventsource.go deleted file mode 100644 index 76a2f66e3c90aebb1f797c8cc7a6e7082d49b926..0000000000000000000000000000000000000000 --- a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_eventsource.go +++ /dev/null @@ -1,128 +0,0 @@ -/* -Copyright The Kubernetes Authors. - -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 client-gen. DO NOT EDIT. - -package fake - -import ( - v1alpha1 "github.com/actionscore/actions/pkg/apis/eventing/v1alpha1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" -) - -// FakeEventSources implements EventSourceInterface -type FakeEventSources struct { - Fake *FakeEventingV1alpha1 - ns string -} - -var eventsourcesResource = schema.GroupVersionResource{Group: "eventing.actions.io", Version: "v1alpha1", Resource: "eventsources"} - -var eventsourcesKind = schema.GroupVersionKind{Group: "eventing.actions.io", Version: "v1alpha1", Kind: "EventSource"} - -// Get takes name of the eventSource, and returns the corresponding eventSource object, and an error if there is any. -func (c *FakeEventSources) Get(name string, options v1.GetOptions) (result *v1alpha1.EventSource, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(eventsourcesResource, c.ns, name), &v1alpha1.EventSource{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha1.EventSource), err -} - -// List takes label and field selectors, and returns the list of EventSources that match those selectors. -func (c *FakeEventSources) List(opts v1.ListOptions) (result *v1alpha1.EventSourceList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(eventsourcesResource, eventsourcesKind, c.ns, opts), &v1alpha1.EventSourceList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &v1alpha1.EventSourceList{ListMeta: obj.(*v1alpha1.EventSourceList).ListMeta} - for _, item := range obj.(*v1alpha1.EventSourceList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested eventSources. -func (c *FakeEventSources) Watch(opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(eventsourcesResource, c.ns, opts)) - -} - -// Create takes the representation of a eventSource and creates it. Returns the server's representation of the eventSource, and an error, if there is any. -func (c *FakeEventSources) Create(eventSource *v1alpha1.EventSource) (result *v1alpha1.EventSource, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(eventsourcesResource, c.ns, eventSource), &v1alpha1.EventSource{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha1.EventSource), err -} - -// Update takes the representation of a eventSource and updates it. Returns the server's representation of the eventSource, and an error, if there is any. -func (c *FakeEventSources) Update(eventSource *v1alpha1.EventSource) (result *v1alpha1.EventSource, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(eventsourcesResource, c.ns, eventSource), &v1alpha1.EventSource{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha1.EventSource), err -} - -// Delete takes name of the eventSource and deletes it. Returns an error if one occurs. -func (c *FakeEventSources) Delete(name string, options *v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteAction(eventsourcesResource, c.ns, name), &v1alpha1.EventSource{}) - - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeEventSources) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(eventsourcesResource, c.ns, listOptions) - - _, err := c.Fake.Invokes(action, &v1alpha1.EventSourceList{}) - return err -} - -// Patch applies the patch and returns the patched eventSource. -func (c *FakeEventSources) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.EventSource, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(eventsourcesResource, c.ns, name, pt, data, subresources...), &v1alpha1.EventSource{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha1.EventSource), err -} diff --git a/pkg/client/informers/externalversions/eventing/interface.go b/pkg/client/informers/externalversions/components/interface.go similarity index 96% rename from pkg/client/informers/externalversions/eventing/interface.go rename to pkg/client/informers/externalversions/components/interface.go index 5f75681d40dc4508744ba8105b2b987e82d4fb8f..c1b394727d734b11e8ec315596d072d4ba5010d5 100644 --- a/pkg/client/informers/externalversions/eventing/interface.go +++ b/pkg/client/informers/externalversions/components/interface.go @@ -16,10 +16,10 @@ limitations under the License. // Code generated by informer-gen. DO NOT EDIT. -package eventing +package components import ( - v1alpha1 "github.com/actionscore/actions/pkg/client/informers/externalversions/eventing/v1alpha1" + v1alpha1 "github.com/actionscore/actions/pkg/client/informers/externalversions/components/v1alpha1" internalinterfaces "github.com/actionscore/actions/pkg/client/informers/externalversions/internalinterfaces" ) diff --git a/pkg/client/informers/externalversions/eventing/v1alpha1/eventsource.go b/pkg/client/informers/externalversions/components/v1alpha1/component.go similarity index 54% rename from pkg/client/informers/externalversions/eventing/v1alpha1/eventsource.go rename to pkg/client/informers/externalversions/components/v1alpha1/component.go index a59cc4c9b2e90521a7b1d3739a71a5c79a5d870a..524970d4934430341dd998e140e3e1e899a5391f 100644 --- a/pkg/client/informers/externalversions/eventing/v1alpha1/eventsource.go +++ b/pkg/client/informers/externalversions/components/v1alpha1/component.go @@ -21,69 +21,69 @@ package v1alpha1 import ( time "time" - eventingv1alpha1 "github.com/actionscore/actions/pkg/apis/eventing/v1alpha1" + componentsv1alpha1 "github.com/actionscore/actions/pkg/apis/components/v1alpha1" versioned "github.com/actionscore/actions/pkg/client/clientset/versioned" internalinterfaces "github.com/actionscore/actions/pkg/client/informers/externalversions/internalinterfaces" - v1alpha1 "github.com/actionscore/actions/pkg/client/listers/eventing/v1alpha1" + v1alpha1 "github.com/actionscore/actions/pkg/client/listers/components/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" ) -// EventSourceInformer provides access to a shared informer and lister for -// EventSources. -type EventSourceInformer interface { +// ComponentInformer provides access to a shared informer and lister for +// Components. +type ComponentInformer interface { Informer() cache.SharedIndexInformer - Lister() v1alpha1.EventSourceLister + Lister() v1alpha1.ComponentLister } -type eventSourceInformer struct { +type componentInformer struct { factory internalinterfaces.SharedInformerFactory tweakListOptions internalinterfaces.TweakListOptionsFunc namespace string } -// NewEventSourceInformer constructs a new informer for EventSource type. +// NewComponentInformer constructs a new informer for Component type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. -func NewEventSourceInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredEventSourceInformer(client, namespace, resyncPeriod, indexers, nil) +func NewComponentInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredComponentInformer(client, namespace, resyncPeriod, indexers, nil) } -// NewFilteredEventSourceInformer constructs a new informer for EventSource type. +// NewFilteredComponentInformer constructs a new informer for Component type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. -func NewFilteredEventSourceInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { +func NewFilteredComponentInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { tweakListOptions(&options) } - return client.EventingV1alpha1().EventSources(namespace).List(options) + return client.ComponentsV1alpha1().Components(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { tweakListOptions(&options) } - return client.EventingV1alpha1().EventSources(namespace).Watch(options) + return client.ComponentsV1alpha1().Components(namespace).Watch(options) }, }, - &eventingv1alpha1.EventSource{}, + &componentsv1alpha1.Component{}, resyncPeriod, indexers, ) } -func (f *eventSourceInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredEventSourceInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +func (f *componentInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredComponentInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } -func (f *eventSourceInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&eventingv1alpha1.EventSource{}, f.defaultInformer) +func (f *componentInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&componentsv1alpha1.Component{}, f.defaultInformer) } -func (f *eventSourceInformer) Lister() v1alpha1.EventSourceLister { - return v1alpha1.NewEventSourceLister(f.Informer().GetIndexer()) +func (f *componentInformer) Lister() v1alpha1.ComponentLister { + return v1alpha1.NewComponentLister(f.Informer().GetIndexer()) } diff --git a/pkg/client/informers/externalversions/eventing/v1alpha1/interface.go b/pkg/client/informers/externalversions/components/v1alpha1/interface.go similarity index 81% rename from pkg/client/informers/externalversions/eventing/v1alpha1/interface.go rename to pkg/client/informers/externalversions/components/v1alpha1/interface.go index bef266a92fafa20406ad356cc16f36c428561b09..a8933d495da8d3bd5ad98aa5fdf9122ddafcd6f9 100644 --- a/pkg/client/informers/externalversions/eventing/v1alpha1/interface.go +++ b/pkg/client/informers/externalversions/components/v1alpha1/interface.go @@ -24,8 +24,8 @@ import ( // Interface provides access to all the informers in this group version. type Interface interface { - // EventSources returns a EventSourceInformer. - EventSources() EventSourceInformer + // Components returns a ComponentInformer. + Components() ComponentInformer } type version struct { @@ -39,7 +39,7 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } -// EventSources returns a EventSourceInformer. -func (v *version) EventSources() EventSourceInformer { - return &eventSourceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +// Components returns a ComponentInformer. +func (v *version) Components() ComponentInformer { + return &componentInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } diff --git a/pkg/client/informers/externalversions/factory.go b/pkg/client/informers/externalversions/factory.go index b2dac02745477cbd033bf5d3885613843bd2e50b..2454382c727f98c455e0cd9f691afee1f36459ed 100644 --- a/pkg/client/informers/externalversions/factory.go +++ b/pkg/client/informers/externalversions/factory.go @@ -24,8 +24,8 @@ import ( time "time" versioned "github.com/actionscore/actions/pkg/client/clientset/versioned" + components "github.com/actionscore/actions/pkg/client/informers/externalversions/components" configuration "github.com/actionscore/actions/pkg/client/informers/externalversions/configuration" - eventing "github.com/actionscore/actions/pkg/client/informers/externalversions/eventing" internalinterfaces "github.com/actionscore/actions/pkg/client/informers/externalversions/internalinterfaces" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" @@ -173,14 +173,14 @@ type SharedInformerFactory interface { ForResource(resource schema.GroupVersionResource) (GenericInformer, error) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool + Components() components.Interface Configuration() configuration.Interface - Eventing() eventing.Interface } -func (f *sharedInformerFactory) Configuration() configuration.Interface { - return configuration.New(f, f.namespace, f.tweakListOptions) +func (f *sharedInformerFactory) Components() components.Interface { + return components.New(f, f.namespace, f.tweakListOptions) } -func (f *sharedInformerFactory) Eventing() eventing.Interface { - return eventing.New(f, f.namespace, f.tweakListOptions) +func (f *sharedInformerFactory) Configuration() configuration.Interface { + return configuration.New(f, f.namespace, f.tweakListOptions) } diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index 40856ef871419a292ec621f74c915e54cb03c87d..c48efc54a37ea7cd9dcad5ba04f872a80fe7d6b4 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -21,8 +21,8 @@ package externalversions import ( "fmt" - v1alpha1 "github.com/actionscore/actions/pkg/apis/configuration/v1alpha1" - eventingv1alpha1 "github.com/actionscore/actions/pkg/apis/eventing/v1alpha1" + v1alpha1 "github.com/actionscore/actions/pkg/apis/components/v1alpha1" + configurationv1alpha1 "github.com/actionscore/actions/pkg/apis/configuration/v1alpha1" schema "k8s.io/apimachinery/pkg/runtime/schema" cache "k8s.io/client-go/tools/cache" ) @@ -53,13 +53,13 @@ func (f *genericInformer) Lister() cache.GenericLister { // TODO extend this to unknown resources with a client pool func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { - // Group=configuration.actions.io, Version=v1alpha1 - case v1alpha1.SchemeGroupVersion.WithResource("configurations"): - return &genericInformer{resource: resource.GroupResource(), informer: f.Configuration().V1alpha1().Configurations().Informer()}, nil + // Group=components.actions.io, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithResource("components"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Components().V1alpha1().Components().Informer()}, nil - // Group=eventing.actions.io, Version=v1alpha1 - case eventingv1alpha1.SchemeGroupVersion.WithResource("eventsources"): - return &genericInformer{resource: resource.GroupResource(), informer: f.Eventing().V1alpha1().EventSources().Informer()}, nil + // Group=configuration.actions.io, Version=v1alpha1 + case configurationv1alpha1.SchemeGroupVersion.WithResource("configurations"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Configuration().V1alpha1().Configurations().Informer()}, nil } diff --git a/pkg/client/listers/components/v1alpha1/component.go b/pkg/client/listers/components/v1alpha1/component.go new file mode 100644 index 0000000000000000000000000000000000000000..e8fbed67ab909e4fa926344dd078268593bcce57 --- /dev/null +++ b/pkg/client/listers/components/v1alpha1/component.go @@ -0,0 +1,94 @@ +/* +Copyright The Kubernetes Authors. + +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 lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/actionscore/actions/pkg/apis/components/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// ComponentLister helps list Components. +type ComponentLister interface { + // List lists all Components in the indexer. + List(selector labels.Selector) (ret []*v1alpha1.Component, err error) + // Components returns an object that can list and get Components. + Components(namespace string) ComponentNamespaceLister + ComponentListerExpansion +} + +// componentLister implements the ComponentLister interface. +type componentLister struct { + indexer cache.Indexer +} + +// NewComponentLister returns a new ComponentLister. +func NewComponentLister(indexer cache.Indexer) ComponentLister { + return &componentLister{indexer: indexer} +} + +// List lists all Components in the indexer. +func (s *componentLister) List(selector labels.Selector) (ret []*v1alpha1.Component, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.Component)) + }) + return ret, err +} + +// Components returns an object that can list and get Components. +func (s *componentLister) Components(namespace string) ComponentNamespaceLister { + return componentNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// ComponentNamespaceLister helps list and get Components. +type ComponentNamespaceLister interface { + // List lists all Components in the indexer for a given namespace. + List(selector labels.Selector) (ret []*v1alpha1.Component, err error) + // Get retrieves the Component from the indexer for a given namespace and name. + Get(name string) (*v1alpha1.Component, error) + ComponentNamespaceListerExpansion +} + +// componentNamespaceLister implements the ComponentNamespaceLister +// interface. +type componentNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all Components in the indexer for a given namespace. +func (s componentNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.Component, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.Component)) + }) + return ret, err +} + +// Get retrieves the Component from the indexer for a given namespace and name. +func (s componentNamespaceLister) Get(name string) (*v1alpha1.Component, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("component"), name) + } + return obj.(*v1alpha1.Component), nil +} diff --git a/pkg/client/listers/eventing/v1alpha1/expansion_generated.go b/pkg/client/listers/components/v1alpha1/expansion_generated.go similarity index 68% rename from pkg/client/listers/eventing/v1alpha1/expansion_generated.go rename to pkg/client/listers/components/v1alpha1/expansion_generated.go index 4137261bc0c79638bbc6476aff59cc1198e6ea11..511afdc3d220227fdb4e25e06c727056ff47ceed 100644 --- a/pkg/client/listers/eventing/v1alpha1/expansion_generated.go +++ b/pkg/client/listers/components/v1alpha1/expansion_generated.go @@ -18,10 +18,10 @@ limitations under the License. package v1alpha1 -// EventSourceListerExpansion allows custom methods to be added to -// EventSourceLister. -type EventSourceListerExpansion interface{} +// ComponentListerExpansion allows custom methods to be added to +// ComponentLister. +type ComponentListerExpansion interface{} -// EventSourceNamespaceListerExpansion allows custom methods to be added to -// EventSourceNamespaceLister. -type EventSourceNamespaceListerExpansion interface{} +// ComponentNamespaceListerExpansion allows custom methods to be added to +// ComponentNamespaceLister. +type ComponentNamespaceListerExpansion interface{} diff --git a/pkg/client/listers/eventing/v1alpha1/eventsource.go b/pkg/client/listers/eventing/v1alpha1/eventsource.go deleted file mode 100644 index 1ee5d1b10e1a3b8995cbb1d1b11c8f9a13071be4..0000000000000000000000000000000000000000 --- a/pkg/client/listers/eventing/v1alpha1/eventsource.go +++ /dev/null @@ -1,94 +0,0 @@ -/* -Copyright The Kubernetes Authors. - -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 lister-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - v1alpha1 "github.com/actionscore/actions/pkg/apis/eventing/v1alpha1" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/tools/cache" -) - -// EventSourceLister helps list EventSources. -type EventSourceLister interface { - // List lists all EventSources in the indexer. - List(selector labels.Selector) (ret []*v1alpha1.EventSource, err error) - // EventSources returns an object that can list and get EventSources. - EventSources(namespace string) EventSourceNamespaceLister - EventSourceListerExpansion -} - -// eventSourceLister implements the EventSourceLister interface. -type eventSourceLister struct { - indexer cache.Indexer -} - -// NewEventSourceLister returns a new EventSourceLister. -func NewEventSourceLister(indexer cache.Indexer) EventSourceLister { - return &eventSourceLister{indexer: indexer} -} - -// List lists all EventSources in the indexer. -func (s *eventSourceLister) List(selector labels.Selector) (ret []*v1alpha1.EventSource, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*v1alpha1.EventSource)) - }) - return ret, err -} - -// EventSources returns an object that can list and get EventSources. -func (s *eventSourceLister) EventSources(namespace string) EventSourceNamespaceLister { - return eventSourceNamespaceLister{indexer: s.indexer, namespace: namespace} -} - -// EventSourceNamespaceLister helps list and get EventSources. -type EventSourceNamespaceLister interface { - // List lists all EventSources in the indexer for a given namespace. - List(selector labels.Selector) (ret []*v1alpha1.EventSource, err error) - // Get retrieves the EventSource from the indexer for a given namespace and name. - Get(name string) (*v1alpha1.EventSource, error) - EventSourceNamespaceListerExpansion -} - -// eventSourceNamespaceLister implements the EventSourceNamespaceLister -// interface. -type eventSourceNamespaceLister struct { - indexer cache.Indexer - namespace string -} - -// List lists all EventSources in the indexer for a given namespace. -func (s eventSourceNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.EventSource, err error) { - err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*v1alpha1.EventSource)) - }) - return ret, err -} - -// Get retrieves the EventSource from the indexer for a given namespace and name. -func (s eventSourceNamespaceLister) Get(name string) (*v1alpha1.EventSource, error) { - obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(v1alpha1.Resource("eventsource"), name) - } - return obj.(*v1alpha1.EventSource), nil -} diff --git a/pkg/components/bindings/input_binding.go b/pkg/components/bindings/input_binding.go new file mode 100644 index 0000000000000000000000000000000000000000..d1d7fc08eb48b22ac395a9a168dab665e6694d08 --- /dev/null +++ b/pkg/components/bindings/input_binding.go @@ -0,0 +1,6 @@ +package bindings + +type InputBinding interface { + Init(metadata Metadata) error + Read(handler func(*ReadResponse) error) error +} diff --git a/pkg/components/bindings/metadata.go b/pkg/components/bindings/metadata.go new file mode 100644 index 0000000000000000000000000000000000000000..d93e53d010f8a7331099d42b9b3d77eae12333d8 --- /dev/null +++ b/pkg/components/bindings/metadata.go @@ -0,0 +1,7 @@ +package bindings + +type Metadata struct { + Name string + ConnectionInfo map[string]string `json:"connectionInfo"` + Properties map[string]string `json:"properties"` +} diff --git a/pkg/components/bindings/output_binding.go b/pkg/components/bindings/output_binding.go new file mode 100644 index 0000000000000000000000000000000000000000..78eb451cee5146922b977be23049de2d3dede215 --- /dev/null +++ b/pkg/components/bindings/output_binding.go @@ -0,0 +1,6 @@ +package bindings + +type OutputBinding interface { + Init(metadata Metadata) error + Write(req *WriteRequest) error +} diff --git a/pkg/components/bindings/registry.go b/pkg/components/bindings/registry.go new file mode 100644 index 0000000000000000000000000000000000000000..cd6a639d750b465f71d060ebeb913890da2ed045 --- /dev/null +++ b/pkg/components/bindings/registry.go @@ -0,0 +1,57 @@ +package bindings + +import ( + "fmt" + "sync" +) + +type BindingsRegistry interface { + CreateInputBinding(name string) (InputBinding, error) + CreateOutputBinding(name string) (OutputBinding, error) +} + +type bindingsRegistry struct { + inputBindings map[string]InputBinding + outputBindings map[string]OutputBinding +} + +var instance *bindingsRegistry +var once sync.Once + +func NewBindingsRegistry() BindingsRegistry { + once.Do(func() { + instance = &bindingsRegistry{ + inputBindings: map[string]InputBinding{}, + outputBindings: map[string]OutputBinding{}, + } + }) + return instance +} + +func RegisterInputBinding(name string, binding InputBinding) { + instance.inputBindings[fmt.Sprintf("bindings.%s", name)] = binding +} + +func RegisterOutputBinding(name string, binding OutputBinding) { + instance.outputBindings[fmt.Sprintf("bindings.%s", name)] = binding +} + +func (b *bindingsRegistry) CreateInputBinding(name string) (InputBinding, error) { + for key, binding := range b.inputBindings { + if key == name { + return binding, nil + } + } + + return nil, fmt.Errorf("couldn't find input binding %s", name) +} + +func (b *bindingsRegistry) CreateOutputBinding(name string) (OutputBinding, error) { + for key, binding := range b.outputBindings { + if key == name { + return binding, nil + } + } + + return nil, fmt.Errorf("couldn't find output binding %s", name) +} diff --git a/pkg/components/bindings/responses.go b/pkg/components/bindings/responses.go new file mode 100644 index 0000000000000000000000000000000000000000..da400c35de4bab576ea39dcc232be3a0ead0fc9a --- /dev/null +++ b/pkg/components/bindings/responses.go @@ -0,0 +1,22 @@ +package bindings + +import ( + "github.com/actionscore/actions/pkg/components/state" +) + +type ReadResponse struct { + Data []byte `json:"data"` + Metadata map[string]string `json:"metadata"` +} + +type WriteRequest struct { + Data []byte `json:"data"` + Metadata map[string]string `json:"metadata"` +} + +type AppResponse struct { + Data interface{} `json:"data"` + To []string `json:"to"` + State []state.SetRequest `json:"state"` + Concurrency string `json:"concurrency"` +} diff --git a/pkg/components/component.go b/pkg/components/component.go new file mode 100644 index 0000000000000000000000000000000000000000..def37144d30718dfcbf96799400609970b0b1624 --- /dev/null +++ b/pkg/components/component.go @@ -0,0 +1,16 @@ +package components + +type Component struct { + Metadata ComponentMetadata `json:"metadata"` + Spec ComponentSpec `json:"spec"` +} + +type ComponentMetadata struct { + Name string `json:"name"` +} + +type ComponentSpec struct { + Type string `json:"type"` + ConnectionInfo map[string]string `json:"connectionInfo"` + Properties map[string]string `json:"properties"` +} diff --git a/pkg/components/component_handler.go b/pkg/components/component_handler.go new file mode 100644 index 0000000000000000000000000000000000000000..7239a8d2da20629d60ed9d202900fec289674a9f --- /dev/null +++ b/pkg/components/component_handler.go @@ -0,0 +1,5 @@ +package components + +type ComponentHandler interface { + OnComponentUpdated(component Component) +} diff --git a/pkg/components/components_loader.go b/pkg/components/components_loader.go new file mode 100644 index 0000000000000000000000000000000000000000..2e761b53f1f625eeec86873c2cbc197fc1440f3d --- /dev/null +++ b/pkg/components/components_loader.go @@ -0,0 +1,5 @@ +package components + +type ComponentLoader interface { + LoadComponents() ([]Component, error) +} diff --git a/pkg/components/kubernetes_loader.go b/pkg/components/kubernetes_loader.go new file mode 100644 index 0000000000000000000000000000000000000000..f5966937c059443dc8ce28204a49009ed5738fe9 --- /dev/null +++ b/pkg/components/kubernetes_loader.go @@ -0,0 +1,46 @@ +package components + +import ( + "encoding/json" + "fmt" + "time" + + config "github.com/actionscore/actions/pkg/config/modes" + "github.com/valyala/fasthttp" +) + +type KubernetesComponents struct { + config config.KubernetesConfig +} + +func NewKubernetesComponents(configuration config.KubernetesConfig) *KubernetesComponents { + return &KubernetesComponents{ + config: configuration, + } +} + +func (k *KubernetesComponents) LoadComponents() ([]Component, error) { + url := fmt.Sprintf("%s/components", k.config.ControlPlaneAddress) + req := fasthttp.AcquireRequest() + req.SetRequestURI(url) + req.Header.SetContentType("application/json") + + resp := fasthttp.AcquireResponse() + client := &fasthttp.Client{ + ReadTimeout: time.Second * 10, + } + err := client.Do(req, resp) + if err != nil { + return nil, err + } + + body := resp.Body() + + var components []Component + err = json.Unmarshal(body, &components) + if err != nil { + return nil, err + } + + return components, nil +} diff --git a/pkg/components/standalone_loader.go b/pkg/components/standalone_loader.go new file mode 100644 index 0000000000000000000000000000000000000000..19eed096e472d4f43689ecf8ad3442642f8fee82 --- /dev/null +++ b/pkg/components/standalone_loader.go @@ -0,0 +1,51 @@ +package components + +import ( + "fmt" + "io/ioutil" + + log "github.com/Sirupsen/logrus" + config "github.com/actionscore/actions/pkg/config/modes" + "github.com/ghodss/yaml" +) + +type StandaloneComponents struct { + config config.StandaloneConfig +} + +func NewStandaloneComponents(configuration config.StandaloneConfig) *StandaloneComponents { + return &StandaloneComponents{ + config: configuration, + } +} + +func (s *StandaloneComponents) LoadComponents() ([]Component, error) { + dir := s.config.ComponentsPath + files, err := ioutil.ReadDir(dir) + if err != nil { + return nil, err + } + + list := []Component{} + + for _, file := range files { + if !file.IsDir() { + b, err := ioutil.ReadFile(fmt.Sprintf("%s/%s", dir, file.Name())) + if err != nil { + log.Warnf("error reading file: %s", err) + continue + } + + var component Component + err = yaml.Unmarshal(b, &component) + if err != nil { + log.Warnf("error parsing file: %s", err) + continue + } + + list = append(list, component) + } + } + + return list, nil +} diff --git a/pkg/components/state/metadata.go b/pkg/components/state/metadata.go new file mode 100644 index 0000000000000000000000000000000000000000..556c1b84e88357bc6c6cc7708cc4cfb02ded124d --- /dev/null +++ b/pkg/components/state/metadata.go @@ -0,0 +1,6 @@ +package state + +type Metadata struct { + ConnectionInfo map[string]string `json:"connectionInfo"` + Properties map[string]string `json:"properties"` +} diff --git a/pkg/components/state/registry.go b/pkg/components/state/registry.go new file mode 100644 index 0000000000000000000000000000000000000000..26a5b2951096d288e786c4d8425a51a6397c881f --- /dev/null +++ b/pkg/components/state/registry.go @@ -0,0 +1,40 @@ +package state + +import ( + "fmt" + "sync" +) + +type StateStoreRegistry interface { + CreateStateStore(name string) (StateStore, error) +} + +type stateStoreRegistry struct { + stateStores map[string]StateStore +} + +var instance *stateStoreRegistry +var once sync.Once + +func NewStateStoreRegistry() StateStoreRegistry { + once.Do(func() { + instance = &stateStoreRegistry{ + stateStores: map[string]StateStore{}, + } + }) + return instance +} + +func RegisterStateStore(name string, store StateStore) { + instance.stateStores[fmt.Sprintf("state.%s", name)] = store +} + +func (s *stateStoreRegistry) CreateStateStore(name string) (StateStore, error) { + for key, s := range s.stateStores { + if key == name { + return s, nil + } + } + + return nil, fmt.Errorf("couldn't find state store %s", name) +} diff --git a/pkg/components/state/requests.go b/pkg/components/state/requests.go new file mode 100644 index 0000000000000000000000000000000000000000..c626ab1fb57016fa025de94f80fab116aa16593a --- /dev/null +++ b/pkg/components/state/requests.go @@ -0,0 +1,17 @@ +package state + +type GetRequest struct { + Key string `json:"key"` + ETag string `json:"etag,omitempty"` +} + +type DeleteRequest struct { + Key string `json:"key"` +} + +type SetRequest struct { + Key string `json:"key"` + Value interface{} `json:"value"` + ETag string `json:"etag,omitempty"` + Metadata map[string]string `json:"metadata"` +} diff --git a/pkg/components/state/responses.go b/pkg/components/state/responses.go new file mode 100644 index 0000000000000000000000000000000000000000..60b3be3c23c0f48088be753431cd2f0da6539b17 --- /dev/null +++ b/pkg/components/state/responses.go @@ -0,0 +1,7 @@ +package state + +type GetResponse struct { + Data []byte `json:"data"` + ETag string `json:"etag,omitempty"` + Metadata map[string]string `json:"metadata"` +} diff --git a/pkg/components/state/state_store.go b/pkg/components/state/state_store.go new file mode 100644 index 0000000000000000000000000000000000000000..c09b2af184f36d71ef781786f6748c92b1c0613e --- /dev/null +++ b/pkg/components/state/state_store.go @@ -0,0 +1,10 @@ +package state + +type StateStore interface { + Init(metadata Metadata) error + Delete(req *DeleteRequest) error + BulkDelete(req []DeleteRequest) error + Get(req *GetRequest) (*GetResponse, error) + Set(req *SetRequest) error + BulkSet(req []SetRequest) error +} diff --git a/pkg/action/app_configuration.go b/pkg/config/app_configuration.go similarity index 82% rename from pkg/action/app_configuration.go rename to pkg/config/app_configuration.go index 96481b4f0d08d6ee14ef5138914024764a2ecc26..6bb39fb7c51ea662b567b0252ee627a7c7c2ec0c 100644 --- a/pkg/action/app_configuration.go +++ b/pkg/config/app_configuration.go @@ -1,4 +1,4 @@ -package action +package config type ApplicationConfig struct { Entities []string `json:"entities"` diff --git a/pkg/config/configuration.go b/pkg/config/configuration.go new file mode 100644 index 0000000000000000000000000000000000000000..9a2a1e922a67185c2aa0faf0243fbadf0f850707 --- /dev/null +++ b/pkg/config/configuration.go @@ -0,0 +1,84 @@ +package config + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "time" + + "github.com/valyala/fasthttp" + "gopkg.in/yaml.v2" +) + +type Configuration struct { + Spec ConfigurationSpec `json:"spec,omitempty"` +} + +type ConfigurationSpec struct { + TracingSpec TracingSpec `json:"tracing,omitempty"` +} + +type TracingSpec struct { + Enabled bool `json:"enabled"` + ExporterType string `json:"exporterType"` + ExporterAddress string `json:"exporterAddress"` + IncludeEvent bool `json:"includeEvent"` + IncludeEventBody bool `json:"includeEventBody"` +} + +func LoadDefaultConfiguration() *Configuration { + return &Configuration{ + Spec: ConfigurationSpec{ + TracingSpec: TracingSpec{ + Enabled: false, + }, + }, + } +} + +func LoadStandaloneConfiguration(config string) (*Configuration, error) { + _, err := os.Stat(config) + if err != nil { + return nil, err + } + + b, err := ioutil.ReadFile(config) + if err != nil { + return nil, err + } + + var conf Configuration + err = yaml.Unmarshal(b, &conf) + if err != nil { + return nil, err + } + + return &conf, nil +} + +func LoadKubernetesConfiguration(config, controlPlaneAddress string) (*Configuration, error) { + url := fmt.Sprintf("%s/configurations/%s", controlPlaneAddress, config) + req := fasthttp.AcquireRequest() + req.SetRequestURI(url) + req.Header.SetContentType("application/json") + + resp := fasthttp.AcquireResponse() + client := &fasthttp.Client{ + ReadTimeout: time.Second * 10, + } + err := client.Do(req, resp) + if err != nil { + return nil, err + } + + body := resp.Body() + + var conf Configuration + err = json.Unmarshal(body, &conf) + if err != nil { + return nil, err + } + + return &conf, nil +} diff --git a/pkg/config/modes/kubernetes_config.go b/pkg/config/modes/kubernetes_config.go new file mode 100644 index 0000000000000000000000000000000000000000..1fd654dcb86a6debe86ef194be090e5f821dc82a --- /dev/null +++ b/pkg/config/modes/kubernetes_config.go @@ -0,0 +1,5 @@ +package config + +type KubernetesConfig struct { + ControlPlaneAddress string +} diff --git a/pkg/config/modes/standalone_config.go b/pkg/config/modes/standalone_config.go new file mode 100644 index 0000000000000000000000000000000000000000..5f75c2fdbfc31ee7f2bce3156ec28499d75cba13 --- /dev/null +++ b/pkg/config/modes/standalone_config.go @@ -0,0 +1,5 @@ +package config + +type StandaloneConfig struct { + ComponentsPath string +} diff --git a/pkg/api/api.go b/pkg/controller/api/api.go similarity index 78% rename from pkg/api/api.go rename to pkg/controller/api/api.go index 517cff2f48fa039688ed53591a53f8b7657b345f..1b1e8faf9e3424ad7bacd2bf1e1869d93c97623d 100644 --- a/pkg/api/api.go +++ b/pkg/controller/api/api.go @@ -9,7 +9,7 @@ import ( "time" log "github.com/Sirupsen/logrus" - eventing_v1alpha1 "github.com/actionscore/actions/pkg/apis/eventing/v1alpha1" + components_v1alpha1 "github.com/actionscore/actions/pkg/apis/components/v1alpha1" scheme "github.com/actionscore/actions/pkg/client/clientset/versioned" "github.com/gorilla/mux" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -25,9 +25,13 @@ type apiServer struct { Client scheme.Interface } -type EventSource struct { - Name string `json:"name"` - Spec eventing_v1alpha1.EventSourceSpec `json:"spec"` +type Component struct { + Metadata ComponentMetadata `json:"metadata"` + Spec components_v1alpha1.ComponentSpec `json:"spec"` +} + +type ComponentMetadata struct { + Name string `json:"name"` } type Configuration struct { @@ -54,7 +58,7 @@ func NewAPIServer(client scheme.Interface) APIServer { func (a *apiServer) Run(ctx context.Context) { r := mux.NewRouter() - r.HandleFunc("/eventsources", a.GetEventSources).Methods("GET") + r.HandleFunc("/components", a.GetComponents).Methods("GET") r.HandleFunc("/configurations/{name}", a.GetConfiguration).Methods("GET") http.Handle("/", r) @@ -95,7 +99,7 @@ func (a *apiServer) GetConfiguration(w http.ResponseWriter, r *http.Request) { name := vars["name"] cfg, err := a.Client.ConfigurationV1alpha1().Configurations(meta_v1.NamespaceAll).Get(name, meta_v1.GetOptions{}) if err != nil { - log.Errorf("Error getting configuration - %s", err) + log.Errorf("Error getting configuration: %s", err) RespondWithError(w, 500, "Error occured") return } @@ -112,20 +116,22 @@ func (a *apiServer) GetConfiguration(w http.ResponseWriter, r *http.Request) { } RespondWithJSON(w, 200, ret) } -func (a *apiServer) GetEventSources(w http.ResponseWriter, r *http.Request) { - list := []EventSource{} +func (a *apiServer) GetComponents(w http.ResponseWriter, r *http.Request) { + list := []Component{} - eventSources, err := a.Client.EventingV1alpha1().EventSources(meta_v1.NamespaceAll).List(meta_v1.ListOptions{}) + components, err := a.Client.ComponentsV1alpha1().Components(meta_v1.NamespaceAll).List(meta_v1.ListOptions{}) if err != nil { - log.Errorf("Error getting event sources - %s", err) + log.Errorf("Error getting components: %s", err) RespondWithError(w, 500, "Error occured") return } - for _, es := range eventSources.Items { - list = append(list, EventSource{ - Name: es.ObjectMeta.Name, - Spec: es.Spec, + for _, c := range components.Items { + list = append(list, Component{ + Metadata: ComponentMetadata{ + Name: c.ObjectMeta.Name, + }, + Spec: c.Spec, }) } diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 64aeb779c931abb5852b3d3e9c8d2a3abf1a4a7b..92c3b55babe777c4cded8045ab3a7c47bdddb8f8 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -4,8 +4,8 @@ import ( "context" log "github.com/Sirupsen/logrus" - "github.com/actionscore/actions/pkg/api" scheme "github.com/actionscore/actions/pkg/client/clientset/versioned" + "github.com/actionscore/actions/pkg/controller/api" "github.com/actionscore/actions/pkg/handlers" k8s "github.com/actionscore/actions/pkg/kubernetes" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -18,13 +18,13 @@ type Controller interface { } type controller struct { - KubeClient kubernetes.Interface - ActionsClient scheme.Interface - DeploymentsInformer cache.SharedInformer - EventSourcesInformer cache.SharedInformer - Ctx context.Context - ActionsHandler handlers.Handler - EventSourcesHandler handlers.Handler + KubeClient kubernetes.Interface + ActionsClient scheme.Interface + DeploymentsInformer cache.SharedInformer + ComponentsInformer cache.SharedInformer + Ctx context.Context + ActionsHandler handlers.Handler + ComponentsHandler handlers.Handler } func NewController(kubeClient kubernetes.Interface, actionsClient scheme.Interface, config Config) *controller { @@ -37,14 +37,14 @@ func NewController(kubeClient kubernetes.Interface, actionsClient scheme.Interfa nil, nil, ), - EventSourcesInformer: k8s.EventSourcesIndexInformer( + ComponentsInformer: k8s.ComponentsIndexInformer( actionsClient, meta_v1.NamespaceAll, nil, nil, ), - ActionsHandler: handlers.NewActionsHandler(actionsClient, handlers.ActionsHandlerConfig{RuntimeImage: config.ActionsRuntimeImage, ImagePullSecretName: config.ImagePullSecretName}), - EventSourcesHandler: handlers.NewEventSourcesHandler(kubeClient), + ActionsHandler: handlers.NewActionsHandler(actionsClient, handlers.ActionsHandlerConfig{RuntimeImage: config.ActionsRuntimeImage, ImagePullSecretName: config.ImagePullSecretName}), + ComponentsHandler: handlers.NewComponentsHandler(kubeClient), } c.DeploymentsInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ @@ -55,18 +55,18 @@ func NewController(kubeClient kubernetes.Interface, actionsClient scheme.Interfa DeleteFunc: c.syncDeletedDeployment, }) - c.EventSourcesInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: c.syncEventSource, + c.ComponentsInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: c.syncComponent, UpdateFunc: func(_, newObj interface{}) { - c.syncEventSource(newObj) + c.syncComponent(newObj) }, }) return c } -func (c *controller) syncEventSource(obj interface{}) { - c.EventSourcesHandler.ObjectCreated(obj) +func (c *controller) syncComponent(obj interface{}) { + c.ComponentsHandler.ObjectCreated(obj) } func (c *controller) syncDeployment(obj interface{}) { @@ -91,7 +91,7 @@ func (c *controller) Run(ctx context.Context) { cancel() }() go func() { - c.EventSourcesInformer.Run(ctx.Done()) + c.ComponentsInformer.Run(ctx.Done()) cancel() }() apiSrv := api.NewAPIServer(c.ActionsClient) diff --git a/pkg/grpc/api.go b/pkg/grpc/api.go new file mode 100644 index 0000000000000000000000000000000000000000..0f61cf5147bef59e99279b6e32cd1f2895f7a1c3 --- /dev/null +++ b/pkg/grpc/api.go @@ -0,0 +1,113 @@ +package grpc + +import ( + "context" + + "github.com/actionscore/actions/pkg/actors" + + "github.com/golang/protobuf/ptypes/any" + "github.com/golang/protobuf/ptypes/empty" + + "github.com/actionscore/actions/pkg/channel" + "github.com/actionscore/actions/pkg/components" + "github.com/actionscore/actions/pkg/messaging" + pb "github.com/actionscore/actions/pkg/proto" +) + +type API interface { + CallActor(ctx context.Context, in *pb.CallActorEnvelope) (*pb.InvokeResponse, error) + CallRemoteApp(ctx context.Context, in *pb.CallRemoteAppEnvelope) (*pb.InvokeResponse, error) + CallLocal(ctx context.Context, in *pb.LocalCallEnvelope) (*pb.InvokeResponse, error) + UpdateComponent(ctx context.Context, in *pb.Component) (*empty.Empty, error) +} + +type api struct { + appChannel channel.AppChannel + actor actors.Actors + directMessaging messaging.DirectMessaging + componentsHandler components.ComponentHandler + id string +} + +func NewAPI(actionsID string, appChannel channel.AppChannel, directMessaging messaging.DirectMessaging, actor actors.Actors, componentHandler components.ComponentHandler) API { + return &api{ + appChannel: appChannel, + directMessaging: directMessaging, + componentsHandler: componentHandler, + actor: actor, + id: actionsID, + } +} + +func (a *api) CallRemoteApp(ctx context.Context, in *pb.CallRemoteAppEnvelope) (*pb.InvokeResponse, error) { + req := messaging.DirectMessageRequest{ + Data: in.Data.Value, + Method: in.Method, + Metadata: in.Metadata, + Target: in.Target, + } + + resp, err := a.directMessaging.Invoke(&req) + if err != nil { + return nil, err + } + + return &pb.InvokeResponse{ + Data: &any.Any{Value: resp.Data}, + Metadata: resp.Metadata, + }, nil +} + +func (a *api) CallLocal(ctx context.Context, in *pb.LocalCallEnvelope) (*pb.InvokeResponse, error) { + req := channel.InvokeRequest{ + Payload: in.Data.Value, + Method: in.Method, + Metadata: in.Metadata, + } + + resp, err := a.appChannel.InvokeMethod(&req) + if err != nil { + return nil, err + } + + return &pb.InvokeResponse{ + Data: &any.Any{Value: resp.Data}, + Metadata: resp.Metadata, + }, nil +} + +func (a *api) CallActor(ctx context.Context, in *pb.CallActorEnvelope) (*pb.InvokeResponse, error) { + req := actors.CallRequest{ + ActorType: in.ActorType, + ActorID: in.ActorID, + Data: in.Data.Value, + Method: in.Method, + } + + resp, err := a.actor.Call(&req) + if err != nil { + return nil, err + } + + return &pb.InvokeResponse{ + Data: &any.Any{Value: resp.Data}, + Metadata: map[string]string{}, + }, nil +} + +func (a *api) UpdateComponent(ctx context.Context, in *pb.Component) (*empty.Empty, error) { + c := components.Component{ + Metadata: components.ComponentMetadata{ + Name: in.Name, + }, + Spec: components.ComponentSpec{ + ConnectionInfo: in.Spec.ConnectionInfo, + Properties: in.Spec.Properties, + Type: in.Spec.Type, + }, + } + + a.componentsHandler.OnComponentUpdated(c) + + return &empty.Empty{}, nil +} diff --git a/pkg/grpc/config.go b/pkg/grpc/config.go new file mode 100644 index 0000000000000000000000000000000000000000..ded4b1287a0ec50db55b717db397aa06dd11e361 --- /dev/null +++ b/pkg/grpc/config.go @@ -0,0 +1,11 @@ +package grpc + +type ServerConfig struct { + Port int +} + +func NewServerConfig(port int) ServerConfig { + return ServerConfig{ + Port: port, + } +} diff --git a/pkg/grpc/grpc.go b/pkg/grpc/grpc.go new file mode 100644 index 0000000000000000000000000000000000000000..4842f87e47d78236694738d6b542f31b845e0874 --- /dev/null +++ b/pkg/grpc/grpc.go @@ -0,0 +1,58 @@ +package grpc + +import ( + "fmt" + "sync" + + "google.golang.org/grpc" + + "github.com/actionscore/actions/pkg/channel" + grpc_channel "github.com/actionscore/actions/pkg/channel/grpc" +) + +type GRPCManager struct { + AppClient *grpc.ClientConn + lock *sync.Mutex + connectionPool map[string]*grpc.ClientConn +} + +func NewGRPCManager() *GRPCManager { + return &GRPCManager{ + lock: &sync.Mutex{}, + connectionPool: map[string]*grpc.ClientConn{}, + } +} + +func (g *GRPCManager) CreateLocalChannel(port int) (channel.AppChannel, error) { + conn, err := g.GetGRPCConnection(fmt.Sprintf("127.0.0.1:%v", port)) + if err != nil { + return nil, fmt.Errorf("error establishing connection to app grpc on port %v: %s", port, err) + } + + g.AppClient = conn + ch := grpc_channel.CreateLocalChannel(port, conn) + return ch, nil +} + +func (g *GRPCManager) GetGRPCConnection(address string) (*grpc.ClientConn, error) { + if val, ok := g.connectionPool[address]; ok { + return val, nil + } + + g.lock.Lock() + if val, ok := g.connectionPool[address]; ok { + g.lock.Unlock() + return val, nil + } + + conn, err := grpc.Dial(address, grpc.WithInsecure()) + if err != nil { + g.lock.Unlock() + return nil, err + } + + g.connectionPool[address] = conn + g.lock.Unlock() + + return conn, nil +} diff --git a/pkg/grpc/server.go b/pkg/grpc/server.go new file mode 100644 index 0000000000000000000000000000000000000000..4296926f6dd20325ef0ace2b6e6628bb2650b140 --- /dev/null +++ b/pkg/grpc/server.go @@ -0,0 +1,45 @@ +package grpc + +import ( + "fmt" + "net" + + log "github.com/Sirupsen/logrus" + grpc_go "google.golang.org/grpc" + + pb "github.com/actionscore/actions/pkg/proto" +) + +type Server interface { + StartNonBlocking() error +} + +type server struct { + api API + config ServerConfig +} + +func NewServer(api API, config ServerConfig) Server { + return &server{ + api: api, + config: config, + } +} + +func (s *server) StartNonBlocking() error { + lis, err := net.Listen("tcp", fmt.Sprintf(":%v", s.config.Port)) + if err != nil { + return err + } + + server := grpc_go.NewServer() + pb.RegisterActionsServer(server, s.api) + + go func() { + if err := server.Serve(lis); err != nil { + log.Fatalf("gRPC serve error: %v", err) + } + }() + + return nil +} diff --git a/pkg/handlers/actions_handler.go b/pkg/handlers/actions_handler.go index e318398a30eec0af34a1c0baaafbae80695ba1f8..e9b1e8b821f7dcf10c488e16c3ab30d76f7c8333 100644 --- a/pkg/handlers/actions_handler.go +++ b/pkg/handlers/actions_handler.go @@ -21,6 +21,7 @@ const ( actionsEnabledAnnotationKey = "actions.io/enabled" actionsIDAnnotationKey = "actions.io/id" actionsProtocolAnnotationKey = "actions.io/protocol" + actionsConfigAnnotationKey = "actions.io/config" actionsEnabledAnnotationValue = "true" actionSidecarContainerName = "action" actionSidecarHTTPPortName = "actions-http" @@ -57,7 +58,7 @@ func (r *ActionsHandler) Init() error { return nil } -func (r *ActionsHandler) GetEventingSidecar(applicationPort, applicationProtocol, actionName, actionSidecarImage string) v1.Container { +func (r *ActionsHandler) GetEventingSidecar(applicationPort, applicationProtocol, actionName, config, actionSidecarImage string) v1.Container { return v1.Container{ Name: actionSidecarContainerName, Image: actionSidecarImage, @@ -72,9 +73,9 @@ func (r *ActionsHandler) GetEventingSidecar(applicationPort, applicationProtocol Name: actionSidecarGRPCPortName, }, }, - Command: []string{"/action"}, + Command: []string{"/actionsrt"}, Env: []v1.EnvVar{v1.EnvVar{Name: "HOST_IP", ValueFrom: &v1.EnvVarSource{FieldRef: &v1.ObjectFieldSelector{FieldPath: "status.podIP"}}}}, - Args: []string{"--mode", "kubernetes", "--action-http-port", fmt.Sprintf("%v", actionSidecarHTTPPort), "--action-grpc-port", fmt.Sprintf("%v", actionSidecarGRPCPort), "--app-port", applicationPort, "--action-id", actionName, "--api-address", apiAddress, "--protocol", applicationProtocol, "--placement-address", placementAddress}, + Args: []string{"--mode", "kubernetes", "--actions-http-port", fmt.Sprintf("%v", actionSidecarHTTPPort), "--actions-grpc-port", fmt.Sprintf("%v", actionSidecarGRPCPort), "--app-port", applicationPort, "--actions-id", actionName, "--control-plane-address", apiAddress, "--protocol", applicationProtocol, "--placement-address", placementAddress, "--config", config}, } } @@ -151,6 +152,14 @@ func (r *ActionsHandler) GetAppProtocol(deployment *appsv1.Deployment) string { return string(HTTPProtocol) } +func (r *ActionsHandler) GetAppConfig(deployment *appsv1.Deployment) string { + if val, ok := deployment.ObjectMeta.Annotations[actionsConfigAnnotationKey]; ok && val != "" { + return val + } + + return "" +} + func (r *ActionsHandler) IsAnnotatedForActions(deployment *appsv1.Deployment) bool { annotations := deployment.ObjectMeta.Annotations if annotations != nil { @@ -175,8 +184,9 @@ func (r *ActionsHandler) ActionEnabled(deployment *appsv1.Deployment) bool { func (r *ActionsHandler) EnableAction(deployment *appsv1.Deployment) error { appPort := r.GetApplicationPort(deployment.Spec.Template.Spec.Containers) appProtocol := r.GetAppProtocol(deployment) + config := r.GetAppConfig(deployment) actionName := r.GetActionName(deployment) - sidecar := r.GetEventingSidecar(appPort, appProtocol, actionName, r.Config.RuntimeImage) + sidecar := r.GetEventingSidecar(appPort, appProtocol, actionName, config, r.Config.RuntimeImage) deployment.Spec.Template.Spec.Containers = append(deployment.Spec.Template.Spec.Containers, sidecar) if r.Config.ImagePullSecretName != "" { diff --git a/pkg/handlers/components.go b/pkg/handlers/components.go new file mode 100644 index 0000000000000000000000000000000000000000..02f9f40e2e61dafaf7edc21d20b690c8a2c3182d --- /dev/null +++ b/pkg/handlers/components.go @@ -0,0 +1,108 @@ +package handlers + +import ( + "context" + "fmt" + "time" + + "google.golang.org/grpc" + + "k8s.io/apimachinery/pkg/labels" + + log "github.com/Sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + + components_v1alpha1 "github.com/actionscore/actions/pkg/apis/components/v1alpha1" + pb "github.com/actionscore/actions/pkg/proto" +) + +type ComponentsHandler struct { + kubeClient kubernetes.Interface +} + +func NewComponentsHandler(client kubernetes.Interface) *ComponentsHandler { + return &ComponentsHandler{ + kubeClient: client, + } +} + +func (c *ComponentsHandler) Init() error { + log.Info("ComponentsHandler.Init") + return nil +} + +func (c *ComponentsHandler) ObjectUpdated(old interface{}, new interface{}) { +} + +func (c *ComponentsHandler) ObjectDeleted(obj interface{}) { +} + +func (c *ComponentsHandler) ObjectCreated(obj interface{}) { + log.Info("Notified about a component update") + + component := obj.(*components_v1alpha1.Component) + err := c.publishComponentToActionsRuntimes(component) + if err != nil { + log.Errorf("Error from ObjectCreated: %s", err) + } +} + +func (c *ComponentsHandler) publishComponentToActionsRuntimes(component *components_v1alpha1.Component) error { + payload := pb.Component{ + Name: component.GetName(), + Spec: &pb.ComponentSpec{ + Type: component.Spec.Type, + ConnectionInfo: component.Spec.ConnectionInfo, + Properties: component.Spec.Properties, + }, + } + + services, err := c.kubeClient.CoreV1().Services(meta_v1.NamespaceAll).List(meta_v1.ListOptions{ + LabelSelector: labels.SelectorFromSet(map[string]string{actionsEnabledAnnotationKey: actionsEnabledAnnotationValue}).String(), + }) + if err != nil { + return err + } + + for _, s := range services.Items { + svcName := s.GetName() + + log.Infof("Updating actions pod selected by service: %s", svcName) + endpoints, err := c.kubeClient.CoreV1().Endpoints(s.GetNamespace()).Get(svcName, meta_v1.GetOptions{}) + if err != nil { + log.Errorf("Error getting endpoints for service %s: %s", svcName, err) + continue + } + go c.publishComponentToService(payload, endpoints) + } + + return nil +} + +func (c *ComponentsHandler) publishComponentToService(component pb.Component, endpoints *corev1.Endpoints) { + if endpoints != nil && len(endpoints.Subsets) > 0 { + for _, a := range endpoints.Subsets[0].Addresses { + address := fmt.Sprintf("%s:%s", a.IP, fmt.Sprintf("%v", actionSidecarGRPCPort)) + go c.updateActionsRuntime(component, address) + } + } +} + +func (c *ComponentsHandler) updateActionsRuntime(component pb.Component, address string) { + conn, err := grpc.Dial(address, grpc.WithInsecure()) + if err != nil { + log.Errorf("gRPC connection failure: %s", err) + return + } + defer conn.Close() + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + + client := pb.NewActionsClient(conn) + _, err = client.UpdateComponent(ctx, &component) + if err != nil { + log.Warnf("Error updating Actions Runtime with component: %s", err) + } +} diff --git a/pkg/handlers/eventsources_handler.go b/pkg/handlers/eventsources_handler.go deleted file mode 100644 index 347d56b6b5ec632ef26d82c255df5e0d8d3de813..0000000000000000000000000000000000000000 --- a/pkg/handlers/eventsources_handler.go +++ /dev/null @@ -1,122 +0,0 @@ -package handlers - -import ( - "context" - "encoding/json" - "fmt" - "time" - - "github.com/golang/protobuf/ptypes/any" - "google.golang.org/grpc" - - "k8s.io/apimachinery/pkg/labels" - - log "github.com/Sirupsen/logrus" - corev1 "k8s.io/api/core/v1" - meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - - actions_v1alpha1 "github.com/actionscore/actions/pkg/apis/eventing/v1alpha1" - pb "github.com/actionscore/actions/pkg/proto" -) - -type eventSourcePayload struct { - Name string `json:"name"` - Spec eventSourceSpec `json:"spec"` -} - -type eventSourceSpec struct { - Type string `json:"type"` - ConnectionInfo interface{} `json:"connectionInfo"` -} - -type EventSourcesHandler struct { - KubeClient kubernetes.Interface -} - -func NewEventSourcesHandler(client kubernetes.Interface) *EventSourcesHandler { - return &EventSourcesHandler{ - KubeClient: client, - } -} - -func (e *EventSourcesHandler) Init() error { - log.Info("EventSourcesHandler.Init") - return nil -} - -func (r *EventSourcesHandler) ObjectUpdated(old interface{}, new interface{}) { -} - -func (r *EventSourcesHandler) ObjectDeleted(obj interface{}) { -} - -func (r *EventSourcesHandler) ObjectCreated(obj interface{}) { - log.Info("Notified about an event source update") - - eventSource := obj.(*actions_v1alpha1.EventSource) - err := r.PublishEventSourceToActions(eventSource) - if err != nil { - log.Errorf("Error from EventSourcesHandler: %s", err) - } -} - -func (r *EventSourcesHandler) PublishEventSourceToActions(eventSource *actions_v1alpha1.EventSource) error { - payload := pb.EventSource{ - Name: eventSource.GetName(), - Spec: &pb.EventSourceSpec{ - Type: eventSource.Spec.Type, - }, - } - - b, err := json.Marshal(eventSource.Spec.ConnectionInfo) - if err != nil { - return err - } - - payload.Spec.ConnectionInfo = &any.Any{Value: b} - - services, err := r.KubeClient.CoreV1().Services(meta_v1.NamespaceAll).List(meta_v1.ListOptions{ - LabelSelector: labels.SelectorFromSet(map[string]string{actionsEnabledAnnotationKey: actionsEnabledAnnotationValue}).String(), - }) - if err != nil { - return err - } - - for _, s := range services.Items { - svcName := s.GetName() - - log.Infof("Updating actions pod selected by service: %s", svcName) - endpoints, err := r.KubeClient.CoreV1().Endpoints(s.GetNamespace()).Get(svcName, meta_v1.GetOptions{}) - if err != nil { - log.Errorf("Error getting endpoints for service %s: %s", svcName, err) - continue - } - go r.PublishEventSourceToService(payload, endpoints) - } - - return nil -} - -func (r *EventSourcesHandler) PublishEventSourceToService(eventSource pb.EventSource, endpoints *corev1.Endpoints) { - if endpoints != nil && len(endpoints.Subsets) > 0 { - for _, a := range endpoints.Subsets[0].Addresses { - address := fmt.Sprintf("%s:%s", a.IP, fmt.Sprintf("%v", actionSidecarGRPCPort)) - go r.sendEventSourceToPod(eventSource, address) - } - } -} - -func (r *EventSourcesHandler) sendEventSourceToPod(eventSource pb.EventSource, address string) { - conn, err := grpc.Dial(address, grpc.WithInsecure()) - if err != nil { - log.Errorf("gRPC connection failure: %s", err) - return - } - defer conn.Close() - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - - c := pb.NewActionsClient(conn) - c.UpdateEventSource(ctx, &eventSource) -} diff --git a/pkg/http/api.go b/pkg/http/api.go new file mode 100644 index 0000000000000000000000000000000000000000..8b06bcc0654f3914480f1ee2ff75b14c8a3a881c --- /dev/null +++ b/pkg/http/api.go @@ -0,0 +1,339 @@ +package http + +import ( + "fmt" + "strconv" + "strings" + + jsoniter "github.com/json-iterator/go" + + "github.com/actionscore/actions/pkg/actors" + "github.com/actionscore/actions/pkg/channel" + "github.com/actionscore/actions/pkg/channel/http" + "github.com/actionscore/actions/pkg/components/state" + "github.com/actionscore/actions/pkg/messaging" + routing "github.com/qiangxue/fasthttp-routing" +) + +type API interface { + APIEndpoints() []Endpoint +} + +type api struct { + endpoints []Endpoint + directMessaging messaging.DirectMessaging + appChannel channel.AppChannel + stateStore state.StateStore + json jsoniter.API + actor actors.Actors + id string +} + +const ( + apiVersionV1 = "v1.0" + idParam = "id" + methodParam = "method" + actorTypeParam = "actorType" + actorIDParam = "actorId" + stateKeyParam = "key" +) + +func NewAPI(actionID string, appChannel channel.AppChannel, directMessaging messaging.DirectMessaging, stateStore state.StateStore, actor actors.Actors) API { + api := &api{ + appChannel: appChannel, + directMessaging: directMessaging, + stateStore: stateStore, + json: jsoniter.ConfigFastest, + actor: actor, + id: actionID, + } + api.endpoints = append(api.endpoints, api.constructStateEndpoints()...) + api.endpoints = append(api.endpoints, api.constructPubSubEndpoints()...) + api.endpoints = append(api.endpoints, api.constructActorEndpoints()...) + api.endpoints = append(api.endpoints, api.constructDirectMessagingEndpoints()...) + api.endpoints = append(api.endpoints, api.constructMetadataEndpoints()...) + + return api +} + +func (a *api) APIEndpoints() []Endpoint { + return a.endpoints +} + +func (a *api) constructStateEndpoints() []Endpoint { + return []Endpoint{ + Endpoint{ + Methods: []string{http.Get}, + Route: "state/", + Version: apiVersionV1, + Handler: a.onGetState, + }, + Endpoint{ + Methods: []string{http.Post}, + Route: "state", + Version: apiVersionV1, + Handler: a.onPostState, + }, + } +} + +func (a *api) constructPubSubEndpoints() []Endpoint { + return []Endpoint{} +} + +func (a *api) constructDirectMessagingEndpoints() []Endpoint { + return []Endpoint{ + Endpoint{ + Methods: []string{http.Get, http.Post, http.Delete, http.Put}, + Route: "actions//", + Version: apiVersionV1, + Handler: a.onDirectMessage, + }, + Endpoint{ + Methods: []string{http.Post}, + Route: "invoke/*", + Version: apiVersionV1, + Handler: a.onInvokeLocal, + }, + } +} + +func (a *api) constructActorEndpoints() []Endpoint { + return []Endpoint{ + Endpoint{ + Methods: []string{http.Get, http.Post, http.Delete, http.Put}, + Route: "actors///", + Version: apiVersionV1, + Handler: a.onDirectActorMessage, + }, + Endpoint{ + Methods: []string{http.Post, http.Put}, + Route: "actors///state", + Version: apiVersionV1, + Handler: a.OnSaveActorState, + }, + Endpoint{ + Methods: []string{http.Get}, + Route: "actors///state", + Version: apiVersionV1, + Handler: a.onGetActorState, + }, + } +} + +func (a *api) constructMetadataEndpoints() []Endpoint { + return []Endpoint{ + Endpoint{ + Methods: []string{http.Get}, + Route: "metadata", + Version: apiVersionV1, + Handler: a.onGetMetadata, + }, + } +} + +func (a *api) onGetState(c *routing.Context) error { + if a.stateStore == nil { + respondWithError(c.RequestCtx, 400, "error: state store not found") + return nil + } + + key := c.Param(stateKeyParam) + req := state.GetRequest{ + Key: a.getModifiedStateKey(key), + } + + resp, err := a.stateStore.Get(&req) + if err != nil { + respondWithError(c.RequestCtx, 500, fmt.Sprintf("error getting state: %s", err)) + return nil + } + + respondWithJSON(c.RequestCtx, 200, resp.Data) + return nil +} + +func (a *api) onPostState(c *routing.Context) error { + if a.stateStore == nil { + respondWithError(c.RequestCtx, 400, "error: state store not found") + return nil + } + + reqs := []state.SetRequest{} + err := a.json.Unmarshal(c.PostBody(), &reqs) + if err != nil { + respondWithError(c.RequestCtx, 400, "error: malformed json request") + return nil + } + + for i, r := range reqs { + reqs[i].Key = a.getModifiedStateKey(r.Key) + } + + err = a.stateStore.BulkSet(reqs) + if err != nil { + respondWithError(c.RequestCtx, 500, fmt.Sprintf("error saving state: %s", err)) + return nil + } + + respondEmpty(c.RequestCtx, 201) + + return nil +} + +func (a *api) getModifiedStateKey(key string) string { + if a.id != "" { + return fmt.Sprintf("%s-%s", a.id, key) + } + + return key +} + +func (a *api) onDirectMessage(c *routing.Context) error { + targetID := c.Param(idParam) + method := c.Param(methodParam) + body := c.PostBody() + verb := string(c.Method()) + + req := messaging.DirectMessageRequest{ + Data: body, + Method: method, + Metadata: map[string]string{http.HTTPVerb: verb}, + Target: targetID, + } + + resp, err := a.directMessaging.Invoke(&req) + if err != nil { + respondWithError(c.RequestCtx, 500, err.Error()) + } else { + statusCode := GetStatusCodeFromMetadata(resp.Metadata) + respondWithJSON(c.RequestCtx, statusCode, resp.Data) + } + + return nil +} + +func (a *api) onInvokeLocal(c *routing.Context) error { + method := string(c.Path())[len(string(c.Path()))-strings.Index(string(c.Path()), "invoke/"):] + body := c.PostBody() + verb := string(c.Method()) + + req := channel.InvokeRequest{ + Metadata: map[string]string{http.HTTPVerb: verb}, + Payload: body, + Method: method, + } + resp, err := a.appChannel.InvokeMethod(&req) + if err != nil { + respondWithError(c.RequestCtx, 500, err.Error()) + } else { + statusCode := GetStatusCodeFromMetadata(resp.Metadata) + respondWithJSON(c.RequestCtx, statusCode, resp.Data) + } + + return nil +} + +func (a *api) onDirectActorMessage(c *routing.Context) error { + if a.actor == nil { + respondWithError(c.RequestCtx, 400, "actors not initialized") + return nil + } + + actorType := c.Param(actorTypeParam) + actorID := c.Param(actorIDParam) + method := c.Param(methodParam) + body := c.PostBody() + + req := actors.CallRequest{ + ActorID: actorID, + ActorType: actorType, + Method: method, + Metadata: map[string]string{}, + Data: body, + } + + resp, err := a.actor.Call(&req) + if err != nil { + respondWithError(c.RequestCtx, 500, err.Error()) + } else { + respondWithJSON(c.RequestCtx, 200, resp.Data) + } + + return nil +} + +func (a *api) OnSaveActorState(c *routing.Context) error { + if a.actor == nil { + respondWithError(c.RequestCtx, 400, "actors not initialized") + return nil + } + + actorType := c.Param(actorTypeParam) + actorID := c.Param(actorIDParam) + body := c.PostBody() + + var state actors.SaveStateRequest + err := a.json.Unmarshal(body, &state) + if err != nil { + respondWithError(c.RequestCtx, 400, "error: malformed json request") + return nil + } + + req := actors.SaveStateRequest{ + ActorID: actorID, + ActorType: actorType, + Data: body, + } + + err = a.actor.SaveState(&req) + if err != nil { + respondWithError(c.RequestCtx, 500, err.Error()) + } else { + respondEmpty(c.RequestCtx, 201) + } + + return nil +} + +func (a *api) onGetActorState(c *routing.Context) error { + if a.actor == nil { + respondWithError(c.RequestCtx, 400, "actors not initialized") + return nil + } + + actorType := c.Param(actorTypeParam) + actorID := c.Param(actorIDParam) + + req := actors.GetStateRequest{ + ActorType: actorType, + ActorID: actorID, + } + + resp, err := a.actor.GetState(&req) + if err != nil { + respondWithError(c.RequestCtx, 500, err.Error()) + } else { + respondWithJSON(c.RequestCtx, 200, resp.Data) + } + + return nil +} + +func (a *api) onGetMetadata(c *routing.Context) error { + //TODO: implement + return nil +} + +// GetStatusCodeFromMetadata extracts the http status code from the metadata if it exists +func GetStatusCodeFromMetadata(metadata map[string]string) int { + code := metadata[http.HTTPStatusCode] + if code != "" { + statusCode, err := strconv.Atoi(code) + if err == nil { + return statusCode + } + } + + return 200 +} diff --git a/pkg/http/config.go b/pkg/http/config.go new file mode 100644 index 0000000000000000000000000000000000000000..1d59cc6a254dcdc8d2cfc42720937e1c12b0c118 --- /dev/null +++ b/pkg/http/config.go @@ -0,0 +1,13 @@ +package http + +type ServerConfig struct { + AllowedOrigins string + Port int +} + +func NewServerConfig(port int, allowedOrigins string) ServerConfig { + return ServerConfig{ + AllowedOrigins: allowedOrigins, + Port: port, + } +} diff --git a/pkg/http/endpoint.go b/pkg/http/endpoint.go new file mode 100644 index 0000000000000000000000000000000000000000..8df47e555b58a69a8fc831b3f3ac592ae2419d9f --- /dev/null +++ b/pkg/http/endpoint.go @@ -0,0 +1,10 @@ +package http + +import routing "github.com/qiangxue/fasthttp-routing" + +type Endpoint struct { + Methods []string + Route string + Version string + Handler func(c *routing.Context) error +} diff --git a/pkg/http/responses.go b/pkg/http/responses.go new file mode 100644 index 0000000000000000000000000000000000000000..86383be6d5b5aa2b8f27b127d12ff100303b6685 --- /dev/null +++ b/pkg/http/responses.go @@ -0,0 +1,43 @@ +package http + +import ( + "bytes" + "encoding/json" + + "github.com/valyala/fasthttp" +) + +func respondWithJSON(ctx *fasthttp.RequestCtx, code int, obj []byte) { + ctx.Response.Header.SetContentType("application/json") + ctx.Response.SetStatusCode(code) + ctx.Response.SetBody(obj) +} + +func respondWithString(ctx *fasthttp.RequestCtx, code int, obj string) { + ctx.Response.Header.SetContentType("application/json") + ctx.Response.SetStatusCode(code) + ctx.Response.SetBodyString(obj) +} + +func respondWithError(ctx *fasthttp.RequestCtx, code int, message string) { + json, _ := serializeToJSON(map[string]string{"error": message}) + respondWithJSON(ctx, code, json) +} + +func respondEmpty(ctx *fasthttp.RequestCtx, code int) { + ctx.Response.SetBody(nil) + ctx.Response.SetStatusCode(code) +} + +func serializeToJSON(obj interface{}) ([]byte, error) { + buffer := &bytes.Buffer{} + encoder := json.NewEncoder(buffer) + encoder.SetEscapeHTML(false) + err := encoder.Encode(obj) + if err != nil { + return nil, err + } + + bytes := buffer.Bytes() + return bytes, nil +} diff --git a/pkg/http/server.go b/pkg/http/server.go new file mode 100644 index 0000000000000000000000000000000000000000..5a49bbf0e3d2b2791bdfd031d328e6ed3b247db7 --- /dev/null +++ b/pkg/http/server.go @@ -0,0 +1,58 @@ +package http + +import ( + "fmt" + "strings" + + cors "github.com/AdhityaRamadhanus/fasthttpcors" + log "github.com/Sirupsen/logrus" + routing "github.com/qiangxue/fasthttp-routing" + "github.com/valyala/fasthttp" +) + +type Server interface { + StartNonBlocking() +} + +type server struct { + config ServerConfig + api API +} + +func NewServer(api API, config ServerConfig) Server { + return &server{ + api: api, + config: config, + } +} + +func (s *server) StartNonBlocking() { + endpoints := s.api.APIEndpoints() + router := s.getRouter(endpoints) + origins := strings.Split(s.config.AllowedOrigins, ",") + corsHandler := s.getCorsHandler(origins) + + go func() { + log.Fatal(fasthttp.ListenAndServe(fmt.Sprintf(":%v", s.config.Port), corsHandler.CorsMiddleware(router.HandleRequest))) + }() +} + +func (s *server) getCorsHandler(allowedOrigins []string) *cors.CorsHandler { + return cors.NewCorsHandler(cors.Options{ + AllowedOrigins: allowedOrigins, + Debug: false, + }) +} + +func (s *server) getRouter(endpoints []Endpoint) *routing.Router { + router := routing.New() + + for _, e := range endpoints { + methods := strings.Join(e.Methods, ",") + path := fmt.Sprintf("/%s/%s", e.Version, e.Route) + + router.To(methods, path, e.Handler) + } + + return router +} diff --git a/pkg/kubernetes/informers.go b/pkg/kubernetes/informers.go index 6926fdddbea86e82acc06e6e096d1d7eb845b6f2..24c5c773359eca9e01bef40074b30fc7856bb8f2 100644 --- a/pkg/kubernetes/informers.go +++ b/pkg/kubernetes/informers.go @@ -1,7 +1,7 @@ package kubernetes import ( - actions_v1alpha1 "github.com/actionscore/actions/pkg/apis/eventing/v1alpha1" + components_v1alpha1 "github.com/actionscore/actions/pkg/apis/components/v1alpha1" scheme "github.com/actionscore/actions/pkg/client/clientset/versioned" appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -47,13 +47,13 @@ func DeploymentsIndexInformer( ) } -func EventSourcesIndexInformer( +func ComponentsIndexInformer( client scheme.Interface, namespace string, fieldSelector fields.Selector, labelSelector labels.Selector, ) cache.SharedIndexInformer { - actionsClient := client.EventingV1alpha1().EventSources(namespace) + actionsClient := client.ComponentsV1alpha1().Components(namespace) return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { @@ -75,7 +75,7 @@ func EventSourcesIndexInformer( return actionsClient.Watch(options) }, }, - &actions_v1alpha1.EventSource{}, + &components_v1alpha1.Component{}, 0, cache.Indexers{}, ) diff --git a/pkg/messaging/direct_messaging.go b/pkg/messaging/direct_messaging.go new file mode 100644 index 0000000000000000000000000000000000000000..6ed222c608529c0cdee913fb179e4a8d29988f00 --- /dev/null +++ b/pkg/messaging/direct_messaging.go @@ -0,0 +1,114 @@ +package messaging + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/golang/protobuf/ptypes/any" + + "github.com/actionscore/actions/pkg/modes" + + "github.com/actionscore/actions/pkg/channel" + "google.golang.org/grpc" + + pb "github.com/actionscore/actions/pkg/proto" +) + +type DirectMessaging interface { + Invoke(req *DirectMessageRequest) (*DirectMessageResponse, error) +} + +type directMessaging struct { + appChannel channel.AppChannel + connectionCreatorFn func(address string) (*grpc.ClientConn, error) + actionsID string + mode modes.ActionsMode + grpcPort int +} + +func NewDirectMessaging(actionsID string, port int, mode modes.ActionsMode, appChannel channel.AppChannel, grpcConnectionFn func(address string) (*grpc.ClientConn, error)) DirectMessaging { + return &directMessaging{ + appChannel: appChannel, + connectionCreatorFn: grpcConnectionFn, + actionsID: actionsID, + mode: mode, + grpcPort: port, + } +} + +func (d *directMessaging) Invoke(req *DirectMessageRequest) (*DirectMessageResponse, error) { + var invokeFn func(*DirectMessageRequest) (*DirectMessageResponse, error) + + if req.Target == d.actionsID { + invokeFn = d.invokeLocal + } else { + invokeFn = d.invokeRemote + } + + return invokeFn(req) +} + +func (d *directMessaging) invokeLocal(req *DirectMessageRequest) (*DirectMessageResponse, error) { + if d.appChannel == nil { + return nil, errors.New("cannot invoke local endpoint: app channel not initialized") + } + + localInvokeReq := channel.InvokeRequest{ + Metadata: req.Metadata, + Method: req.Method, + Payload: req.Data, + } + + resp, err := d.appChannel.InvokeMethod(&localInvokeReq) + if err != nil { + return nil, err + } + + return &DirectMessageResponse{ + Data: resp.Data, + Metadata: resp.Metadata, + }, nil +} + +func (d *directMessaging) invokeRemote(req *DirectMessageRequest) (*DirectMessageResponse, error) { + address, err := d.getAddress(req.Target) + if err != nil { + return nil, err + } + + conn, err := d.connectionCreatorFn(address) + if err != nil { + return nil, err + } + + msg := pb.LocalCallEnvelope{ + Data: &any.Any{Value: req.Data}, + Metadata: req.Metadata, + Method: req.Method, + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*1) + defer cancel() + + client := pb.NewActionsClient(conn) + resp, err := client.CallLocal(ctx, &msg) + if err != nil { + return nil, err + } + + return &DirectMessageResponse{ + Data: resp.Data.Value, + Metadata: resp.Metadata, + }, nil +} + +func (d *directMessaging) getAddress(target string) (string, error) { + switch d.mode { + case modes.KubernetesMode: + return fmt.Sprintf("%s-action.default.svc.cluster.local:%v", target, d.grpcPort), nil + default: + return "", fmt.Errorf("remote calls not supported for %s mode", string(d.mode)) + } +} diff --git a/pkg/messaging/request.go b/pkg/messaging/request.go new file mode 100644 index 0000000000000000000000000000000000000000..ae02f5bca0c0ce981cb9aa668fd701b1a48ff88a --- /dev/null +++ b/pkg/messaging/request.go @@ -0,0 +1,9 @@ +package messaging + +type DirectMessageRequest struct { + Target string `json:"target"` + Method string `json:"method"` + From string `json:"from,omitempty"` + Metadata map[string]string `json:"metadata"` + Data []byte `json:"data,omitempty"` +} diff --git a/pkg/messaging/response.go b/pkg/messaging/response.go new file mode 100644 index 0000000000000000000000000000000000000000..4bf42d4e53951e09b736d101204abb3df014534f --- /dev/null +++ b/pkg/messaging/response.go @@ -0,0 +1,6 @@ +package messaging + +type DirectMessageResponse struct { + Data []byte `json:"data"` + Metadata map[string]string `json:"metadata"` +} diff --git a/pkg/modes/modes.go b/pkg/modes/modes.go new file mode 100644 index 0000000000000000000000000000000000000000..1bb9133d2d1f03d61f48f3bbd3f7c11ba80c4964 --- /dev/null +++ b/pkg/modes/modes.go @@ -0,0 +1,8 @@ +package modes + +type ActionsMode string + +var ( + KubernetesMode ActionsMode = "kubernetes" + StandaloneMode ActionsMode = "standalone" +) diff --git a/pkg/placement/placement.go b/pkg/placement/placement.go index 0e2e79258dd21b8ad9d589120e70834e91aa0c3c..62495718584bc522d25b6f8066a72ed41bda67e9 100644 --- a/pkg/placement/placement.go +++ b/pkg/placement/placement.go @@ -89,7 +89,7 @@ func (p *PlacementService) PerformTablesUpdate() { for _, host := range p.hosts { err := host.Send(&o) if err != nil { - log.Errorf("Error updating host on lock operation: %s", err) + log.Errorf("error updating host on lock operation: %s", err) continue } } @@ -127,7 +127,7 @@ func (p *PlacementService) PerformTablesUpdate() { for _, host := range p.hosts { err := host.Send(&o) if err != nil { - log.Errorf("Error updating host on update operation: %s", err) + log.Errorf("error updating host on update operation: %s", err) continue } } @@ -138,7 +138,7 @@ func (p *PlacementService) PerformTablesUpdate() { for _, host := range p.hosts { err := host.Send(&o) if err != nil { - log.Errorf("Error updating host on unlock operation: %s", err) + log.Errorf("error updating host on unlock operation: %s", err) continue } } diff --git a/pkg/proto/actions.pb.go b/pkg/proto/actions.pb.go index 19213a407fa18e26586194e09f9636cee58fb377..32dab961176bbb081f7ff3e9d2999f588aa14900 100644 --- a/pkg/proto/actions.pb.go +++ b/pkg/proto/actions.pb.go @@ -24,92 +24,336 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package -type InvokeEnvelope struct { - Data *any.Any `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` +type LocalCallEnvelope struct { + Data *any.Any `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"` + Metadata map[string]string `protobuf:"bytes,3,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *LocalCallEnvelope) Reset() { *m = LocalCallEnvelope{} } +func (m *LocalCallEnvelope) String() string { return proto.CompactTextString(m) } +func (*LocalCallEnvelope) ProtoMessage() {} +func (*LocalCallEnvelope) Descriptor() ([]byte, []int) { + return fileDescriptor_eeb49063df94c2b8, []int{0} +} + +func (m *LocalCallEnvelope) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_LocalCallEnvelope.Unmarshal(m, b) +} +func (m *LocalCallEnvelope) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_LocalCallEnvelope.Marshal(b, m, deterministic) +} +func (m *LocalCallEnvelope) XXX_Merge(src proto.Message) { + xxx_messageInfo_LocalCallEnvelope.Merge(m, src) +} +func (m *LocalCallEnvelope) XXX_Size() int { + return xxx_messageInfo_LocalCallEnvelope.Size(m) +} +func (m *LocalCallEnvelope) XXX_DiscardUnknown() { + xxx_messageInfo_LocalCallEnvelope.DiscardUnknown(m) +} + +var xxx_messageInfo_LocalCallEnvelope proto.InternalMessageInfo + +func (m *LocalCallEnvelope) GetData() *any.Any { + if m != nil { + return m.Data + } + return nil +} + +func (m *LocalCallEnvelope) GetMethod() string { + if m != nil { + return m.Method + } + return "" +} + +func (m *LocalCallEnvelope) GetMetadata() map[string]string { + if m != nil { + return m.Metadata + } + return nil +} + +type CallRemoteAppEnvelope struct { + Target string `protobuf:"bytes,1,opt,name=target,proto3" json:"target,omitempty"` + Data *any.Any `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + Method string `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"` + Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CallRemoteAppEnvelope) Reset() { *m = CallRemoteAppEnvelope{} } +func (m *CallRemoteAppEnvelope) String() string { return proto.CompactTextString(m) } +func (*CallRemoteAppEnvelope) ProtoMessage() {} +func (*CallRemoteAppEnvelope) Descriptor() ([]byte, []int) { + return fileDescriptor_eeb49063df94c2b8, []int{1} +} + +func (m *CallRemoteAppEnvelope) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CallRemoteAppEnvelope.Unmarshal(m, b) +} +func (m *CallRemoteAppEnvelope) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CallRemoteAppEnvelope.Marshal(b, m, deterministic) +} +func (m *CallRemoteAppEnvelope) XXX_Merge(src proto.Message) { + xxx_messageInfo_CallRemoteAppEnvelope.Merge(m, src) +} +func (m *CallRemoteAppEnvelope) XXX_Size() int { + return xxx_messageInfo_CallRemoteAppEnvelope.Size(m) +} +func (m *CallRemoteAppEnvelope) XXX_DiscardUnknown() { + xxx_messageInfo_CallRemoteAppEnvelope.DiscardUnknown(m) +} + +var xxx_messageInfo_CallRemoteAppEnvelope proto.InternalMessageInfo + +func (m *CallRemoteAppEnvelope) GetTarget() string { + if m != nil { + return m.Target + } + return "" +} + +func (m *CallRemoteAppEnvelope) GetData() *any.Any { + if m != nil { + return m.Data + } + return nil +} + +func (m *CallRemoteAppEnvelope) GetMethod() string { + if m != nil { + return m.Method + } + return "" +} + +func (m *CallRemoteAppEnvelope) GetMetadata() map[string]string { + if m != nil { + return m.Metadata + } + return nil +} + +type CallActorEnvelope struct { + ActorID string `protobuf:"bytes,1,opt,name=actorID,proto3" json:"actorID,omitempty"` + ActorType string `protobuf:"bytes,2,opt,name=actorType,proto3" json:"actorType,omitempty"` + Method string `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"` + Data *any.Any `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } -func (m *InvokeEnvelope) Reset() { *m = InvokeEnvelope{} } -func (m *InvokeEnvelope) String() string { return proto.CompactTextString(m) } -func (*InvokeEnvelope) ProtoMessage() {} -func (*InvokeEnvelope) Descriptor() ([]byte, []int) { - return fileDescriptor_eeb49063df94c2b8, []int{0} +func (m *CallActorEnvelope) Reset() { *m = CallActorEnvelope{} } +func (m *CallActorEnvelope) String() string { return proto.CompactTextString(m) } +func (*CallActorEnvelope) ProtoMessage() {} +func (*CallActorEnvelope) Descriptor() ([]byte, []int) { + return fileDescriptor_eeb49063df94c2b8, []int{2} } -func (m *InvokeEnvelope) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_InvokeEnvelope.Unmarshal(m, b) +func (m *CallActorEnvelope) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CallActorEnvelope.Unmarshal(m, b) } -func (m *InvokeEnvelope) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_InvokeEnvelope.Marshal(b, m, deterministic) +func (m *CallActorEnvelope) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CallActorEnvelope.Marshal(b, m, deterministic) } -func (m *InvokeEnvelope) XXX_Merge(src proto.Message) { - xxx_messageInfo_InvokeEnvelope.Merge(m, src) +func (m *CallActorEnvelope) XXX_Merge(src proto.Message) { + xxx_messageInfo_CallActorEnvelope.Merge(m, src) } -func (m *InvokeEnvelope) XXX_Size() int { - return xxx_messageInfo_InvokeEnvelope.Size(m) +func (m *CallActorEnvelope) XXX_Size() int { + return xxx_messageInfo_CallActorEnvelope.Size(m) } -func (m *InvokeEnvelope) XXX_DiscardUnknown() { - xxx_messageInfo_InvokeEnvelope.DiscardUnknown(m) +func (m *CallActorEnvelope) XXX_DiscardUnknown() { + xxx_messageInfo_CallActorEnvelope.DiscardUnknown(m) } -var xxx_messageInfo_InvokeEnvelope proto.InternalMessageInfo +var xxx_messageInfo_CallActorEnvelope proto.InternalMessageInfo -func (m *InvokeEnvelope) GetData() *any.Any { +func (m *CallActorEnvelope) GetActorID() string { + if m != nil { + return m.ActorID + } + return "" +} + +func (m *CallActorEnvelope) GetActorType() string { + if m != nil { + return m.ActorType + } + return "" +} + +func (m *CallActorEnvelope) GetMethod() string { + if m != nil { + return m.Method + } + return "" +} + +func (m *CallActorEnvelope) GetData() *any.Any { if m != nil { return m.Data } return nil } -type EventSource struct { - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Spec *EventSourceSpec `protobuf:"bytes,2,opt,name=spec,proto3" json:"spec,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` +type InvokeResponse struct { + Data *any.Any `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + Metadata map[string]string `protobuf:"bytes,2,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (m *EventSource) Reset() { *m = EventSource{} } -func (m *EventSource) String() string { return proto.CompactTextString(m) } -func (*EventSource) ProtoMessage() {} -func (*EventSource) Descriptor() ([]byte, []int) { - return fileDescriptor_eeb49063df94c2b8, []int{1} +func (m *InvokeResponse) Reset() { *m = InvokeResponse{} } +func (m *InvokeResponse) String() string { return proto.CompactTextString(m) } +func (*InvokeResponse) ProtoMessage() {} +func (*InvokeResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_eeb49063df94c2b8, []int{3} +} + +func (m *InvokeResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_InvokeResponse.Unmarshal(m, b) +} +func (m *InvokeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_InvokeResponse.Marshal(b, m, deterministic) +} +func (m *InvokeResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_InvokeResponse.Merge(m, src) +} +func (m *InvokeResponse) XXX_Size() int { + return xxx_messageInfo_InvokeResponse.Size(m) +} +func (m *InvokeResponse) XXX_DiscardUnknown() { + xxx_messageInfo_InvokeResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_InvokeResponse proto.InternalMessageInfo + +func (m *InvokeResponse) GetData() *any.Any { + if m != nil { + return m.Data + } + return nil } -func (m *EventSource) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_EventSource.Unmarshal(m, b) +func (m *InvokeResponse) GetMetadata() map[string]string { + if m != nil { + return m.Metadata + } + return nil } -func (m *EventSource) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_EventSource.Marshal(b, m, deterministic) + +type Component struct { + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Spec *ComponentSpec `protobuf:"bytes,2,opt,name=spec,proto3" json:"spec,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (m *EventSource) XXX_Merge(src proto.Message) { - xxx_messageInfo_EventSource.Merge(m, src) + +func (m *Component) Reset() { *m = Component{} } +func (m *Component) String() string { return proto.CompactTextString(m) } +func (*Component) ProtoMessage() {} +func (*Component) Descriptor() ([]byte, []int) { + return fileDescriptor_eeb49063df94c2b8, []int{4} } -func (m *EventSource) XXX_Size() int { - return xxx_messageInfo_EventSource.Size(m) + +func (m *Component) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Component.Unmarshal(m, b) } -func (m *EventSource) XXX_DiscardUnknown() { - xxx_messageInfo_EventSource.DiscardUnknown(m) +func (m *Component) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Component.Marshal(b, m, deterministic) +} +func (m *Component) XXX_Merge(src proto.Message) { + xxx_messageInfo_Component.Merge(m, src) +} +func (m *Component) XXX_Size() int { + return xxx_messageInfo_Component.Size(m) +} +func (m *Component) XXX_DiscardUnknown() { + xxx_messageInfo_Component.DiscardUnknown(m) } -var xxx_messageInfo_EventSource proto.InternalMessageInfo +var xxx_messageInfo_Component proto.InternalMessageInfo -func (m *EventSource) GetName() string { +func (m *Component) GetName() string { if m != nil { return m.Name } return "" } -func (m *EventSource) GetSpec() *EventSourceSpec { +func (m *Component) GetSpec() *ComponentSpec { if m != nil { return m.Spec } return nil } +type ComponentSpec struct { + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + ConnectionInfo map[string]string `protobuf:"bytes,2,rep,name=connection_info,json=connectionInfo,proto3" json:"connection_info,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Properties map[string]string `protobuf:"bytes,3,rep,name=properties,proto3" json:"properties,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ComponentSpec) Reset() { *m = ComponentSpec{} } +func (m *ComponentSpec) String() string { return proto.CompactTextString(m) } +func (*ComponentSpec) ProtoMessage() {} +func (*ComponentSpec) Descriptor() ([]byte, []int) { + return fileDescriptor_eeb49063df94c2b8, []int{5} +} + +func (m *ComponentSpec) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ComponentSpec.Unmarshal(m, b) +} +func (m *ComponentSpec) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ComponentSpec.Marshal(b, m, deterministic) +} +func (m *ComponentSpec) XXX_Merge(src proto.Message) { + xxx_messageInfo_ComponentSpec.Merge(m, src) +} +func (m *ComponentSpec) XXX_Size() int { + return xxx_messageInfo_ComponentSpec.Size(m) +} +func (m *ComponentSpec) XXX_DiscardUnknown() { + xxx_messageInfo_ComponentSpec.DiscardUnknown(m) +} + +var xxx_messageInfo_ComponentSpec proto.InternalMessageInfo + +func (m *ComponentSpec) GetType() string { + if m != nil { + return m.Type + } + return "" +} + +func (m *ComponentSpec) GetConnectionInfo() map[string]string { + if m != nil { + return m.ConnectionInfo + } + return nil +} + +func (m *ComponentSpec) GetProperties() map[string]string { + if m != nil { + return m.Properties + } + return nil +} + type SaveStateEnvelope struct { State []*KeyVal `protobuf:"bytes,1,rep,name=state,proto3" json:"state,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -121,7 +365,7 @@ func (m *SaveStateEnvelope) Reset() { *m = SaveStateEnvelope{} } func (m *SaveStateEnvelope) String() string { return proto.CompactTextString(m) } func (*SaveStateEnvelope) ProtoMessage() {} func (*SaveStateEnvelope) Descriptor() ([]byte, []int) { - return fileDescriptor_eeb49063df94c2b8, []int{2} + return fileDescriptor_eeb49063df94c2b8, []int{6} } func (m *SaveStateEnvelope) XXX_Unmarshal(b []byte) error { @@ -160,7 +404,7 @@ func (m *GetStateEnvelope) Reset() { *m = GetStateEnvelope{} } func (m *GetStateEnvelope) String() string { return proto.CompactTextString(m) } func (*GetStateEnvelope) ProtoMessage() {} func (*GetStateEnvelope) Descriptor() ([]byte, []int) { - return fileDescriptor_eeb49063df94c2b8, []int{3} + return fileDescriptor_eeb49063df94c2b8, []int{7} } func (m *GetStateEnvelope) XXX_Unmarshal(b []byte) error { @@ -188,102 +432,47 @@ func (m *GetStateEnvelope) GetKey() string { return "" } -type EventSourceSpec struct { - Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` - ConnectionInfo *any.Any `protobuf:"bytes,2,opt,name=connection_info,json=connectionInfo,proto3" json:"connection_info,omitempty"` +type AppMethodCallEnvelope struct { + Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"` + Data *any.Any `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } -func (m *EventSourceSpec) Reset() { *m = EventSourceSpec{} } -func (m *EventSourceSpec) String() string { return proto.CompactTextString(m) } -func (*EventSourceSpec) ProtoMessage() {} -func (*EventSourceSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_eeb49063df94c2b8, []int{4} -} - -func (m *EventSourceSpec) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_EventSourceSpec.Unmarshal(m, b) -} -func (m *EventSourceSpec) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_EventSourceSpec.Marshal(b, m, deterministic) -} -func (m *EventSourceSpec) XXX_Merge(src proto.Message) { - xxx_messageInfo_EventSourceSpec.Merge(m, src) -} -func (m *EventSourceSpec) XXX_Size() int { - return xxx_messageInfo_EventSourceSpec.Size(m) -} -func (m *EventSourceSpec) XXX_DiscardUnknown() { - xxx_messageInfo_EventSourceSpec.DiscardUnknown(m) -} - -var xxx_messageInfo_EventSourceSpec proto.InternalMessageInfo - -func (m *EventSourceSpec) GetType() string { - if m != nil { - return m.Type - } - return "" -} - -func (m *EventSourceSpec) GetConnectionInfo() *any.Any { - if m != nil { - return m.ConnectionInfo - } - return nil -} - -type InvokeMethod struct { - MethodName string `protobuf:"bytes,1,opt,name=method_name,json=methodName,proto3" json:"method_name,omitempty"` - ContextId string `protobuf:"bytes,2,opt,name=context_id,json=contextId,proto3" json:"context_id,omitempty"` - Data *any.Any `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *InvokeMethod) Reset() { *m = InvokeMethod{} } -func (m *InvokeMethod) String() string { return proto.CompactTextString(m) } -func (*InvokeMethod) ProtoMessage() {} -func (*InvokeMethod) Descriptor() ([]byte, []int) { - return fileDescriptor_eeb49063df94c2b8, []int{5} +func (m *AppMethodCallEnvelope) Reset() { *m = AppMethodCallEnvelope{} } +func (m *AppMethodCallEnvelope) String() string { return proto.CompactTextString(m) } +func (*AppMethodCallEnvelope) ProtoMessage() {} +func (*AppMethodCallEnvelope) Descriptor() ([]byte, []int) { + return fileDescriptor_eeb49063df94c2b8, []int{8} } -func (m *InvokeMethod) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_InvokeMethod.Unmarshal(m, b) +func (m *AppMethodCallEnvelope) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AppMethodCallEnvelope.Unmarshal(m, b) } -func (m *InvokeMethod) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_InvokeMethod.Marshal(b, m, deterministic) +func (m *AppMethodCallEnvelope) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AppMethodCallEnvelope.Marshal(b, m, deterministic) } -func (m *InvokeMethod) XXX_Merge(src proto.Message) { - xxx_messageInfo_InvokeMethod.Merge(m, src) +func (m *AppMethodCallEnvelope) XXX_Merge(src proto.Message) { + xxx_messageInfo_AppMethodCallEnvelope.Merge(m, src) } -func (m *InvokeMethod) XXX_Size() int { - return xxx_messageInfo_InvokeMethod.Size(m) +func (m *AppMethodCallEnvelope) XXX_Size() int { + return xxx_messageInfo_AppMethodCallEnvelope.Size(m) } -func (m *InvokeMethod) XXX_DiscardUnknown() { - xxx_messageInfo_InvokeMethod.DiscardUnknown(m) +func (m *AppMethodCallEnvelope) XXX_DiscardUnknown() { + xxx_messageInfo_AppMethodCallEnvelope.DiscardUnknown(m) } -var xxx_messageInfo_InvokeMethod proto.InternalMessageInfo +var xxx_messageInfo_AppMethodCallEnvelope proto.InternalMessageInfo -func (m *InvokeMethod) GetMethodName() string { +func (m *AppMethodCallEnvelope) GetMethod() string { if m != nil { - return m.MethodName + return m.Method } return "" } -func (m *InvokeMethod) GetContextId() string { - if m != nil { - return m.ContextId - } - return "" -} - -func (m *InvokeMethod) GetData() *any.Any { +func (m *AppMethodCallEnvelope) GetData() *any.Any { if m != nil { return m.Data } @@ -301,7 +490,7 @@ func (m *State) Reset() { *m = State{} } func (m *State) String() string { return proto.CompactTextString(m) } func (*State) ProtoMessage() {} func (*State) Descriptor() ([]byte, []int) { - return fileDescriptor_eeb49063df94c2b8, []int{6} + return fileDescriptor_eeb49063df94c2b8, []int{9} } func (m *State) XXX_Unmarshal(b []byte) error { @@ -341,7 +530,7 @@ func (m *KeyVal) Reset() { *m = KeyVal{} } func (m *KeyVal) String() string { return proto.CompactTextString(m) } func (*KeyVal) ProtoMessage() {} func (*KeyVal) Descriptor() ([]byte, []int) { - return fileDescriptor_eeb49063df94c2b8, []int{7} + return fileDescriptor_eeb49063df94c2b8, []int{10} } func (m *KeyVal) XXX_Unmarshal(b []byte) error { @@ -387,7 +576,7 @@ func (m *ApplicationConfig) Reset() { *m = ApplicationConfig{} } func (m *ApplicationConfig) String() string { return proto.CompactTextString(m) } func (*ApplicationConfig) ProtoMessage() {} func (*ApplicationConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_eeb49063df94c2b8, []int{8} + return fileDescriptor_eeb49063df94c2b8, []int{11} } func (m *ApplicationConfig) XXX_Unmarshal(b []byte) error { @@ -427,7 +616,7 @@ func (m *PlacementOrder) Reset() { *m = PlacementOrder{} } func (m *PlacementOrder) String() string { return proto.CompactTextString(m) } func (*PlacementOrder) ProtoMessage() {} func (*PlacementOrder) Descriptor() ([]byte, []int) { - return fileDescriptor_eeb49063df94c2b8, []int{9} + return fileDescriptor_eeb49063df94c2b8, []int{12} } func (m *PlacementOrder) XXX_Unmarshal(b []byte) error { @@ -474,7 +663,7 @@ func (m *PlacementTables) Reset() { *m = PlacementTables{} } func (m *PlacementTables) String() string { return proto.CompactTextString(m) } func (*PlacementTables) ProtoMessage() {} func (*PlacementTables) Descriptor() ([]byte, []int) { - return fileDescriptor_eeb49063df94c2b8, []int{10} + return fileDescriptor_eeb49063df94c2b8, []int{13} } func (m *PlacementTables) XXX_Unmarshal(b []byte) error { @@ -523,7 +712,7 @@ func (m *PlacementTable) Reset() { *m = PlacementTable{} } func (m *PlacementTable) String() string { return proto.CompactTextString(m) } func (*PlacementTable) ProtoMessage() {} func (*PlacementTable) Descriptor() ([]byte, []int) { - return fileDescriptor_eeb49063df94c2b8, []int{11} + return fileDescriptor_eeb49063df94c2b8, []int{14} } func (m *PlacementTable) XXX_Unmarshal(b []byte) error { @@ -586,7 +775,7 @@ func (m *Host) Reset() { *m = Host{} } func (m *Host) String() string { return proto.CompactTextString(m) } func (*Host) ProtoMessage() {} func (*Host) Descriptor() ([]byte, []int) { - return fileDescriptor_eeb49063df94c2b8, []int{12} + return fileDescriptor_eeb49063df94c2b8, []int{15} } func (m *Host) XXX_Unmarshal(b []byte) error { @@ -636,12 +825,20 @@ func (m *Host) GetEntities() []string { } func init() { - proto.RegisterType((*InvokeEnvelope)(nil), "actions.InvokeEnvelope") - proto.RegisterType((*EventSource)(nil), "actions.EventSource") + proto.RegisterType((*LocalCallEnvelope)(nil), "actions.LocalCallEnvelope") + proto.RegisterMapType((map[string]string)(nil), "actions.LocalCallEnvelope.MetadataEntry") + proto.RegisterType((*CallRemoteAppEnvelope)(nil), "actions.CallRemoteAppEnvelope") + proto.RegisterMapType((map[string]string)(nil), "actions.CallRemoteAppEnvelope.MetadataEntry") + proto.RegisterType((*CallActorEnvelope)(nil), "actions.CallActorEnvelope") + proto.RegisterType((*InvokeResponse)(nil), "actions.InvokeResponse") + proto.RegisterMapType((map[string]string)(nil), "actions.InvokeResponse.MetadataEntry") + proto.RegisterType((*Component)(nil), "actions.Component") + proto.RegisterType((*ComponentSpec)(nil), "actions.ComponentSpec") + proto.RegisterMapType((map[string]string)(nil), "actions.ComponentSpec.ConnectionInfoEntry") + proto.RegisterMapType((map[string]string)(nil), "actions.ComponentSpec.PropertiesEntry") proto.RegisterType((*SaveStateEnvelope)(nil), "actions.SaveStateEnvelope") proto.RegisterType((*GetStateEnvelope)(nil), "actions.GetStateEnvelope") - proto.RegisterType((*EventSourceSpec)(nil), "actions.EventSourceSpec") - proto.RegisterType((*InvokeMethod)(nil), "actions.InvokeMethod") + proto.RegisterType((*AppMethodCallEnvelope)(nil), "actions.AppMethodCallEnvelope") proto.RegisterType((*State)(nil), "actions.State") proto.RegisterType((*KeyVal)(nil), "actions.KeyVal") proto.RegisterType((*ApplicationConfig)(nil), "actions.ApplicationConfig") @@ -657,57 +854,69 @@ func init() { func init() { proto.RegisterFile("actions.proto", fileDescriptor_eeb49063df94c2b8) } var fileDescriptor_eeb49063df94c2b8 = []byte{ - // 797 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0x5d, 0x8f, 0xdb, 0x44, - 0x14, 0x8d, 0x63, 0xef, 0xa6, 0xbe, 0xd9, 0xee, 0xc7, 0x68, 0x01, 0xd7, 0x80, 0x58, 0x0d, 0xad, - 0x14, 0x21, 0xf0, 0x56, 0xe1, 0x81, 0xd5, 0x8a, 0x82, 0x42, 0x15, 0x4a, 0x04, 0xa5, 0xc8, 0x81, - 0x3e, 0xb2, 0x78, 0xed, 0x9b, 0xad, 0x55, 0x67, 0xc6, 0xb2, 0x27, 0x51, 0xf3, 0x27, 0xf8, 0x43, - 0x3c, 0xf1, 0xcc, 0x9f, 0x42, 0x33, 0xe3, 0xcf, 0x24, 0x5e, 0x78, 0xf2, 0xf8, 0xde, 0x73, 0xef, - 0x3d, 0x73, 0x66, 0xe6, 0xc0, 0xc3, 0x20, 0x14, 0x31, 0x67, 0xb9, 0x97, 0x66, 0x5c, 0x70, 0x32, - 0x28, 0x7e, 0xdd, 0x47, 0x77, 0x9c, 0xdf, 0x25, 0x78, 0xa9, 0xc2, 0xb7, 0xab, 0xc5, 0x65, 0xc0, - 0x36, 0x1a, 0xe3, 0x7e, 0xb8, 0x9d, 0xc2, 0x65, 0x2a, 0x8a, 0x24, 0xbd, 0x86, 0xe3, 0x19, 0x5b, - 0xf3, 0xb7, 0x38, 0x65, 0x6b, 0x4c, 0x78, 0x8a, 0x64, 0x04, 0x56, 0x14, 0x88, 0xc0, 0x31, 0x2e, - 0x8c, 0xd1, 0x70, 0x7c, 0xee, 0xe9, 0x6a, 0xaf, 0xac, 0xf6, 0x26, 0x6c, 0xe3, 0x2b, 0x04, 0x7d, - 0x05, 0xc3, 0xe9, 0x1a, 0x99, 0x98, 0xf3, 0x55, 0x16, 0x22, 0x21, 0x60, 0xb1, 0x60, 0x89, 0xaa, - 0xd0, 0xf6, 0xd5, 0x9a, 0x7c, 0x0e, 0x56, 0x9e, 0x62, 0xe8, 0xf4, 0x55, 0x33, 0xc7, 0x2b, 0xd9, - 0x37, 0xea, 0xe6, 0x29, 0x86, 0xbe, 0x42, 0xd1, 0x6b, 0x38, 0x9b, 0x07, 0x6b, 0x9c, 0x8b, 0x40, - 0xd4, 0x7c, 0x9e, 0xc0, 0x41, 0x2e, 0x03, 0x8e, 0x71, 0x61, 0x8e, 0x86, 0xe3, 0x93, 0xaa, 0xc7, - 0x8f, 0xb8, 0x79, 0x1d, 0x24, 0xbe, 0xce, 0xd2, 0xc7, 0x70, 0xfa, 0x02, 0x45, 0xbb, 0xf4, 0x14, - 0xcc, 0xb7, 0xb8, 0x29, 0x08, 0xc9, 0x25, 0x8d, 0xe0, 0x64, 0x6b, 0xb4, 0xa4, 0x2d, 0x36, 0x69, - 0x45, 0x5b, 0xae, 0xc9, 0x33, 0x38, 0x09, 0x39, 0x63, 0xa8, 0x26, 0xdd, 0xc4, 0x6c, 0xc1, 0x8b, - 0x1d, 0xec, 0x97, 0xe3, 0xb8, 0x06, 0xcf, 0xd8, 0x82, 0xd3, 0x77, 0x70, 0xa4, 0x45, 0x7d, 0x89, - 0xe2, 0x0d, 0x8f, 0xc8, 0x27, 0x30, 0x5c, 0xaa, 0xd5, 0x4d, 0x43, 0x20, 0xd0, 0xa1, 0x9f, 0xa5, - 0x4c, 0x1f, 0x03, 0x84, 0x9c, 0x09, 0x7c, 0x27, 0x6e, 0xe2, 0x48, 0x8d, 0xb2, 0x7d, 0xbb, 0x88, - 0xcc, 0xa2, 0xea, 0x48, 0xcc, 0xff, 0x3c, 0x12, 0x0f, 0x0e, 0x94, 0x04, 0xff, 0x57, 0xb5, 0xef, - 0xe1, 0x50, 0x07, 0x76, 0xb5, 0x22, 0x9f, 0xc1, 0xc1, 0x3a, 0x48, 0x56, 0x78, 0xef, 0xd6, 0x35, - 0x84, 0x5e, 0xc2, 0xd9, 0x24, 0x4d, 0x93, 0x38, 0x0c, 0xe4, 0x94, 0xe7, 0x9c, 0x2d, 0xe2, 0x3b, - 0xe2, 0xc2, 0x03, 0x64, 0x22, 0x16, 0x31, 0xe6, 0x8a, 0x86, 0xed, 0x57, 0xff, 0xf4, 0x0f, 0x38, - 0xfe, 0x25, 0x09, 0x42, 0x5c, 0x22, 0x13, 0xaf, 0xb2, 0x08, 0x33, 0xf2, 0x14, 0x0e, 0x45, 0x70, - 0x9b, 0x28, 0x6c, 0xfb, 0xb2, 0x54, 0xc0, 0x5f, 0x55, 0xde, 0x2f, 0x70, 0xe4, 0x23, 0xb0, 0x79, - 0x8a, 0x99, 0x1a, 0x59, 0x8a, 0x56, 0x05, 0xe8, 0xdf, 0x06, 0x9c, 0x6c, 0x55, 0x92, 0x6f, 0x61, - 0x80, 0x4c, 0x64, 0x25, 0xa1, 0xe1, 0xf8, 0x49, 0xd7, 0x10, 0x6f, 0xaa, 0x71, 0xf2, 0xb3, 0xf1, - 0xcb, 0x2a, 0xe2, 0xc0, 0x60, 0x8d, 0x59, 0x5e, 0x0f, 0x2c, 0x7f, 0xdd, 0x39, 0x1c, 0x35, 0x4b, - 0xf6, 0xe8, 0xf9, 0x45, 0x5b, 0xcf, 0x0f, 0x3a, 0x46, 0x17, 0x92, 0x5e, 0xf7, 0xaf, 0x0c, 0xfa, - 0x4f, 0xbf, 0x21, 0x93, 0xca, 0x92, 0x2b, 0x38, 0x78, 0xc3, 0x73, 0x51, 0x6e, 0x80, 0x76, 0x74, - 0xf1, 0x7e, 0x90, 0x20, 0xcd, 0x5e, 0x17, 0x48, 0xb9, 0x72, 0x9e, 0x09, 0x8c, 0xe6, 0x28, 0x9c, - 0xfe, 0x85, 0x39, 0xb2, 0xfc, 0x3a, 0x40, 0xbe, 0x81, 0x41, 0xc2, 0x83, 0xe8, 0x65, 0x90, 0x3a, - 0xa6, 0xea, 0xfc, 0xb8, 0xab, 0xf3, 0x4f, 0x1a, 0x56, 0x28, 0x53, 0x14, 0xc9, 0xee, 0x82, 0x8b, - 0x20, 0x91, 0x59, 0xc7, 0xba, 0x30, 0x46, 0xa6, 0x5f, 0x07, 0xdc, 0x2b, 0x80, 0x9a, 0x50, 0x53, - 0x1b, 0x4b, 0x6b, 0x73, 0xde, 0xd4, 0xc6, 0x6e, 0x48, 0xe0, 0xce, 0xe0, 0xa8, 0x39, 0x70, 0x8f, - 0xae, 0x9f, 0xb6, 0x75, 0x7d, 0x58, 0xf1, 0x96, 0x13, 0x9b, 0x6a, 0xfe, 0x0e, 0x96, 0x0c, 0xed, - 0x35, 0x2a, 0x02, 0x56, 0xca, 0x33, 0xa1, 0x7a, 0x98, 0xbe, 0x5a, 0xcb, 0x98, 0xdc, 0x9d, 0x7a, - 0x76, 0xa6, 0xaf, 0xd6, 0xad, 0x3b, 0x6d, 0xb5, 0xef, 0xf4, 0xf8, 0xcf, 0x3e, 0x0c, 0x26, 0x7a, - 0x36, 0xf9, 0x1a, 0x0e, 0xb5, 0x05, 0x90, 0xfa, 0x9c, 0xdb, 0x46, 0xeb, 0x76, 0x25, 0x68, 0x8f, - 0x4c, 0xc0, 0xae, 0x8c, 0x90, 0xb8, 0x15, 0x6e, 0xc7, 0x1c, 0xdd, 0xf7, 0x77, 0x1e, 0xe5, 0x54, - 0x9a, 0x3b, 0xed, 0x91, 0x67, 0xf0, 0xa0, 0xf4, 0x43, 0xf2, 0xa8, 0xea, 0xb0, 0x6d, 0x91, 0xee, - 0xde, 0x57, 0x4d, 0x7b, 0xe4, 0x39, 0x9c, 0xfd, 0x96, 0x46, 0x12, 0xd9, 0x70, 0xf8, 0xf3, 0x7d, - 0xfe, 0xdd, 0xcd, 0x61, 0xfc, 0x97, 0x01, 0xe6, 0x24, 0x4d, 0xc9, 0x57, 0x95, 0x18, 0xef, 0x6d, - 0xed, 0x59, 0x1b, 0x64, 0x27, 0x8b, 0x2b, 0x38, 0xf2, 0x31, 0x17, 0x3c, 0x2b, 0xa4, 0x38, 0xae, - 0xa5, 0x90, 0xff, 0xf7, 0x6c, 0x7f, 0x02, 0xf6, 0x0b, 0x14, 0x85, 0x11, 0x75, 0xc0, 0xdc, 0x5a, - 0xd9, 0x1d, 0xf3, 0xa2, 0xbd, 0xf1, 0x6b, 0x38, 0xad, 0x6e, 0xfe, 0x1c, 0xb3, 0x75, 0x1c, 0x22, - 0xf9, 0x0e, 0x88, 0x8f, 0xf2, 0x72, 0xe8, 0x73, 0x96, 0x2c, 0x56, 0x39, 0x69, 0x5f, 0x39, 0x77, - 0xcf, 0xcb, 0x56, 0x16, 0x47, 0x7b, 0x23, 0xe3, 0xa9, 0x71, 0x7b, 0xa8, 0x58, 0x7c, 0xf9, 0x6f, - 0x00, 0x00, 0x00, 0xff, 0xff, 0x2d, 0x82, 0xe9, 0xe9, 0xcb, 0x07, 0x00, 0x00, + // 987 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0x51, 0x6f, 0xdb, 0x36, + 0x10, 0xb6, 0x6c, 0x25, 0xa9, 0xaf, 0x71, 0x9c, 0x70, 0x6d, 0xea, 0x69, 0xc3, 0x10, 0x68, 0xed, + 0x60, 0x04, 0x9b, 0x52, 0x78, 0x2f, 0x41, 0x87, 0xad, 0x70, 0xd3, 0xa4, 0xcd, 0xda, 0xa0, 0x85, + 0xdc, 0x15, 0xd8, 0xcb, 0x36, 0x46, 0xbe, 0xa4, 0x46, 0x65, 0x92, 0x90, 0x18, 0x03, 0xfe, 0x11, + 0xfb, 0x0b, 0xfb, 0x21, 0x7b, 0xdb, 0x1e, 0xb7, 0x3f, 0xb0, 0x9f, 0xb2, 0xc7, 0x82, 0x14, 0x45, + 0x4b, 0xb1, 0xd4, 0x34, 0x0f, 0x7d, 0x23, 0x8f, 0x77, 0xdf, 0xdd, 0x7d, 0x3a, 0x7e, 0x14, 0x74, + 0x68, 0x24, 0x27, 0x9c, 0xa5, 0x81, 0x48, 0xb8, 0xe4, 0x64, 0xcd, 0x6c, 0xbd, 0x4f, 0xcf, 0x39, + 0x3f, 0x8f, 0x71, 0x4f, 0x9b, 0x4f, 0x2f, 0xce, 0xf6, 0x28, 0x9b, 0x67, 0x3e, 0xde, 0x67, 0x97, + 0x8f, 0x70, 0x2a, 0xa4, 0x39, 0xf4, 0xff, 0x73, 0x60, 0xeb, 0x39, 0x8f, 0x68, 0x7c, 0x40, 0xe3, + 0xf8, 0x90, 0xcd, 0x30, 0xe6, 0x02, 0x49, 0x1f, 0xdc, 0x31, 0x95, 0xb4, 0xe7, 0xec, 0x38, 0xfd, + 0x9b, 0x83, 0x5b, 0x41, 0x86, 0x10, 0xe4, 0x08, 0xc1, 0x90, 0xcd, 0x43, 0xed, 0x41, 0xb6, 0x61, + 0x75, 0x8a, 0xf2, 0x0d, 0x1f, 0xf7, 0x9a, 0x3b, 0x4e, 0xbf, 0x1d, 0x9a, 0x1d, 0x79, 0x0c, 0x37, + 0xa6, 0x28, 0xa9, 0x46, 0x69, 0xed, 0xb4, 0xfa, 0x37, 0x07, 0xfd, 0x20, 0x2f, 0x7d, 0x29, 0x5f, + 0x70, 0x62, 0x5c, 0x0f, 0x99, 0x4c, 0xe6, 0xa1, 0x8d, 0xf4, 0xbe, 0x83, 0x4e, 0xe9, 0x88, 0x6c, + 0x42, 0xeb, 0x2d, 0xce, 0x75, 0x5d, 0xed, 0x50, 0x2d, 0xc9, 0x2d, 0x58, 0x99, 0xd1, 0xf8, 0x02, + 0x4d, 0xfe, 0x6c, 0xf3, 0xa0, 0xb9, 0xef, 0xf8, 0xff, 0x3b, 0x70, 0x5b, 0x65, 0x09, 0x71, 0xca, + 0x25, 0x0e, 0x85, 0xb0, 0xed, 0x6d, 0xc3, 0xaa, 0xa4, 0xc9, 0x39, 0x4a, 0x03, 0x64, 0x76, 0xb6, + 0xed, 0xe6, 0x35, 0xda, 0x6e, 0x95, 0xda, 0x7e, 0x5a, 0x68, 0xdb, 0xd5, 0x6d, 0x7f, 0x6d, 0xdb, + 0xae, 0xac, 0xe5, 0xe3, 0xb4, 0xfe, 0xbb, 0x03, 0x5b, 0x2a, 0xdd, 0x30, 0x92, 0x3c, 0xb1, 0x6d, + 0xf7, 0x40, 0x8d, 0x0b, 0x4f, 0x8e, 0x1f, 0x1b, 0x94, 0x7c, 0x4b, 0x3e, 0x87, 0xb6, 0x5e, 0xbe, + 0x9a, 0x8b, 0x1c, 0x6d, 0x61, 0xa8, 0x6d, 0x36, 0xa7, 0xcb, 0xbd, 0x8a, 0x2e, 0xff, 0x4f, 0x07, + 0x36, 0x8e, 0xd9, 0x8c, 0xbf, 0xc5, 0x10, 0x53, 0xc1, 0x59, 0x7a, 0x9d, 0x11, 0x1b, 0x16, 0x38, + 0x6d, 0x6a, 0x4e, 0xef, 0x59, 0x4e, 0xcb, 0xa0, 0x1f, 0x87, 0xcc, 0x67, 0xd0, 0x3e, 0xe0, 0x53, + 0xc1, 0x19, 0x32, 0x49, 0x08, 0xb8, 0x8c, 0x4e, 0xd1, 0x44, 0xea, 0x35, 0xd9, 0x05, 0x37, 0x15, + 0x18, 0x99, 0xb1, 0xd9, 0x5e, 0x7c, 0xf0, 0x3c, 0x6a, 0x24, 0x30, 0x0a, 0xb5, 0x8f, 0xff, 0x77, + 0x13, 0x3a, 0x25, 0xbb, 0x42, 0x94, 0x8a, 0x76, 0x83, 0xa8, 0xd6, 0x64, 0x04, 0xdd, 0x88, 0x33, + 0x86, 0x1a, 0xe8, 0xd7, 0x09, 0x3b, 0xe3, 0xa6, 0xf3, 0xdd, 0x6a, 0xf0, 0xe0, 0xc0, 0x7a, 0x1f, + 0xb3, 0x33, 0x9e, 0xb5, 0xbf, 0x11, 0x95, 0x8c, 0xe4, 0x08, 0x40, 0x24, 0x5c, 0x60, 0x22, 0x27, + 0x98, 0x9a, 0x4b, 0xf9, 0x55, 0x0d, 0xde, 0x4b, 0xeb, 0x98, 0x61, 0x15, 0x22, 0xbd, 0x21, 0x7c, + 0x52, 0x91, 0xee, 0x3a, 0x94, 0x7a, 0xdf, 0x43, 0xf7, 0x52, 0x86, 0x6b, 0x7d, 0x91, 0x07, 0xb0, + 0x35, 0xa2, 0x33, 0x1c, 0x49, 0x2a, 0xd1, 0x4e, 0xf7, 0x3d, 0x58, 0x49, 0x95, 0xa1, 0xe7, 0xe8, + 0xce, 0xba, 0xb6, 0xb3, 0x67, 0x38, 0x7f, 0x4d, 0xe3, 0x30, 0x3b, 0xf5, 0xef, 0xc2, 0xe6, 0x13, + 0x94, 0xe5, 0xd0, 0xa5, 0xdc, 0xfe, 0xcf, 0x70, 0x7b, 0x28, 0xc4, 0x89, 0x9e, 0xf3, 0x92, 0x32, + 0x2e, 0xee, 0x82, 0x53, 0x79, 0x17, 0xae, 0x94, 0x0e, 0x3f, 0x80, 0x15, 0x9d, 0xfd, 0x43, 0x0b, + 0x3e, 0x82, 0xd5, 0xcc, 0x50, 0x41, 0xd1, 0x6e, 0x91, 0xa2, 0xba, 0xb4, 0x99, 0x8b, 0xbf, 0x07, + 0x5b, 0x43, 0x21, 0xe2, 0x49, 0x44, 0x55, 0x96, 0x03, 0xce, 0xce, 0x26, 0xe7, 0xc4, 0x83, 0x1b, + 0xc8, 0xe4, 0x44, 0x4f, 0x84, 0x2a, 0xa3, 0x1d, 0xda, 0xbd, 0xff, 0x1b, 0x6c, 0xbc, 0x8c, 0x69, + 0x84, 0x53, 0x64, 0xf2, 0x45, 0x32, 0xc6, 0x84, 0xdc, 0x57, 0xba, 0x79, 0x1a, 0x6b, 0x5f, 0x95, + 0xaf, 0x67, 0x4b, 0xb6, 0x8e, 0xaf, 0xf4, 0x79, 0x68, 0xfc, 0x94, 0xb0, 0xa8, 0xcf, 0xac, 0x53, + 0xe6, 0xc2, 0x62, 0x0d, 0xfe, 0x5f, 0x0e, 0x74, 0x2f, 0x45, 0x92, 0x87, 0xb0, 0x86, 0x4c, 0x26, + 0x79, 0x41, 0xc5, 0xcb, 0x7e, 0xc9, 0x35, 0x38, 0xcc, 0xfc, 0xb2, 0x09, 0xcd, 0xa3, 0x94, 0xca, + 0xcd, 0x30, 0x49, 0x17, 0x09, 0xf3, 0xad, 0x37, 0x82, 0xf5, 0x62, 0x48, 0x05, 0x9f, 0xdf, 0x94, + 0xf9, 0xbc, 0x53, 0x93, 0xba, 0x38, 0x8b, 0xff, 0x36, 0x0b, 0x34, 0xe9, 0x53, 0xb2, 0x0f, 0x2b, + 0x6f, 0x78, 0x2a, 0xf3, 0x06, 0xfc, 0x1a, 0x94, 0xe0, 0xa9, 0x72, 0xca, 0xaa, 0xcf, 0x02, 0x14, + 0x5d, 0x29, 0x4f, 0x24, 0x8e, 0x47, 0x28, 0xf5, 0x8d, 0x77, 0xc3, 0x85, 0x81, 0xfc, 0x00, 0x6b, + 0x31, 0xa7, 0xe3, 0x13, 0x2a, 0xcc, 0xed, 0xbd, 0x5b, 0x87, 0xfc, 0x3c, 0x73, 0x33, 0xcc, 0x98, + 0x20, 0x85, 0x2e, 0xb9, 0xa4, 0xb1, 0x3a, 0xd5, 0xa2, 0xdd, 0x0a, 0x17, 0x06, 0x6f, 0x1f, 0x60, + 0x51, 0x50, 0x91, 0x1b, 0xf7, 0xaa, 0xdb, 0x7c, 0x0c, 0xeb, 0xc5, 0x84, 0x15, 0xbc, 0x7e, 0x59, + 0xe6, 0xb5, 0x63, 0xeb, 0x56, 0x19, 0x8b, 0x6c, 0xfe, 0x02, 0xae, 0x32, 0x55, 0xca, 0x2c, 0x01, + 0x57, 0xf0, 0x44, 0x6a, 0x8c, 0x56, 0xa8, 0xd7, 0xca, 0xa6, 0xba, 0xd3, 0x0f, 0x53, 0x2b, 0xd4, + 0xeb, 0xd2, 0x4c, 0xbb, 0xe5, 0x99, 0x1e, 0xfc, 0xd1, 0x84, 0xb5, 0x61, 0x96, 0x9b, 0xfc, 0x08, + 0x9d, 0xd2, 0x93, 0x4c, 0xbe, 0x78, 0xff, 0x53, 0xed, 0xdd, 0xa9, 0x79, 0x76, 0xfc, 0x06, 0x79, + 0x04, 0x6d, 0xfb, 0xde, 0x12, 0xaf, 0x84, 0x53, 0x7a, 0x83, 0x3f, 0x00, 0x43, 0xff, 0x1d, 0x15, + 0x30, 0x96, 0xfe, 0x96, 0xde, 0x87, 0xf1, 0x10, 0xba, 0x3f, 0x89, 0x31, 0x95, 0x58, 0x78, 0xb1, + 0x96, 0x25, 0xde, 0xdb, 0x5e, 0x12, 0x8a, 0x43, 0xf5, 0x4f, 0xe8, 0x37, 0x06, 0xff, 0x38, 0xd0, + 0x52, 0x5c, 0x1c, 0xc1, 0xfa, 0x0b, 0xb6, 0xd0, 0xbf, 0x02, 0x37, 0x95, 0xba, 0xe8, 0x55, 0x4a, + 0x8f, 0xdf, 0x20, 0xfb, 0xb0, 0x1e, 0x62, 0x2a, 0x79, 0x92, 0xa9, 0x35, 0xd9, 0xb0, 0x38, 0x7a, + 0x5f, 0x5f, 0x09, 0x19, 0x42, 0xfb, 0x09, 0x4a, 0xa3, 0x53, 0x35, 0x6e, 0x9e, 0x57, 0x2c, 0xab, + 0xac, 0x6d, 0x7e, 0x63, 0xf0, 0x1a, 0x36, 0xed, 0xc5, 0x18, 0x61, 0x32, 0x9b, 0x44, 0x48, 0x1e, + 0x01, 0x09, 0x51, 0xcd, 0x4e, 0x36, 0x06, 0xaa, 0x8a, 0x8b, 0x94, 0x94, 0x27, 0xd2, 0xab, 0xb8, + 0xf8, 0x5a, 0x01, 0xfd, 0x46, 0xdf, 0xb9, 0xef, 0x9c, 0xae, 0xea, 0x2a, 0xbe, 0x7d, 0x17, 0x00, + 0x00, 0xff, 0xff, 0x50, 0x8f, 0xcf, 0x18, 0x8d, 0x0b, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -722,10 +931,10 @@ const _ = grpc.SupportPackageIsVersion4 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type ActionsClient interface { - Invoke(ctx context.Context, in *InvokeEnvelope, opts ...grpc.CallOption) (*InvokeEnvelope, error) - SaveState(ctx context.Context, in *SaveStateEnvelope, opts ...grpc.CallOption) (*empty.Empty, error) - GetState(ctx context.Context, in *GetStateEnvelope, opts ...grpc.CallOption) (*any.Any, error) - UpdateEventSource(ctx context.Context, in *EventSource, opts ...grpc.CallOption) (*empty.Empty, error) + CallRemoteApp(ctx context.Context, in *CallRemoteAppEnvelope, opts ...grpc.CallOption) (*InvokeResponse, error) + CallActor(ctx context.Context, in *CallActorEnvelope, opts ...grpc.CallOption) (*InvokeResponse, error) + CallLocal(ctx context.Context, in *LocalCallEnvelope, opts ...grpc.CallOption) (*InvokeResponse, error) + UpdateComponent(ctx context.Context, in *Component, opts ...grpc.CallOption) (*empty.Empty, error) } type actionsClient struct { @@ -736,36 +945,36 @@ func NewActionsClient(cc *grpc.ClientConn) ActionsClient { return &actionsClient{cc} } -func (c *actionsClient) Invoke(ctx context.Context, in *InvokeEnvelope, opts ...grpc.CallOption) (*InvokeEnvelope, error) { - out := new(InvokeEnvelope) - err := c.cc.Invoke(ctx, "/actions.Actions/Invoke", in, out, opts...) +func (c *actionsClient) CallRemoteApp(ctx context.Context, in *CallRemoteAppEnvelope, opts ...grpc.CallOption) (*InvokeResponse, error) { + out := new(InvokeResponse) + err := c.cc.Invoke(ctx, "/actions.Actions/CallRemoteApp", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *actionsClient) SaveState(ctx context.Context, in *SaveStateEnvelope, opts ...grpc.CallOption) (*empty.Empty, error) { - out := new(empty.Empty) - err := c.cc.Invoke(ctx, "/actions.Actions/SaveState", in, out, opts...) +func (c *actionsClient) CallActor(ctx context.Context, in *CallActorEnvelope, opts ...grpc.CallOption) (*InvokeResponse, error) { + out := new(InvokeResponse) + err := c.cc.Invoke(ctx, "/actions.Actions/CallActor", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *actionsClient) GetState(ctx context.Context, in *GetStateEnvelope, opts ...grpc.CallOption) (*any.Any, error) { - out := new(any.Any) - err := c.cc.Invoke(ctx, "/actions.Actions/GetState", in, out, opts...) +func (c *actionsClient) CallLocal(ctx context.Context, in *LocalCallEnvelope, opts ...grpc.CallOption) (*InvokeResponse, error) { + out := new(InvokeResponse) + err := c.cc.Invoke(ctx, "/actions.Actions/CallLocal", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *actionsClient) UpdateEventSource(ctx context.Context, in *EventSource, opts ...grpc.CallOption) (*empty.Empty, error) { +func (c *actionsClient) UpdateComponent(ctx context.Context, in *Component, opts ...grpc.CallOption) (*empty.Empty, error) { out := new(empty.Empty) - err := c.cc.Invoke(ctx, "/actions.Actions/UpdateEventSource", in, out, opts...) + err := c.cc.Invoke(ctx, "/actions.Actions/UpdateComponent", in, out, opts...) if err != nil { return nil, err } @@ -774,84 +983,84 @@ func (c *actionsClient) UpdateEventSource(ctx context.Context, in *EventSource, // ActionsServer is the server API for Actions service. type ActionsServer interface { - Invoke(context.Context, *InvokeEnvelope) (*InvokeEnvelope, error) - SaveState(context.Context, *SaveStateEnvelope) (*empty.Empty, error) - GetState(context.Context, *GetStateEnvelope) (*any.Any, error) - UpdateEventSource(context.Context, *EventSource) (*empty.Empty, error) + CallRemoteApp(context.Context, *CallRemoteAppEnvelope) (*InvokeResponse, error) + CallActor(context.Context, *CallActorEnvelope) (*InvokeResponse, error) + CallLocal(context.Context, *LocalCallEnvelope) (*InvokeResponse, error) + UpdateComponent(context.Context, *Component) (*empty.Empty, error) } func RegisterActionsServer(s *grpc.Server, srv ActionsServer) { s.RegisterService(&_Actions_serviceDesc, srv) } -func _Actions_Invoke_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(InvokeEnvelope) +func _Actions_CallRemoteApp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CallRemoteAppEnvelope) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(ActionsServer).Invoke(ctx, in) + return srv.(ActionsServer).CallRemoteApp(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/actions.Actions/Invoke", + FullMethod: "/actions.Actions/CallRemoteApp", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ActionsServer).Invoke(ctx, req.(*InvokeEnvelope)) + return srv.(ActionsServer).CallRemoteApp(ctx, req.(*CallRemoteAppEnvelope)) } return interceptor(ctx, in, info, handler) } -func _Actions_SaveState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(SaveStateEnvelope) +func _Actions_CallActor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CallActorEnvelope) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(ActionsServer).SaveState(ctx, in) + return srv.(ActionsServer).CallActor(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/actions.Actions/SaveState", + FullMethod: "/actions.Actions/CallActor", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ActionsServer).SaveState(ctx, req.(*SaveStateEnvelope)) + return srv.(ActionsServer).CallActor(ctx, req.(*CallActorEnvelope)) } return interceptor(ctx, in, info, handler) } -func _Actions_GetState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetStateEnvelope) +func _Actions_CallLocal_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(LocalCallEnvelope) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(ActionsServer).GetState(ctx, in) + return srv.(ActionsServer).CallLocal(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/actions.Actions/GetState", + FullMethod: "/actions.Actions/CallLocal", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ActionsServer).GetState(ctx, req.(*GetStateEnvelope)) + return srv.(ActionsServer).CallLocal(ctx, req.(*LocalCallEnvelope)) } return interceptor(ctx, in, info, handler) } -func _Actions_UpdateEventSource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(EventSource) +func _Actions_UpdateComponent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Component) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(ActionsServer).UpdateEventSource(ctx, in) + return srv.(ActionsServer).UpdateComponent(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/actions.Actions/UpdateEventSource", + FullMethod: "/actions.Actions/UpdateComponent", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ActionsServer).UpdateEventSource(ctx, req.(*EventSource)) + return srv.(ActionsServer).UpdateComponent(ctx, req.(*Component)) } return interceptor(ctx, in, info, handler) } @@ -861,20 +1070,20 @@ var _Actions_serviceDesc = grpc.ServiceDesc{ HandlerType: (*ActionsServer)(nil), Methods: []grpc.MethodDesc{ { - MethodName: "Invoke", - Handler: _Actions_Invoke_Handler, + MethodName: "CallRemoteApp", + Handler: _Actions_CallRemoteApp_Handler, }, { - MethodName: "SaveState", - Handler: _Actions_SaveState_Handler, + MethodName: "CallActor", + Handler: _Actions_CallActor_Handler, }, { - MethodName: "GetState", - Handler: _Actions_GetState_Handler, + MethodName: "CallLocal", + Handler: _Actions_CallLocal_Handler, }, { - MethodName: "UpdateEventSource", - Handler: _Actions_UpdateEventSource_Handler, + MethodName: "UpdateComponent", + Handler: _Actions_UpdateComponent_Handler, }, }, Streams: []grpc.StreamDesc{}, @@ -885,7 +1094,7 @@ var _Actions_serviceDesc = grpc.ServiceDesc{ // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type AppClient interface { - Invoke(ctx context.Context, in *InvokeMethod, opts ...grpc.CallOption) (*any.Any, error) + OnMethodCall(ctx context.Context, in *AppMethodCallEnvelope, opts ...grpc.CallOption) (*any.Any, error) RestoreState(ctx context.Context, in *State, opts ...grpc.CallOption) (*empty.Empty, error) GetConfig(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*ApplicationConfig, error) } @@ -898,9 +1107,9 @@ func NewAppClient(cc *grpc.ClientConn) AppClient { return &appClient{cc} } -func (c *appClient) Invoke(ctx context.Context, in *InvokeMethod, opts ...grpc.CallOption) (*any.Any, error) { +func (c *appClient) OnMethodCall(ctx context.Context, in *AppMethodCallEnvelope, opts ...grpc.CallOption) (*any.Any, error) { out := new(any.Any) - err := c.cc.Invoke(ctx, "/actions.App/Invoke", in, out, opts...) + err := c.cc.Invoke(ctx, "/actions.App/OnMethodCall", in, out, opts...) if err != nil { return nil, err } @@ -927,7 +1136,7 @@ func (c *appClient) GetConfig(ctx context.Context, in *empty.Empty, opts ...grpc // AppServer is the server API for App service. type AppServer interface { - Invoke(context.Context, *InvokeMethod) (*any.Any, error) + OnMethodCall(context.Context, *AppMethodCallEnvelope) (*any.Any, error) RestoreState(context.Context, *State) (*empty.Empty, error) GetConfig(context.Context, *empty.Empty) (*ApplicationConfig, error) } @@ -936,20 +1145,20 @@ func RegisterAppServer(s *grpc.Server, srv AppServer) { s.RegisterService(&_App_serviceDesc, srv) } -func _App_Invoke_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(InvokeMethod) +func _App_OnMethodCall_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AppMethodCallEnvelope) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(AppServer).Invoke(ctx, in) + return srv.(AppServer).OnMethodCall(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/actions.App/Invoke", + FullMethod: "/actions.App/OnMethodCall", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(AppServer).Invoke(ctx, req.(*InvokeMethod)) + return srv.(AppServer).OnMethodCall(ctx, req.(*AppMethodCallEnvelope)) } return interceptor(ctx, in, info, handler) } @@ -995,8 +1204,8 @@ var _App_serviceDesc = grpc.ServiceDesc{ HandlerType: (*AppServer)(nil), Methods: []grpc.MethodDesc{ { - MethodName: "Invoke", - Handler: _App_Invoke_Handler, + MethodName: "OnMethodCall", + Handler: _App_OnMethodCall_Handler, }, { MethodName: "RestoreState", diff --git a/pkg/proto/actions.proto b/pkg/proto/actions.proto index 0d6f5dc074b699da4cde132f07d840477fffad32..befc90e321de2b1bfba5a676bdf4dde7c7321400 100644 --- a/pkg/proto/actions.proto +++ b/pkg/proto/actions.proto @@ -7,19 +7,46 @@ import "google/protobuf/empty.proto"; // Actions Runtime definitions service Actions { - rpc Invoke (InvokeEnvelope) returns (InvokeEnvelope) {} - rpc SaveState (SaveStateEnvelope) returns (google.protobuf.Empty) {} - rpc GetState (GetStateEnvelope) returns (google.protobuf.Any) {} - rpc UpdateEventSource (EventSource) returns (google.protobuf.Empty) {} + rpc CallRemoteApp (CallRemoteAppEnvelope) returns (InvokeResponse) {} + rpc CallActor (CallActorEnvelope) returns (InvokeResponse) {} + rpc CallLocal (LocalCallEnvelope) returns (InvokeResponse) {} + rpc UpdateComponent (Component) returns (google.protobuf.Empty) {} } -message InvokeEnvelope { +message LocalCallEnvelope { google.protobuf.Any data = 1; + string method = 2; + map metadata = 3; } -message EventSource { +message CallRemoteAppEnvelope { + string target = 1; + google.protobuf.Any data = 2; + string method = 3; + map metadata = 4; +} + +message CallActorEnvelope { + string actorID = 1; + string actorType = 2; + string method = 3; + google.protobuf.Any data = 4; +} + +message InvokeResponse { + google.protobuf.Any data = 1; + map metadata = 2; +} + +message Component { string name = 1; - EventSourceSpec spec = 2; + ComponentSpec spec = 2; +} + +message ComponentSpec { + string type = 1; + map connection_info = 2; + map properties = 3; } message SaveStateEnvelope { @@ -30,22 +57,16 @@ message GetStateEnvelope { string key = 1; } -message EventSourceSpec { - string type = 1; - google.protobuf.Any connection_info = 2; -} - // User Code definitions service App { - rpc Invoke (InvokeMethod) returns (google.protobuf.Any) {} + rpc OnMethodCall (AppMethodCallEnvelope) returns (google.protobuf.Any) {} rpc RestoreState (State) returns (google.protobuf.Empty) {} rpc GetConfig(google.protobuf.Empty) returns (ApplicationConfig) {} } -message InvokeMethod { - string method_name = 1; - string context_id = 2; - google.protobuf.Any data = 3; +message AppMethodCallEnvelope { + string method = 1; + google.protobuf.Any data = 2; } message State { diff --git a/pkg/runtime/config.go b/pkg/runtime/config.go new file mode 100644 index 0000000000000000000000000000000000000000..fc6cc0fafb60e6f91dcfdb7cb87fca358f663f43 --- /dev/null +++ b/pkg/runtime/config.go @@ -0,0 +1,52 @@ +package runtime + +import ( + config "github.com/actionscore/actions/pkg/config/modes" + "github.com/actionscore/actions/pkg/modes" +) + +type Protocol string + +const ( + GRPCProtocol Protocol = "grpc" + HTTPProtocol Protocol = "http" + + DefaultActionsHTTPPort = 3500 + DefaultActionsGRPCPort = 50001 + DefaultComponentsPath = "./components" + DefaultAllowedOrigins = "*" +) + +type RuntimeConfig struct { + ID string + HTTPPort int + GRPCPort int + ApplicationPort int + ApplicationProtocol Protocol + Mode modes.ActionsMode + PlacementServiceAddress string + GlobalConfig string + AllowedOrigins string + Standalone config.StandaloneConfig + Kubernetes config.KubernetesConfig +} + +func NewRuntimeConfig(id, placementServiceAddress, controlPlaneAddress, allowedOrigins, globalConfig, componentsPath, appProtocol, mode string, httpPort, grpcPort, appPort int) *RuntimeConfig { + return &RuntimeConfig{ + ID: id, + HTTPPort: httpPort, + GRPCPort: grpcPort, + ApplicationPort: appPort, + ApplicationProtocol: Protocol(appProtocol), + Mode: modes.ActionsMode(mode), + PlacementServiceAddress: placementServiceAddress, + GlobalConfig: globalConfig, + AllowedOrigins: allowedOrigins, + Standalone: config.StandaloneConfig{ + ComponentsPath: componentsPath, + }, + Kubernetes: config.KubernetesConfig{ + ControlPlaneAddress: controlPlaneAddress, + }, + } +} diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go new file mode 100644 index 0000000000000000000000000000000000000000..a1dbeccf280ec97347539b5a094be712bb4bb29c --- /dev/null +++ b/pkg/runtime/runtime.go @@ -0,0 +1,619 @@ +package runtime + +import ( + "context" + "errors" + "fmt" + "net" + "os" + "reflect" + "strings" + "time" + + "github.com/actionscore/actions/pkg/actors" + "github.com/actionscore/actions/pkg/components/state" + "github.com/actionscore/actions/pkg/messaging" + state_loader "github.com/actionscore/actions/pkg/state" + + bindings_loader "github.com/actionscore/actions/pkg/bindings" + "github.com/actionscore/actions/pkg/components/bindings" + + "github.com/actionscore/actions/pkg/http" + jsoniter "github.com/json-iterator/go" + + "github.com/actionscore/actions/pkg/channel" + http_channel "github.com/actionscore/actions/pkg/channel/http" + + "github.com/actionscore/actions/pkg/config" + "github.com/actionscore/actions/pkg/grpc" + "github.com/golang/protobuf/ptypes/empty" + + "github.com/actionscore/actions/pkg/components" + "github.com/actionscore/actions/pkg/modes" + + log "github.com/Sirupsen/logrus" + pb "github.com/actionscore/actions/pkg/proto" +) + +const ( + appConfigEndpoint = "actions/config" + hostIPEnvVar = "HOST_IP" + parallelConcurrency = "parallel" +) + +// ActionsRuntime holds all the core components of the runtime +type ActionsRuntime struct { + runtimeConfig *RuntimeConfig + globalConfig *config.Configuration + components []components.Component + grpc *grpc.GRPCManager + appChannel channel.AppChannel + appConfig config.ApplicationConfig + directMessaging messaging.DirectMessaging + stateStoreRegistry state.StateStoreRegistry + stateStore state.StateStore + actor actors.Actors + bindingsRegistry bindings.BindingsRegistry + inputBindings map[string]bindings.InputBinding + outputBindings map[string]bindings.OutputBinding + json jsoniter.API + hostAddress string +} + +// NewActionsRuntime returns a new runtime with the given runtime config and global config +func NewActionsRuntime(runtimeConfig *RuntimeConfig, globalConfig *config.Configuration) *ActionsRuntime { + return &ActionsRuntime{ + runtimeConfig: runtimeConfig, + globalConfig: globalConfig, + grpc: grpc.NewGRPCManager(), + json: jsoniter.ConfigFastest, + inputBindings: map[string]bindings.InputBinding{}, + outputBindings: map[string]bindings.OutputBinding{}, + stateStoreRegistry: state.NewStateStoreRegistry(), + bindingsRegistry: bindings.NewBindingsRegistry(), + } +} + +// Run performs initialization of the runtime with the runtime and global configurations +func (a *ActionsRuntime) Run() error { + start := time.Now() + log.Infof("%s mode configured", a.runtimeConfig.Mode) + log.Infof("action id: %s", a.runtimeConfig.ID) + + err := a.initRuntime() + if err != nil { + return err + } + + err = a.beginReadInputBindings() + if err != nil { + log.Warn(err) + } + + d := time.Since(start).Seconds() * 1000 + log.Infof("actions initialized. Status: Running. Init Elapsed %vms", d) + + return nil +} + +func (a *ActionsRuntime) initRuntime() error { + err := a.loadComponents() + if err != nil { + log.Warnf("failed to load components: %s", err) + } + + a.blockUntilAppIsReady() + + err = a.setHostAddress() + if err != nil { + log.Warnf("failed to set host address: %s", err) + } + + err = a.createAppChannel() + if err != nil { + log.Warnf("failed to open %s channel to app: %s", string(a.runtimeConfig.ApplicationProtocol), err) + } + + a.loadAppConfiguration() + + err = a.initState(a.stateStoreRegistry) + if err != nil { + log.Warnf("failed to init state: %s", err) + } + + err = a.initPubSub() + if err != nil { + log.Warnf("failed to init pubsub: %s", err) + } + + a.initBindings() + a.initDirectMessaging() + + err = a.initActors() + if err != nil { + log.Warnf("failed to init actors: %s", err) + } + + a.startHTTPServer(a.runtimeConfig.HTTPPort, a.runtimeConfig.AllowedOrigins) + log.Infof("http server is running on port %v", a.runtimeConfig.HTTPPort) + + err = a.startGRPCServer(a.runtimeConfig.GRPCPort) + if err != nil { + log.Fatalf("failed to start gRPC server: %s", err) + } + log.Infof("gRPC server is running on port %v", a.runtimeConfig.GRPCPort) + + return nil +} + +func (a *ActionsRuntime) initBindings() { + if a.appChannel == nil { + log.Warnf("skipping binding initialization: app channel not initialized") + return + } + + bindings_loader.Load() + err := a.initOutputBindings(a.bindingsRegistry) + if err != nil { + log.Warnf("failed to init output bindings: %s", err) + } + + err = a.initInputBindings(a.bindingsRegistry) + if err != nil { + log.Warnf("failed to init input bindings: %s", err) + } +} + +func (a *ActionsRuntime) beginReadInputBindings() error { + for key, b := range a.inputBindings { + go func(name string, binding bindings.InputBinding) { + err := a.readFromBinding(name, binding) + if err != nil { + log.Warnf("error reading from input binding: %s", err) + } + }(key, b) + } + + return nil +} + +func (a *ActionsRuntime) initDirectMessaging() { + a.directMessaging = messaging.NewDirectMessaging(a.runtimeConfig.ID, a.runtimeConfig.GRPCPort, a.runtimeConfig.Mode, a.appChannel, a.grpc.GetGRPCConnection) +} + +// OnComponentUpdated updates the Actions runtime with new or changed components +// This method is invoked from the Actions Control Plane whenever a component update happens +func (a *ActionsRuntime) OnComponentUpdated(component components.Component) { + update := false + + for i, c := range a.components { + if c.Spec.Type == component.Spec.Type && c.Metadata.Name == component.Metadata.Name { + if reflect.DeepEqual(c, component) { + return + } + + a.components[i] = component + update = true + break + } + } + + if !update { + a.components = append(a.components, component) + } + + if strings.Index(component.Spec.Type, "state") == 0 { + store, err := a.stateStoreRegistry.CreateStateStore(component.Spec.Type) + if err == nil { + err := a.stateStore.Init(state.Metadata{ + ConnectionInfo: component.Spec.ConnectionInfo, + Properties: component.Spec.Properties, + }) + if err != nil { + log.Errorf("Error on init state store: %s", err) + } else { + a.stateStore = store + } + } + + return + } else if strings.Index(component.Spec.Type, "bindings") == 0 { + //TODO: implement update for input bindings too + binding, err := a.bindingsRegistry.CreateOutputBinding(component.Spec.Type) + if err != nil { + log.Errorf("Failed to create output binding: %s", err) + return + } + + err = binding.Init(bindings.Metadata{ + ConnectionInfo: component.Spec.ConnectionInfo, + Properties: component.Spec.Properties, + Name: component.Metadata.Name, + }) + if err == nil { + a.outputBindings[component.Metadata.Name] = binding + } + } +} + +func (a *ActionsRuntime) sendBatchOutputBindingsParallel(to []string, data []byte) { + for _, dst := range to { + go func(name string) { + err := a.sendToOutputBinding(name, data) + if err != nil { + log.Error(err) + } + }(dst) + } +} + +func (a *ActionsRuntime) sendBatchOutputBindingsSequential(to []string, data []byte) error { + for _, dst := range to { + err := a.sendToOutputBinding(dst, data) + if err != nil { + return err + } + } + + return nil +} + +func (a *ActionsRuntime) sendToOutputBinding(name string, data []byte) error { + if binding, ok := a.outputBindings[name]; ok { + err := binding.Write(&bindings.WriteRequest{ + Data: data, + }) + return err + } + + return fmt.Errorf("couldn't find output binding %s", name) +} + +func (a *ActionsRuntime) onAppResponse(response *bindings.AppResponse) error { + if len(response.State) > 0 { + go func(reqs []state.SetRequest) { + if a.stateStore != nil { + err := a.stateStore.BulkSet(reqs) + if err != nil { + log.Errorf("error saving state from app response: %s", err) + } + } + }(response.State) + } + + if len(response.To) > 0 { + b, err := a.json.Marshal(&response.Data) + if err != nil { + return err + } + + if response.Concurrency == parallelConcurrency { + a.sendBatchOutputBindingsParallel(response.To, b) + } else { + return a.sendBatchOutputBindingsSequential(response.To, b) + } + } + + return nil +} + +func (a *ActionsRuntime) readFromBinding(name string, binding bindings.InputBinding) error { + err := binding.Read(func(resp *bindings.ReadResponse) error { + if resp != nil { + req := channel.InvokeRequest{ + Metadata: resp.Metadata, + Method: name, + Payload: resp.Data, + } + resp, err := a.appChannel.InvokeMethod(&req) + if err != nil { + return errors.New("error from app") + } + + if resp != nil { + statusCode := http.GetStatusCodeFromMetadata(resp.Metadata) + if statusCode != 200 && statusCode != 201 { + return errors.New("error from app") + } + + var response bindings.AppResponse + err := a.json.Unmarshal(resp.Data, &response) + if err == nil { + err = a.onAppResponse(&response) + if err != nil { + log.Errorf("error executing app response: %s", err) + } + } + } + } + + return nil + }) + return err +} + +func (a *ActionsRuntime) startHTTPServer(port int, allowedOrigins string) { + api := http.NewAPI(a.runtimeConfig.ID, a.appChannel, a.directMessaging, a.stateStore, a.actor) + serverConf := http.NewServerConfig(port, allowedOrigins) + server := http.NewServer(api, serverConf) + server.StartNonBlocking() +} + +func (a *ActionsRuntime) startGRPCServer(port int) error { + api := grpc.NewAPI(a.runtimeConfig.ID, a.appChannel, a.directMessaging, a.actor, a) + serverConf := grpc.NewServerConfig(port) + server := grpc.NewServer(api, serverConf) + err := server.StartNonBlocking() + + return err +} + +func (a *ActionsRuntime) setHostAddress() error { + if a.runtimeConfig.Mode == modes.StandaloneMode { + addrs, err := net.InterfaceAddrs() + if err != nil { + return err + } + + for _, addr := range addrs { + if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + if ipnet.IP.To4() != nil { + a.hostAddress = ipnet.IP.String() + return nil + } + } + } + } else if a.runtimeConfig.Mode == modes.KubernetesMode { + a.hostAddress = os.Getenv(hostIPEnvVar) + } + + return nil +} + +func (a *ActionsRuntime) initInputBindings(registry bindings.BindingsRegistry) error { + for _, c := range a.components { + if strings.Index(c.Spec.Type, "bindings") == 0 { + req := channel.InvokeRequest{ + Method: c.Metadata.Name, + Metadata: map[string]string{http_channel.HTTPVerb: http_channel.Options}, + } + + _, err := a.appChannel.InvokeMethod(&req) + if err != nil { + log.Error(err) + continue + } + + binding, err := registry.CreateInputBinding(c.Spec.Type) + if err != nil { + log.Warnf("failed to create input binding %s: %s", c.Spec.Type, err) + continue + } + + err = binding.Init(bindings.Metadata{ + ConnectionInfo: c.Spec.ConnectionInfo, + Properties: c.Spec.Properties, + Name: c.Metadata.Name, + }) + if err != nil { + log.Warnf("failed to init input binding %s: %s", c.Spec.Type, err) + continue + } + + a.inputBindings[c.Metadata.Name] = binding + } + } + + return nil +} + +func (a *ActionsRuntime) initOutputBindings(registry bindings.BindingsRegistry) error { + for _, c := range a.components { + if strings.Index(c.Spec.Type, "bindings") == 0 { + binding, err := registry.CreateOutputBinding(c.Spec.Type) + if err != nil { + continue + } + + if binding != nil { + err := binding.Init(bindings.Metadata{ + ConnectionInfo: c.Spec.ConnectionInfo, + Properties: c.Spec.Properties, + }) + if err != nil { + log.Warnf("failed to init output binding %s: %s", c.Spec.Type, err) + continue + } + + a.outputBindings[c.Metadata.Name] = binding + } + } + } + + return nil +} + +func (a *ActionsRuntime) initState(registry state.StateStoreRegistry) error { + state_loader.Load() + + for _, s := range a.components { + if strings.Index(s.Spec.Type, "state") == 0 { + store, err := registry.CreateStateStore(s.Spec.Type) + if err != nil { + log.Warn(err) + continue + } + + if store != nil { + err := store.Init(state.Metadata{ + ConnectionInfo: s.Spec.ConnectionInfo, + Properties: s.Spec.Properties, + }) + if err != nil { + return err + } + + a.stateStore = store + break + } + } + } + + return nil +} + +func (a *ActionsRuntime) initPubSub() error { + return nil +} + +func (a *ActionsRuntime) initActors() error { + act := actors.NewActors(a.stateStore, a.appChannel, a.grpc.GetGRPCConnection, actors.Config{ + ActionsID: a.runtimeConfig.ID, + PlacementServiceAddress: a.runtimeConfig.PlacementServiceAddress, + HostedActorTypes: a.appConfig.Entities, + HostAddress: a.hostAddress, + Port: a.runtimeConfig.GRPCPort, + HeartbeatInterval: time.Second * 1, + }) + err := act.Init() + a.actor = act + return err +} + +func (a *ActionsRuntime) loadComponents() error { + var loader components.ComponentLoader + + switch a.runtimeConfig.Mode { + case modes.KubernetesMode: + loader = components.NewKubernetesComponents(a.runtimeConfig.Kubernetes) + case modes.StandaloneMode: + loader = components.NewStandaloneComponents(a.runtimeConfig.Standalone) + default: + return fmt.Errorf("components loader for mode %s not found", a.runtimeConfig.Mode) + } + + allComponents, err := loader.LoadComponents() + if err != nil { + return err + } + + a.components = allComponents + + for _, c := range a.components { + log.Infof("loaded component %s (%s)", c.Metadata.Name, c.Spec.Type) + } + + return nil +} + +// Stop allows for a graceful shutdown of all runtime internal operations or components +func (a *ActionsRuntime) Stop() { + log.Info("stop command issued. Shutting down all operations") +} + +func (a *ActionsRuntime) blockUntilAppIsReady() { + if a.runtimeConfig.ApplicationPort <= 0 { + return + } + + log.Infof("application protocol: %s. waiting on port %v", string(a.runtimeConfig.ApplicationProtocol), a.runtimeConfig.ApplicationPort) + + for { + conn, _ := net.DialTimeout("tcp", net.JoinHostPort("localhost", fmt.Sprintf("%v", a.runtimeConfig.ApplicationPort)), time.Millisecond*500) + if conn != nil { + conn.Close() + break + } + } + + log.Infof("application discovered on port %v", a.runtimeConfig.ApplicationPort) +} + +func (a *ActionsRuntime) loadAppConfiguration() { + if a.appChannel == nil { + return + } + + var appConfigGetFn func() (*config.ApplicationConfig, error) + + switch a.runtimeConfig.ApplicationProtocol { + case HTTPProtocol: + appConfigGetFn = a.getConfigurationHTTP + case GRPCProtocol: + appConfigGetFn = a.getConfigurationGRPC + default: + appConfigGetFn = a.getConfigurationHTTP + } + + appConfig, err := appConfigGetFn() + if err != nil { + return + } + + if appConfig != nil { + a.appConfig = *appConfig + log.Info("application configuration loaded") + } + + return +} + +func (a *ActionsRuntime) getConfigurationHTTP() (*config.ApplicationConfig, error) { + req := channel.InvokeRequest{ + Method: appConfigEndpoint, + Metadata: map[string]string{http_channel.HTTPVerb: http_channel.Get}, + } + + resp, err := a.appChannel.InvokeMethod(&req) + if err != nil { + return nil, err + } + + var config config.ApplicationConfig + err = a.json.Unmarshal(resp.Data, &config) + if err != nil { + return nil, err + } + + return &config, nil +} + +func (a *ActionsRuntime) getConfigurationGRPC() (*config.ApplicationConfig, error) { + client := pb.NewAppClient(a.grpc.AppClient) + resp, err := client.GetConfig(context.Background(), &empty.Empty{}) + if err != nil { + return nil, err + } + + if resp != nil { + return &config.ApplicationConfig{ + Entities: resp.Entities, + }, nil + } + + return nil, nil +} + +func (a *ActionsRuntime) createAppChannel() error { + if a.runtimeConfig.ApplicationPort > 0 { + var channelCreatorFn func(port int) (channel.AppChannel, error) + + switch a.runtimeConfig.ApplicationProtocol { + case GRPCProtocol: + channelCreatorFn = a.grpc.CreateLocalChannel + case HTTPProtocol: + channelCreatorFn = http_channel.CreateLocalChannel + default: + return fmt.Errorf("cannot create app channel for protocol %s", string(a.runtimeConfig.ApplicationProtocol)) + } + + ch, err := channelCreatorFn(a.runtimeConfig.ApplicationPort) + if err != nil { + return err + } + + a.appChannel = ch + } + + return nil +} diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go new file mode 100644 index 0000000000000000000000000000000000000000..8e9d3f2b25082ca63242033f15c9f52e5539ce2c --- /dev/null +++ b/pkg/runtime/runtime_test.go @@ -0,0 +1,25 @@ +package runtime + +import ( + "testing" + + "github.com/actionscore/actions/pkg/config" +) + +func TestNewRuntime(t *testing.T) { + r := NewActionsRuntime(&RuntimeConfig{}, &config.Configuration{}) + if r == nil { + t.Error( + "Expected success but got nil", + ) + } +} + +func TestConfig(t *testing.T) { + c := NewRuntimeConfig("", "", "", "", "", "", "", "", 0, 0, 0) + if c == nil { + t.Error( + "Expected success but got nil", + ) + } +} diff --git a/pkg/action/cosmos_state_store.go b/pkg/state/cosmosdb/cosmosdb.go similarity index 66% rename from pkg/action/cosmos_state_store.go rename to pkg/state/cosmosdb/cosmosdb.go index 2ce510384003725b16c0fbb51298eb95c1fadcb3..38b8c6e2f81209af630dce71e7a869e8842df49c 100644 --- a/pkg/action/cosmos_state_store.go +++ b/pkg/state/cosmosdb/cosmosdb.go @@ -1,9 +1,12 @@ -package action +package cosmosdb import ( "encoding/json" "fmt" - "strings" + + jsoniter "github.com/json-iterator/go" + + "github.com/actionscore/actions/pkg/components/state" documentdb "github.com/a8m/documentdb-go" ) @@ -31,8 +34,8 @@ func NewCosmosDBStateStore() *CosmosDBStateStore { return &CosmosDBStateStore{} } -func (c *CosmosDBStateStore) Init(eventSourceSpec EventSourceSpec) error { - connInfo := eventSourceSpec.ConnectionInfo +func (c *CosmosDBStateStore) Init(metadata state.Metadata) error { + connInfo := metadata.ConnectionInfo b, err := json.Marshal(connInfo) if err != nil { return err @@ -75,73 +78,45 @@ func (c *CosmosDBStateStore) Init(eventSourceSpec EventSourceSpec) error { return nil } -func (c *CosmosDBStateStore) ReadAsync(metadata interface{}, callback func([]byte) error) error { - return nil -} +func (c *CosmosDBStateStore) Get(req *state.GetRequest) (*state.GetResponse, error) { + key := req.Key -func (c *CosmosDBStateStore) GetAll(actionID string) ([]KeyValState, error) { - state := []KeyValState{} items := []CosmosItem{} - _, err := c.client.QueryDocuments( c.collection.Self, - documentdb.NewQuery(fmt.Sprintf(`SELECT * FROM c WHERE CONTAINS(c.id, '%s')`, actionID)), + documentdb.NewQuery("SELECT * FROM ROOT r WHERE r.id=@id", documentdb.P{"@id", key}), &items, - documentdb.CrossPartition(), + documentdb.PartitionKey(key), ) if err != nil { - fmt.Println(err) return nil, err } else if len(items) == 0 { return nil, nil } - for _, i := range items { - state = append(state, KeyValState{ - Key: strings.Replace(i.Id, fmt.Sprintf("%s-", actionID), "", -1), - Value: i.Value, - }) - } - - return state, nil -} - -func (c *CosmosDBStateStore) Read(metadata interface{}) (interface{}, error) { - key := metadata.(string) - - items := []CosmosItem{} - _, err := c.client.QueryDocuments( - c.collection.Self, - documentdb.NewQuery("SELECT * FROM ROOT r WHERE r.id=@id", documentdb.P{"@id", key}), - &items, - documentdb.PartitionKey(key), - ) + b, err := jsoniter.ConfigFastest.Marshal(&items[0].Value) if err != nil { return nil, err - } else if len(items) == 0 { - return nil, nil } - return items[0].Value, nil + return &state.GetResponse{ + Data: b, + }, nil } -func (c *CosmosDBStateStore) Write(data interface{}) error { - state := data.(KeyValState) - key := state.Key - value := state.Value - +func (c *CosmosDBStateStore) Set(req *state.SetRequest) error { items := []CosmosItem{} _, err := c.client.QueryDocuments( c.collection.Self, - documentdb.NewQuery("SELECT * FROM ROOT r WHERE r.id=@id", documentdb.P{"@id", key}), + documentdb.NewQuery("SELECT * FROM ROOT r WHERE r.id=@id", documentdb.P{"@id", req.Key}), &items, - documentdb.PartitionKey(key), + documentdb.PartitionKey(req.Key), ) if err != nil || len(items) > 0 { // Update i := items[0] - i.Value = value - _, err := c.client.ReplaceDocument(i.Self, i, documentdb.PartitionKey(key)) + i.Value = req.Value + _, err := c.client.ReplaceDocument(i.Self, i, documentdb.PartitionKey(req.Key)) if err != nil { return err } @@ -149,11 +124,39 @@ func (c *CosmosDBStateStore) Write(data interface{}) error { } else { // Create i := CosmosItem{ - ID: key, - Value: value, + ID: req.Key, + Value: req.Value, } - _, err := c.client.CreateDocument(c.collection.Self, i, documentdb.PartitionKey(key)) + _, err := c.client.CreateDocument(c.collection.Self, i, documentdb.PartitionKey(req.Key)) + if err != nil { + return err + } + } + + return nil +} + +func (c *CosmosDBStateStore) BulkSet(req []state.SetRequest) error { + for _, s := range req { + err := c.Set(&s) + if err != nil { + return err + } + } + + return nil +} + +func (c *CosmosDBStateStore) Delete(req *state.DeleteRequest) error { + selfLink := fmt.Sprintf("dbs/%s/colls/%s/documents/%s", c.db.Id, c.collection.Id, req.Key) + _, err := c.client.DeleteDocument(selfLink) + return err +} + +func (c *CosmosDBStateStore) BulkDelete(req []state.DeleteRequest) error { + for _, r := range req { + err := c.Delete(&r) if err != nil { return err } diff --git a/pkg/state/redis/redis.go b/pkg/state/redis/redis.go new file mode 100644 index 0000000000000000000000000000000000000000..09eab414c5b8f4ed7d7002811e0706d69c062678 --- /dev/null +++ b/pkg/state/redis/redis.go @@ -0,0 +1,124 @@ +package redis + +import ( + "context" + "encoding/json" + "fmt" + "math/rand" + "strconv" + "time" + + "github.com/actionscore/actions/pkg/components/state" + + "github.com/joomcode/redispipe/redis" + "github.com/joomcode/redispipe/redisconn" + jsoniter "github.com/json-iterator/go" +) + +type RedisStateStore struct { + client *redis.SyncCtx + json jsoniter.API +} + +type RedisCredentials struct { + Host string `json:"redisHost"` + Password string `json:"redisPassword"` +} + +func NewRedisStateStore() *RedisStateStore { + return &RedisStateStore{ + json: jsoniter.ConfigFastest, + } +} + +func (r *RedisStateStore) Init(metadata state.Metadata) error { + rand.Seed(time.Now().Unix()) + + connInfo := metadata.ConnectionInfo + b, err := json.Marshal(connInfo) + if err != nil { + return err + } + + var redisCreds RedisCredentials + err = json.Unmarshal(b, &redisCreds) + if err != nil { + return err + } + + ctx := context.Background() + opts := redisconn.Opts{ + DB: 0, + Password: redisCreds.Password, + } + conn, err := redisconn.Connect(ctx, redisCreds.Host, opts) + if err != nil { + return err + } + + r.client = &redis.SyncCtx{ + S: conn, + } + + return nil +} + +func (r *RedisStateStore) Delete(req *state.DeleteRequest) error { + res := r.client.Do(context.Background(), "DEL", req.Key) + if err := redis.AsError(res); err != nil { + return err + } + + return nil +} + +func (r *RedisStateStore) BulkDelete(req []state.DeleteRequest) error { + keys := make([]interface{}, len(req)) + for i, r := range req { + keys[i] = r + } + + res := r.client.Do(context.Background(), "DEL", keys...) + if err := redis.AsError(res); err != nil { + return err + } + + return nil +} + +func (r *RedisStateStore) Get(req *state.GetRequest) (*state.GetResponse, error) { + res := r.client.Do(context.Background(), "GET", req.Key) + if err := redis.AsError(res); err != nil { + return nil, err + } + + if res == nil { + return &state.GetResponse{}, nil + } + s, _ := strconv.Unquote(fmt.Sprintf("%q", res)) + + return &state.GetResponse{ + Data: []byte(s), + }, nil +} + +func (r *RedisStateStore) Set(req *state.SetRequest) error { + b, _ := r.json.Marshal(req.Value) + res := r.client.Do(context.Background(), "SET", req.Key, b) + if err := redis.AsError(res); err != nil { + return err + } + + return nil +} + +func (r *RedisStateStore) BulkSet(req []state.SetRequest) error { + for _, s := range req { + err := r.Set(&s) + if err != nil { + return err + } + } + + return nil +} diff --git a/pkg/state/state.go b/pkg/state/state.go new file mode 100644 index 0000000000000000000000000000000000000000..23c72ef3550bb2cd60e421289a873bb6993a3ad0 --- /dev/null +++ b/pkg/state/state.go @@ -0,0 +1,13 @@ +package state + +import ( + "github.com/actionscore/actions/pkg/components/state" + "github.com/actionscore/actions/pkg/state/cosmosdb" + "github.com/actionscore/actions/pkg/state/redis" +) + +// Load state stores +func Load() { + state.RegisterStateStore("redis", redis.NewRedisStateStore()) + state.RegisterStateStore("azure.cosmosdb", cosmosdb.NewCosmosDBStateStore()) +} diff --git a/tools/codegen.sh b/tools/codegen.sh index 8044c9aef43d29322371bdbcc01d82047748f498..211b6f91bd962f894e863ff6ce5e11bd60e00bd8 100755 --- a/tools/codegen.sh +++ b/tools/codegen.sh @@ -6,4 +6,4 @@ go get -u k8s.io/code-generator/... cd $GOPATH/src/k8s.io/code-generator # run the code-generator entrypoint script -./generate-groups.sh all "$ROOT_PACKAGE/pkg/client" "$ROOT_PACKAGE/pkg/apis" "eventing:v1alpha1 configuration:v1alpha1" +./generate-groups.sh all "$ROOT_PACKAGE/pkg/client" "$ROOT_PACKAGE/pkg/apis" "components:v1alpha1 configuration:v1alpha1" diff --git a/vendor/github.com/go-redis/redis/.gitignore b/vendor/github.com/go-redis/redis/.gitignore deleted file mode 100644 index ebfe903bcde38355679af2e899f681a6983950ea..0000000000000000000000000000000000000000 --- a/vendor/github.com/go-redis/redis/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.rdb -testdata/*/ diff --git a/vendor/github.com/go-redis/redis/.travis.yml b/vendor/github.com/go-redis/redis/.travis.yml deleted file mode 100644 index 6b110b4cbb4586d7214d88faca8d24116808cd45..0000000000000000000000000000000000000000 --- a/vendor/github.com/go-redis/redis/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -sudo: false -language: go - -services: - - redis-server - -go: - - 1.9.x - - 1.10.x - - 1.11.x - - tip - -matrix: - allow_failures: - - go: tip - -install: - - go get github.com/onsi/ginkgo - - go get github.com/onsi/gomega diff --git a/vendor/github.com/go-redis/redis/CHANGELOG.md b/vendor/github.com/go-redis/redis/CHANGELOG.md deleted file mode 100644 index 19645661a4a7d4ba53bb483b04a3fa308719ac75..0000000000000000000000000000000000000000 --- a/vendor/github.com/go-redis/redis/CHANGELOG.md +++ /dev/null @@ -1,25 +0,0 @@ -# Changelog - -## Unreleased - -- Cluster and Ring pipelines process commands for each node in its own goroutine. - -## 6.14 - -- Added Options.MinIdleConns. -- Added Options.MaxConnAge. -- PoolStats.FreeConns is renamed to PoolStats.IdleConns. -- Add Client.Do to simplify creating custom commands. -- Add Cmd.String, Cmd.Int, Cmd.Int64, Cmd.Uint64, Cmd.Float64, and Cmd.Bool helpers. -- Lower memory usage. - -## v6.13 - -- Ring got new options called `HashReplicas` and `Hash`. It is recommended to set `HashReplicas = 1000` for better keys distribution between shards. -- Cluster client was optimized to use much less memory when reloading cluster state. -- PubSub.ReceiveMessage is re-worked to not use ReceiveTimeout so it does not lose data when timeout occurres. In most cases it is recommended to use PubSub.Channel instead. -- Dialer.KeepAlive is set to 5 minutes by default. - -## v6.12 - -- ClusterClient got new option called `ClusterSlots` which allows to build cluster of normal Redis Servers that don't have cluster mode enabled. See https://godoc.org/github.com/go-redis/redis#example-NewClusterClient--ManualSetup diff --git a/vendor/github.com/go-redis/redis/LICENSE b/vendor/github.com/go-redis/redis/LICENSE deleted file mode 100644 index 298bed9beaf7d43c0d2c464d6fb36dcec733b3bb..0000000000000000000000000000000000000000 --- a/vendor/github.com/go-redis/redis/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2013 The github.com/go-redis/redis Authors. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/go-redis/redis/Makefile b/vendor/github.com/go-redis/redis/Makefile deleted file mode 100644 index fa3b4e004f264dfc59d1ac34cb82ad8e75b7a517..0000000000000000000000000000000000000000 --- a/vendor/github.com/go-redis/redis/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -all: testdeps - go test ./... - go test ./... -short -race - env GOOS=linux GOARCH=386 go test ./... - go vet - go get github.com/gordonklaus/ineffassign - ineffassign . - -testdeps: testdata/redis/src/redis-server - -bench: testdeps - go test ./... -test.run=NONE -test.bench=. -test.benchmem - -.PHONY: all test testdeps bench - -testdata/redis: - mkdir -p $@ - wget -qO- https://github.com/antirez/redis/archive/5.0.tar.gz | tar xvz --strip-components=1 -C $@ - -testdata/redis/src/redis-server: testdata/redis - sed -i.bak 's/libjemalloc.a/libjemalloc.a -lrt/g' $ -} - -func ExampleClient() { - err := client.Set("key", "value", 0).Err() - if err != nil { - panic(err) - } - - val, err := client.Get("key").Result() - if err != nil { - panic(err) - } - fmt.Println("key", val) - - val2, err := client.Get("key2").Result() - if err == redis.Nil { - fmt.Println("key2 does not exist") - } else if err != nil { - panic(err) - } else { - fmt.Println("key2", val2) - } - // Output: key value - // key2 does not exist -} -``` - -## Howto - -Please go through [examples](https://godoc.org/github.com/go-redis/redis#pkg-examples) to get an idea how to use this package. - -## Look and feel - -Some corner cases: - -```go -// SET key value EX 10 NX -set, err := client.SetNX("key", "value", 10*time.Second).Result() - -// SORT list LIMIT 0 2 ASC -vals, err := client.Sort("list", redis.Sort{Offset: 0, Count: 2, Order: "ASC"}).Result() - -// ZRANGEBYSCORE zset -inf +inf WITHSCORES LIMIT 0 2 -vals, err := client.ZRangeByScoreWithScores("zset", redis.ZRangeBy{ - Min: "-inf", - Max: "+inf", - Offset: 0, - Count: 2, -}).Result() - -// ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3 AGGREGATE SUM -vals, err := client.ZInterStore("out", redis.ZStore{Weights: []int64{2, 3}}, "zset1", "zset2").Result() - -// EVAL "return {KEYS[1],ARGV[1]}" 1 "key" "hello" -vals, err := client.Eval("return {KEYS[1],ARGV[1]}", []string{"key"}, "hello").Result() -``` - -## Benchmark - -go-redis vs redigo: - -``` -BenchmarkSetGoRedis10Conns64Bytes-4 200000 7621 ns/op 210 B/op 6 allocs/op -BenchmarkSetGoRedis100Conns64Bytes-4 200000 7554 ns/op 210 B/op 6 allocs/op -BenchmarkSetGoRedis10Conns1KB-4 200000 7697 ns/op 210 B/op 6 allocs/op -BenchmarkSetGoRedis100Conns1KB-4 200000 7688 ns/op 210 B/op 6 allocs/op -BenchmarkSetGoRedis10Conns10KB-4 200000 9214 ns/op 210 B/op 6 allocs/op -BenchmarkSetGoRedis100Conns10KB-4 200000 9181 ns/op 210 B/op 6 allocs/op -BenchmarkSetGoRedis10Conns1MB-4 2000 583242 ns/op 2337 B/op 6 allocs/op -BenchmarkSetGoRedis100Conns1MB-4 2000 583089 ns/op 2338 B/op 6 allocs/op -BenchmarkSetRedigo10Conns64Bytes-4 200000 7576 ns/op 208 B/op 7 allocs/op -BenchmarkSetRedigo100Conns64Bytes-4 200000 7782 ns/op 208 B/op 7 allocs/op -BenchmarkSetRedigo10Conns1KB-4 200000 7958 ns/op 208 B/op 7 allocs/op -BenchmarkSetRedigo100Conns1KB-4 200000 7725 ns/op 208 B/op 7 allocs/op -BenchmarkSetRedigo10Conns10KB-4 100000 18442 ns/op 208 B/op 7 allocs/op -BenchmarkSetRedigo100Conns10KB-4 100000 18818 ns/op 208 B/op 7 allocs/op -BenchmarkSetRedigo10Conns1MB-4 2000 668829 ns/op 226 B/op 7 allocs/op -BenchmarkSetRedigo100Conns1MB-4 2000 679542 ns/op 226 B/op 7 allocs/op -``` - -Redis Cluster: - -``` -BenchmarkRedisPing-4 200000 6983 ns/op 116 B/op 4 allocs/op -BenchmarkRedisClusterPing-4 100000 11535 ns/op 117 B/op 4 allocs/op -``` - -## See also - -- [Golang PostgreSQL ORM](https://github.com/go-pg/pg) -- [Golang msgpack](https://github.com/vmihailenco/msgpack) -- [Golang message task queue](https://github.com/go-msgqueue/msgqueue) diff --git a/vendor/github.com/go-redis/redis/cluster.go b/vendor/github.com/go-redis/redis/cluster.go deleted file mode 100644 index 0cecc62c4b854cd7c8325297b72448a337dc1b2d..0000000000000000000000000000000000000000 --- a/vendor/github.com/go-redis/redis/cluster.go +++ /dev/null @@ -1,1621 +0,0 @@ -package redis - -import ( - "context" - "crypto/tls" - "fmt" - "math" - "math/rand" - "net" - "runtime" - "sort" - "sync" - "sync/atomic" - "time" - - "github.com/go-redis/redis/internal" - "github.com/go-redis/redis/internal/hashtag" - "github.com/go-redis/redis/internal/pool" - "github.com/go-redis/redis/internal/proto" -) - -var errClusterNoNodes = fmt.Errorf("redis: cluster has no nodes") - -// ClusterOptions are used to configure a cluster client and should be -// passed to NewClusterClient. -type ClusterOptions struct { - // A seed list of host:port addresses of cluster nodes. - Addrs []string - - // The maximum number of retries before giving up. Command is retried - // on network errors and MOVED/ASK redirects. - // Default is 8 retries. - MaxRedirects int - - // Enables read-only commands on slave nodes. - ReadOnly bool - // Allows routing read-only commands to the closest master or slave node. - // It automatically enables ReadOnly. - RouteByLatency bool - // Allows routing read-only commands to the random master or slave node. - // It automatically enables ReadOnly. - RouteRandomly bool - - // Optional function that returns cluster slots information. - // It is useful to manually create cluster of standalone Redis servers - // and load-balance read/write operations between master and slaves. - // It can use service like ZooKeeper to maintain configuration information - // and Cluster.ReloadState to manually trigger state reloading. - ClusterSlots func() ([]ClusterSlot, error) - - // Optional hook that is called when a new node is created. - OnNewNode func(*Client) - - // Following options are copied from Options struct. - - OnConnect func(*Conn) error - - Password string - - MaxRetries int - MinRetryBackoff time.Duration - MaxRetryBackoff time.Duration - - DialTimeout time.Duration - ReadTimeout time.Duration - WriteTimeout time.Duration - - // PoolSize applies per cluster node and not for the whole cluster. - PoolSize int - MinIdleConns int - MaxConnAge time.Duration - PoolTimeout time.Duration - IdleTimeout time.Duration - IdleCheckFrequency time.Duration - - TLSConfig *tls.Config -} - -func (opt *ClusterOptions) init() { - if opt.MaxRedirects == -1 { - opt.MaxRedirects = 0 - } else if opt.MaxRedirects == 0 { - opt.MaxRedirects = 8 - } - - if (opt.RouteByLatency || opt.RouteRandomly) && opt.ClusterSlots == nil { - opt.ReadOnly = true - } - - if opt.PoolSize == 0 { - opt.PoolSize = 5 * runtime.NumCPU() - } - - switch opt.ReadTimeout { - case -1: - opt.ReadTimeout = 0 - case 0: - opt.ReadTimeout = 3 * time.Second - } - switch opt.WriteTimeout { - case -1: - opt.WriteTimeout = 0 - case 0: - opt.WriteTimeout = opt.ReadTimeout - } - - switch opt.MinRetryBackoff { - case -1: - opt.MinRetryBackoff = 0 - case 0: - opt.MinRetryBackoff = 8 * time.Millisecond - } - switch opt.MaxRetryBackoff { - case -1: - opt.MaxRetryBackoff = 0 - case 0: - opt.MaxRetryBackoff = 512 * time.Millisecond - } -} - -func (opt *ClusterOptions) clientOptions() *Options { - const disableIdleCheck = -1 - - return &Options{ - OnConnect: opt.OnConnect, - - MaxRetries: opt.MaxRetries, - MinRetryBackoff: opt.MinRetryBackoff, - MaxRetryBackoff: opt.MaxRetryBackoff, - Password: opt.Password, - readOnly: opt.ReadOnly, - - DialTimeout: opt.DialTimeout, - ReadTimeout: opt.ReadTimeout, - WriteTimeout: opt.WriteTimeout, - - PoolSize: opt.PoolSize, - MinIdleConns: opt.MinIdleConns, - MaxConnAge: opt.MaxConnAge, - PoolTimeout: opt.PoolTimeout, - IdleTimeout: opt.IdleTimeout, - IdleCheckFrequency: disableIdleCheck, - - TLSConfig: opt.TLSConfig, - } -} - -//------------------------------------------------------------------------------ - -type clusterNode struct { - Client *Client - - latency uint32 // atomic - generation uint32 // atomic - loading uint32 // atomic -} - -func newClusterNode(clOpt *ClusterOptions, addr string) *clusterNode { - opt := clOpt.clientOptions() - opt.Addr = addr - node := clusterNode{ - Client: NewClient(opt), - } - - node.latency = math.MaxUint32 - if clOpt.RouteByLatency { - go node.updateLatency() - } - - if clOpt.OnNewNode != nil { - clOpt.OnNewNode(node.Client) - } - - return &node -} - -func (n *clusterNode) String() string { - return n.Client.String() -} - -func (n *clusterNode) Close() error { - return n.Client.Close() -} - -func (n *clusterNode) updateLatency() { - const probes = 10 - - var latency uint32 - for i := 0; i < probes; i++ { - start := time.Now() - n.Client.Ping() - probe := uint32(time.Since(start) / time.Microsecond) - latency = (latency + probe) / 2 - } - atomic.StoreUint32(&n.latency, latency) -} - -func (n *clusterNode) Latency() time.Duration { - latency := atomic.LoadUint32(&n.latency) - return time.Duration(latency) * time.Microsecond -} - -func (n *clusterNode) MarkAsLoading() { - atomic.StoreUint32(&n.loading, uint32(time.Now().Unix())) -} - -func (n *clusterNode) Loading() bool { - const minute = int64(time.Minute / time.Second) - - loading := atomic.LoadUint32(&n.loading) - if loading == 0 { - return false - } - if time.Now().Unix()-int64(loading) < minute { - return true - } - atomic.StoreUint32(&n.loading, 0) - return false -} - -func (n *clusterNode) Generation() uint32 { - return atomic.LoadUint32(&n.generation) -} - -func (n *clusterNode) SetGeneration(gen uint32) { - for { - v := atomic.LoadUint32(&n.generation) - if gen < v || atomic.CompareAndSwapUint32(&n.generation, v, gen) { - break - } - } -} - -//------------------------------------------------------------------------------ - -type clusterNodes struct { - opt *ClusterOptions - - mu sync.RWMutex - allAddrs []string - allNodes map[string]*clusterNode - clusterAddrs []string - closed bool - - _generation uint32 // atomic -} - -func newClusterNodes(opt *ClusterOptions) *clusterNodes { - return &clusterNodes{ - opt: opt, - - allAddrs: opt.Addrs, - allNodes: make(map[string]*clusterNode), - } -} - -func (c *clusterNodes) Close() error { - c.mu.Lock() - defer c.mu.Unlock() - - if c.closed { - return nil - } - c.closed = true - - var firstErr error - for _, node := range c.allNodes { - if err := node.Client.Close(); err != nil && firstErr == nil { - firstErr = err - } - } - - c.allNodes = nil - c.clusterAddrs = nil - - return firstErr -} - -func (c *clusterNodes) Addrs() ([]string, error) { - var addrs []string - c.mu.RLock() - closed := c.closed - if !closed { - if len(c.clusterAddrs) > 0 { - addrs = c.clusterAddrs - } else { - addrs = c.allAddrs - } - } - c.mu.RUnlock() - - if closed { - return nil, pool.ErrClosed - } - if len(addrs) == 0 { - return nil, errClusterNoNodes - } - return addrs, nil -} - -func (c *clusterNodes) NextGeneration() uint32 { - return atomic.AddUint32(&c._generation, 1) -} - -// GC removes unused nodes. -func (c *clusterNodes) GC(generation uint32) { - var collected []*clusterNode - c.mu.Lock() - for addr, node := range c.allNodes { - if node.Generation() >= generation { - continue - } - - c.clusterAddrs = remove(c.clusterAddrs, addr) - delete(c.allNodes, addr) - collected = append(collected, node) - } - c.mu.Unlock() - - for _, node := range collected { - _ = node.Client.Close() - } -} - -func (c *clusterNodes) Get(addr string) (*clusterNode, error) { - var node *clusterNode - var err error - c.mu.RLock() - if c.closed { - err = pool.ErrClosed - } else { - node = c.allNodes[addr] - } - c.mu.RUnlock() - return node, err -} - -func (c *clusterNodes) GetOrCreate(addr string) (*clusterNode, error) { - node, err := c.Get(addr) - if err != nil { - return nil, err - } - if node != nil { - return node, nil - } - - c.mu.Lock() - defer c.mu.Unlock() - - if c.closed { - return nil, pool.ErrClosed - } - - node, ok := c.allNodes[addr] - if ok { - return node, err - } - - node = newClusterNode(c.opt, addr) - - c.allAddrs = appendIfNotExists(c.allAddrs, addr) - c.clusterAddrs = append(c.clusterAddrs, addr) - c.allNodes[addr] = node - - return node, err -} - -func (c *clusterNodes) All() ([]*clusterNode, error) { - c.mu.RLock() - defer c.mu.RUnlock() - - if c.closed { - return nil, pool.ErrClosed - } - - cp := make([]*clusterNode, 0, len(c.allNodes)) - for _, node := range c.allNodes { - cp = append(cp, node) - } - return cp, nil -} - -func (c *clusterNodes) Random() (*clusterNode, error) { - addrs, err := c.Addrs() - if err != nil { - return nil, err - } - - n := rand.Intn(len(addrs)) - return c.GetOrCreate(addrs[n]) -} - -//------------------------------------------------------------------------------ - -type clusterSlot struct { - start, end int - nodes []*clusterNode -} - -type clusterSlotSlice []*clusterSlot - -func (p clusterSlotSlice) Len() int { - return len(p) -} - -func (p clusterSlotSlice) Less(i, j int) bool { - return p[i].start < p[j].start -} - -func (p clusterSlotSlice) Swap(i, j int) { - p[i], p[j] = p[j], p[i] -} - -type clusterState struct { - nodes *clusterNodes - Masters []*clusterNode - Slaves []*clusterNode - - slots []*clusterSlot - - generation uint32 - createdAt time.Time -} - -func newClusterState( - nodes *clusterNodes, slots []ClusterSlot, origin string, -) (*clusterState, error) { - c := clusterState{ - nodes: nodes, - - slots: make([]*clusterSlot, 0, len(slots)), - - generation: nodes.NextGeneration(), - createdAt: time.Now(), - } - - originHost, _, _ := net.SplitHostPort(origin) - isLoopbackOrigin := isLoopback(originHost) - - for _, slot := range slots { - var nodes []*clusterNode - for i, slotNode := range slot.Nodes { - addr := slotNode.Addr - if !isLoopbackOrigin { - addr = replaceLoopbackHost(addr, originHost) - } - - node, err := c.nodes.GetOrCreate(addr) - if err != nil { - return nil, err - } - - node.SetGeneration(c.generation) - nodes = append(nodes, node) - - if i == 0 { - c.Masters = appendUniqueNode(c.Masters, node) - } else { - c.Slaves = appendUniqueNode(c.Slaves, node) - } - } - - c.slots = append(c.slots, &clusterSlot{ - start: slot.Start, - end: slot.End, - nodes: nodes, - }) - } - - sort.Sort(clusterSlotSlice(c.slots)) - - time.AfterFunc(time.Minute, func() { - nodes.GC(c.generation) - }) - - return &c, nil -} - -func replaceLoopbackHost(nodeAddr, originHost string) string { - nodeHost, nodePort, err := net.SplitHostPort(nodeAddr) - if err != nil { - return nodeAddr - } - - nodeIP := net.ParseIP(nodeHost) - if nodeIP == nil { - return nodeAddr - } - - if !nodeIP.IsLoopback() { - return nodeAddr - } - - // Use origin host which is not loopback and node port. - return net.JoinHostPort(originHost, nodePort) -} - -func isLoopback(host string) bool { - ip := net.ParseIP(host) - if ip == nil { - return true - } - return ip.IsLoopback() -} - -func (c *clusterState) slotMasterNode(slot int) (*clusterNode, error) { - nodes := c.slotNodes(slot) - if len(nodes) > 0 { - return nodes[0], nil - } - return c.nodes.Random() -} - -func (c *clusterState) slotSlaveNode(slot int) (*clusterNode, error) { - nodes := c.slotNodes(slot) - switch len(nodes) { - case 0: - return c.nodes.Random() - case 1: - return nodes[0], nil - case 2: - if slave := nodes[1]; !slave.Loading() { - return slave, nil - } - return nodes[0], nil - default: - var slave *clusterNode - for i := 0; i < 10; i++ { - n := rand.Intn(len(nodes)-1) + 1 - slave = nodes[n] - if !slave.Loading() { - return slave, nil - } - } - - // All slaves are loading - use master. - return nodes[0], nil - } -} - -func (c *clusterState) slotClosestNode(slot int) (*clusterNode, error) { - const threshold = time.Millisecond - - nodes := c.slotNodes(slot) - if len(nodes) == 0 { - return c.nodes.Random() - } - - var node *clusterNode - for _, n := range nodes { - if n.Loading() { - continue - } - if node == nil || node.Latency()-n.Latency() > threshold { - node = n - } - } - return node, nil -} - -func (c *clusterState) slotRandomNode(slot int) *clusterNode { - nodes := c.slotNodes(slot) - n := rand.Intn(len(nodes)) - return nodes[n] -} - -func (c *clusterState) slotNodes(slot int) []*clusterNode { - i := sort.Search(len(c.slots), func(i int) bool { - return c.slots[i].end >= slot - }) - if i >= len(c.slots) { - return nil - } - x := c.slots[i] - if slot >= x.start && slot <= x.end { - return x.nodes - } - return nil -} - -//------------------------------------------------------------------------------ - -type clusterStateHolder struct { - load func() (*clusterState, error) - - state atomic.Value - reloading uint32 // atomic -} - -func newClusterStateHolder(fn func() (*clusterState, error)) *clusterStateHolder { - return &clusterStateHolder{ - load: fn, - } -} - -func (c *clusterStateHolder) Reload() (*clusterState, error) { - state, err := c.load() - if err != nil { - return nil, err - } - c.state.Store(state) - return state, nil -} - -func (c *clusterStateHolder) LazyReload() { - if !atomic.CompareAndSwapUint32(&c.reloading, 0, 1) { - return - } - go func() { - defer atomic.StoreUint32(&c.reloading, 0) - - _, err := c.Reload() - if err != nil { - return - } - time.Sleep(100 * time.Millisecond) - }() -} - -func (c *clusterStateHolder) Get() (*clusterState, error) { - v := c.state.Load() - if v != nil { - state := v.(*clusterState) - if time.Since(state.createdAt) > time.Minute { - c.LazyReload() - } - return state, nil - } - return c.Reload() -} - -func (c *clusterStateHolder) ReloadOrGet() (*clusterState, error) { - state, err := c.Reload() - if err == nil { - return state, nil - } - return c.Get() -} - -//------------------------------------------------------------------------------ - -// ClusterClient is a Redis Cluster client representing a pool of zero -// or more underlying connections. It's safe for concurrent use by -// multiple goroutines. -type ClusterClient struct { - cmdable - - ctx context.Context - - opt *ClusterOptions - nodes *clusterNodes - state *clusterStateHolder - cmdsInfoCache *cmdsInfoCache - - process func(Cmder) error - processPipeline func([]Cmder) error - processTxPipeline func([]Cmder) error -} - -// NewClusterClient returns a Redis Cluster client as described in -// http://redis.io/topics/cluster-spec. -func NewClusterClient(opt *ClusterOptions) *ClusterClient { - opt.init() - - c := &ClusterClient{ - opt: opt, - nodes: newClusterNodes(opt), - } - c.state = newClusterStateHolder(c.loadState) - c.cmdsInfoCache = newCmdsInfoCache(c.cmdsInfo) - - c.process = c.defaultProcess - c.processPipeline = c.defaultProcessPipeline - c.processTxPipeline = c.defaultProcessTxPipeline - - c.init() - if opt.IdleCheckFrequency > 0 { - go c.reaper(opt.IdleCheckFrequency) - } - - return c -} - -func (c *ClusterClient) init() { - c.cmdable.setProcessor(c.Process) -} - -// ReloadState reloads cluster state. If available it calls ClusterSlots func -// to get cluster slots information. -func (c *ClusterClient) ReloadState() error { - _, err := c.state.Reload() - return err -} - -func (c *ClusterClient) Context() context.Context { - if c.ctx != nil { - return c.ctx - } - return context.Background() -} - -func (c *ClusterClient) WithContext(ctx context.Context) *ClusterClient { - if ctx == nil { - panic("nil context") - } - c2 := c.copy() - c2.ctx = ctx - return c2 -} - -func (c *ClusterClient) copy() *ClusterClient { - cp := *c - cp.init() - return &cp -} - -// Options returns read-only Options that were used to create the client. -func (c *ClusterClient) Options() *ClusterOptions { - return c.opt -} - -func (c *ClusterClient) retryBackoff(attempt int) time.Duration { - return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff) -} - -func (c *ClusterClient) cmdsInfo() (map[string]*CommandInfo, error) { - addrs, err := c.nodes.Addrs() - if err != nil { - return nil, err - } - - var firstErr error - for _, addr := range addrs { - node, err := c.nodes.Get(addr) - if err != nil { - return nil, err - } - if node == nil { - continue - } - - info, err := node.Client.Command().Result() - if err == nil { - return info, nil - } - if firstErr == nil { - firstErr = err - } - } - return nil, firstErr -} - -func (c *ClusterClient) cmdInfo(name string) *CommandInfo { - cmdsInfo, err := c.cmdsInfoCache.Get() - if err != nil { - return nil - } - - info := cmdsInfo[name] - if info == nil { - internal.Logf("info for cmd=%s not found", name) - } - return info -} - -func cmdSlot(cmd Cmder, pos int) int { - if pos == 0 { - return hashtag.RandomSlot() - } - firstKey := cmd.stringArg(pos) - return hashtag.Slot(firstKey) -} - -func (c *ClusterClient) cmdSlot(cmd Cmder) int { - args := cmd.Args() - if args[0] == "cluster" && args[1] == "getkeysinslot" { - return args[2].(int) - } - - cmdInfo := c.cmdInfo(cmd.Name()) - return cmdSlot(cmd, cmdFirstKeyPos(cmd, cmdInfo)) -} - -func (c *ClusterClient) cmdSlotAndNode(cmd Cmder) (int, *clusterNode, error) { - state, err := c.state.Get() - if err != nil { - return 0, nil, err - } - - cmdInfo := c.cmdInfo(cmd.Name()) - slot := c.cmdSlot(cmd) - - if c.opt.ReadOnly && cmdInfo != nil && cmdInfo.ReadOnly { - if c.opt.RouteByLatency { - node, err := state.slotClosestNode(slot) - return slot, node, err - } - - if c.opt.RouteRandomly { - node := state.slotRandomNode(slot) - return slot, node, nil - } - - node, err := state.slotSlaveNode(slot) - return slot, node, err - } - - node, err := state.slotMasterNode(slot) - return slot, node, err -} - -func (c *ClusterClient) slotMasterNode(slot int) (*clusterNode, error) { - state, err := c.state.Get() - if err != nil { - return nil, err - } - - nodes := state.slotNodes(slot) - if len(nodes) > 0 { - return nodes[0], nil - } - return c.nodes.Random() -} - -func (c *ClusterClient) Watch(fn func(*Tx) error, keys ...string) error { - if len(keys) == 0 { - return fmt.Errorf("redis: Watch requires at least one key") - } - - slot := hashtag.Slot(keys[0]) - for _, key := range keys[1:] { - if hashtag.Slot(key) != slot { - err := fmt.Errorf("redis: Watch requires all keys to be in the same slot") - return err - } - } - - node, err := c.slotMasterNode(slot) - if err != nil { - return err - } - - for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { - if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) - } - - err = node.Client.Watch(fn, keys...) - if err == nil { - break - } - if err != Nil { - c.state.LazyReload() - } - - moved, ask, addr := internal.IsMovedError(err) - if moved || ask { - node, err = c.nodes.GetOrCreate(addr) - if err != nil { - return err - } - continue - } - - if err == pool.ErrClosed || internal.IsReadOnlyError(err) { - node, err = c.slotMasterNode(slot) - if err != nil { - return err - } - continue - } - - if internal.IsRetryableError(err, true) { - continue - } - - return err - } - - return err -} - -// Close closes the cluster client, releasing any open resources. -// -// It is rare to Close a ClusterClient, as the ClusterClient is meant -// to be long-lived and shared between many goroutines. -func (c *ClusterClient) Close() error { - return c.nodes.Close() -} - -// Do creates a Cmd from the args and processes the cmd. -func (c *ClusterClient) Do(args ...interface{}) *Cmd { - cmd := NewCmd(args...) - c.Process(cmd) - return cmd -} - -func (c *ClusterClient) WrapProcess( - fn func(oldProcess func(Cmder) error) func(Cmder) error, -) { - c.process = fn(c.process) -} - -func (c *ClusterClient) Process(cmd Cmder) error { - return c.process(cmd) -} - -func (c *ClusterClient) defaultProcess(cmd Cmder) error { - var node *clusterNode - var ask bool - for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { - if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) - } - - if node == nil { - var err error - _, node, err = c.cmdSlotAndNode(cmd) - if err != nil { - cmd.setErr(err) - break - } - } - - var err error - if ask { - pipe := node.Client.Pipeline() - _ = pipe.Process(NewCmd("ASKING")) - _ = pipe.Process(cmd) - _, err = pipe.Exec() - _ = pipe.Close() - ask = false - } else { - err = node.Client.Process(cmd) - } - - // If there is no error - we are done. - if err == nil { - break - } - if err != Nil { - c.state.LazyReload() - } - - // If slave is loading - pick another node. - if c.opt.ReadOnly && internal.IsLoadingError(err) { - node.MarkAsLoading() - node = nil - continue - } - - var moved bool - var addr string - moved, ask, addr = internal.IsMovedError(err) - if moved || ask { - node, err = c.nodes.GetOrCreate(addr) - if err != nil { - break - } - continue - } - - if err == pool.ErrClosed || internal.IsReadOnlyError(err) { - node = nil - continue - } - - if internal.IsRetryableError(err, true) { - // First retry the same node. - if attempt == 0 { - continue - } - - // Second try random node. - node, err = c.nodes.Random() - if err != nil { - break - } - continue - } - - break - } - - return cmd.Err() -} - -// ForEachMaster concurrently calls the fn on each master node in the cluster. -// It returns the first error if any. -func (c *ClusterClient) ForEachMaster(fn func(client *Client) error) error { - state, err := c.state.ReloadOrGet() - if err != nil { - return err - } - - var wg sync.WaitGroup - errCh := make(chan error, 1) - for _, master := range state.Masters { - wg.Add(1) - go func(node *clusterNode) { - defer wg.Done() - err := fn(node.Client) - if err != nil { - select { - case errCh <- err: - default: - } - } - }(master) - } - wg.Wait() - - select { - case err := <-errCh: - return err - default: - return nil - } -} - -// ForEachSlave concurrently calls the fn on each slave node in the cluster. -// It returns the first error if any. -func (c *ClusterClient) ForEachSlave(fn func(client *Client) error) error { - state, err := c.state.ReloadOrGet() - if err != nil { - return err - } - - var wg sync.WaitGroup - errCh := make(chan error, 1) - for _, slave := range state.Slaves { - wg.Add(1) - go func(node *clusterNode) { - defer wg.Done() - err := fn(node.Client) - if err != nil { - select { - case errCh <- err: - default: - } - } - }(slave) - } - wg.Wait() - - select { - case err := <-errCh: - return err - default: - return nil - } -} - -// ForEachNode concurrently calls the fn on each known node in the cluster. -// It returns the first error if any. -func (c *ClusterClient) ForEachNode(fn func(client *Client) error) error { - state, err := c.state.ReloadOrGet() - if err != nil { - return err - } - - var wg sync.WaitGroup - errCh := make(chan error, 1) - worker := func(node *clusterNode) { - defer wg.Done() - err := fn(node.Client) - if err != nil { - select { - case errCh <- err: - default: - } - } - } - - for _, node := range state.Masters { - wg.Add(1) - go worker(node) - } - for _, node := range state.Slaves { - wg.Add(1) - go worker(node) - } - - wg.Wait() - select { - case err := <-errCh: - return err - default: - return nil - } -} - -// PoolStats returns accumulated connection pool stats. -func (c *ClusterClient) PoolStats() *PoolStats { - var acc PoolStats - - state, _ := c.state.Get() - if state == nil { - return &acc - } - - for _, node := range state.Masters { - s := node.Client.connPool.Stats() - acc.Hits += s.Hits - acc.Misses += s.Misses - acc.Timeouts += s.Timeouts - - acc.TotalConns += s.TotalConns - acc.IdleConns += s.IdleConns - acc.StaleConns += s.StaleConns - } - - for _, node := range state.Slaves { - s := node.Client.connPool.Stats() - acc.Hits += s.Hits - acc.Misses += s.Misses - acc.Timeouts += s.Timeouts - - acc.TotalConns += s.TotalConns - acc.IdleConns += s.IdleConns - acc.StaleConns += s.StaleConns - } - - return &acc -} - -func (c *ClusterClient) loadState() (*clusterState, error) { - if c.opt.ClusterSlots != nil { - slots, err := c.opt.ClusterSlots() - if err != nil { - return nil, err - } - return newClusterState(c.nodes, slots, "") - } - - addrs, err := c.nodes.Addrs() - if err != nil { - return nil, err - } - - var firstErr error - for _, addr := range addrs { - node, err := c.nodes.GetOrCreate(addr) - if err != nil { - if firstErr == nil { - firstErr = err - } - continue - } - - slots, err := node.Client.ClusterSlots().Result() - if err != nil { - if firstErr == nil { - firstErr = err - } - continue - } - - return newClusterState(c.nodes, slots, node.Client.opt.Addr) - } - - return nil, firstErr -} - -// reaper closes idle connections to the cluster. -func (c *ClusterClient) reaper(idleCheckFrequency time.Duration) { - ticker := time.NewTicker(idleCheckFrequency) - defer ticker.Stop() - - for range ticker.C { - nodes, err := c.nodes.All() - if err != nil { - break - } - - for _, node := range nodes { - _, err := node.Client.connPool.(*pool.ConnPool).ReapStaleConns() - if err != nil { - internal.Logf("ReapStaleConns failed: %s", err) - } - } - } -} - -func (c *ClusterClient) Pipeline() Pipeliner { - pipe := Pipeline{ - exec: c.processPipeline, - } - pipe.statefulCmdable.setProcessor(pipe.Process) - return &pipe -} - -func (c *ClusterClient) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) { - return c.Pipeline().Pipelined(fn) -} - -func (c *ClusterClient) WrapProcessPipeline( - fn func(oldProcess func([]Cmder) error) func([]Cmder) error, -) { - c.processPipeline = fn(c.processPipeline) -} - -func (c *ClusterClient) defaultProcessPipeline(cmds []Cmder) error { - cmdsMap := newCmdsMap() - err := c.mapCmdsByNode(cmds, cmdsMap) - if err != nil { - setCmdsErr(cmds, err) - return err - } - - for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { - if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) - } - - failedCmds := newCmdsMap() - var wg sync.WaitGroup - - for node, cmds := range cmdsMap.m { - wg.Add(1) - go func(node *clusterNode, cmds []Cmder) { - defer wg.Done() - - cn, err := node.Client.getConn() - if err != nil { - if err == pool.ErrClosed { - c.mapCmdsByNode(cmds, failedCmds) - } else { - setCmdsErr(cmds, err) - } - return - } - - err = c.pipelineProcessCmds(node, cn, cmds, failedCmds) - node.Client.releaseConnStrict(cn, err) - }(node, cmds) - } - - wg.Wait() - if len(failedCmds.m) == 0 { - break - } - cmdsMap = failedCmds - } - - return cmdsFirstErr(cmds) -} - -type cmdsMap struct { - mu sync.Mutex - m map[*clusterNode][]Cmder -} - -func newCmdsMap() *cmdsMap { - return &cmdsMap{ - m: make(map[*clusterNode][]Cmder), - } -} - -func (c *ClusterClient) mapCmdsByNode(cmds []Cmder, cmdsMap *cmdsMap) error { - state, err := c.state.Get() - if err != nil { - setCmdsErr(cmds, err) - return err - } - - cmdsAreReadOnly := c.cmdsAreReadOnly(cmds) - for _, cmd := range cmds { - var node *clusterNode - var err error - if cmdsAreReadOnly { - _, node, err = c.cmdSlotAndNode(cmd) - } else { - slot := c.cmdSlot(cmd) - node, err = state.slotMasterNode(slot) - } - if err != nil { - return err - } - cmdsMap.mu.Lock() - cmdsMap.m[node] = append(cmdsMap.m[node], cmd) - cmdsMap.mu.Unlock() - } - return nil -} - -func (c *ClusterClient) cmdsAreReadOnly(cmds []Cmder) bool { - for _, cmd := range cmds { - cmdInfo := c.cmdInfo(cmd.Name()) - if cmdInfo == nil || !cmdInfo.ReadOnly { - return false - } - } - return true -} - -func (c *ClusterClient) pipelineProcessCmds( - node *clusterNode, cn *pool.Conn, cmds []Cmder, failedCmds *cmdsMap, -) error { - err := cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error { - return writeCmd(wr, cmds...) - }) - if err != nil { - setCmdsErr(cmds, err) - failedCmds.mu.Lock() - failedCmds.m[node] = cmds - failedCmds.mu.Unlock() - return err - } - - err = cn.WithReader(c.opt.ReadTimeout, func(rd *proto.Reader) error { - return c.pipelineReadCmds(node, rd, cmds, failedCmds) - }) - return err -} - -func (c *ClusterClient) pipelineReadCmds( - node *clusterNode, rd *proto.Reader, cmds []Cmder, failedCmds *cmdsMap, -) error { - var firstErr error - for _, cmd := range cmds { - err := cmd.readReply(rd) - if err == nil { - continue - } - - if c.checkMovedErr(cmd, err, failedCmds) { - continue - } - - if internal.IsRedisError(err) { - continue - } - - failedCmds.mu.Lock() - failedCmds.m[node] = append(failedCmds.m[node], cmd) - failedCmds.mu.Unlock() - if firstErr == nil { - firstErr = err - } - } - return firstErr -} - -func (c *ClusterClient) checkMovedErr( - cmd Cmder, err error, failedCmds *cmdsMap, -) bool { - moved, ask, addr := internal.IsMovedError(err) - - if moved { - c.state.LazyReload() - - node, err := c.nodes.GetOrCreate(addr) - if err != nil { - return false - } - - failedCmds.mu.Lock() - failedCmds.m[node] = append(failedCmds.m[node], cmd) - failedCmds.mu.Unlock() - return true - } - - if ask { - node, err := c.nodes.GetOrCreate(addr) - if err != nil { - return false - } - - failedCmds.mu.Lock() - failedCmds.m[node] = append(failedCmds.m[node], NewCmd("ASKING"), cmd) - failedCmds.mu.Unlock() - return true - } - - return false -} - -// TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC. -func (c *ClusterClient) TxPipeline() Pipeliner { - pipe := Pipeline{ - exec: c.processTxPipeline, - } - pipe.statefulCmdable.setProcessor(pipe.Process) - return &pipe -} - -func (c *ClusterClient) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { - return c.TxPipeline().Pipelined(fn) -} - -func (c *ClusterClient) defaultProcessTxPipeline(cmds []Cmder) error { - state, err := c.state.Get() - if err != nil { - return err - } - - cmdsMap := c.mapCmdsBySlot(cmds) - for slot, cmds := range cmdsMap { - node, err := state.slotMasterNode(slot) - if err != nil { - setCmdsErr(cmds, err) - continue - } - cmdsMap := map[*clusterNode][]Cmder{node: cmds} - - for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { - if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) - } - - failedCmds := newCmdsMap() - var wg sync.WaitGroup - - for node, cmds := range cmdsMap { - wg.Add(1) - go func(node *clusterNode, cmds []Cmder) { - defer wg.Done() - - cn, err := node.Client.getConn() - if err != nil { - if err == pool.ErrClosed { - c.mapCmdsByNode(cmds, failedCmds) - } else { - setCmdsErr(cmds, err) - } - return - } - - err = c.txPipelineProcessCmds(node, cn, cmds, failedCmds) - node.Client.releaseConnStrict(cn, err) - }(node, cmds) - } - - wg.Wait() - if len(failedCmds.m) == 0 { - break - } - cmdsMap = failedCmds.m - } - } - - return cmdsFirstErr(cmds) -} - -func (c *ClusterClient) mapCmdsBySlot(cmds []Cmder) map[int][]Cmder { - cmdsMap := make(map[int][]Cmder) - for _, cmd := range cmds { - slot := c.cmdSlot(cmd) - cmdsMap[slot] = append(cmdsMap[slot], cmd) - } - return cmdsMap -} - -func (c *ClusterClient) txPipelineProcessCmds( - node *clusterNode, cn *pool.Conn, cmds []Cmder, failedCmds *cmdsMap, -) error { - err := cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error { - return txPipelineWriteMulti(wr, cmds) - }) - if err != nil { - setCmdsErr(cmds, err) - failedCmds.mu.Lock() - failedCmds.m[node] = cmds - failedCmds.mu.Unlock() - return err - } - - err = cn.WithReader(c.opt.ReadTimeout, func(rd *proto.Reader) error { - err := c.txPipelineReadQueued(rd, cmds, failedCmds) - if err != nil { - setCmdsErr(cmds, err) - return err - } - return pipelineReadCmds(rd, cmds) - }) - return err -} - -func (c *ClusterClient) txPipelineReadQueued( - rd *proto.Reader, cmds []Cmder, failedCmds *cmdsMap, -) error { - // Parse queued replies. - var statusCmd StatusCmd - if err := statusCmd.readReply(rd); err != nil { - return err - } - - for _, cmd := range cmds { - err := statusCmd.readReply(rd) - if err == nil { - continue - } - - if c.checkMovedErr(cmd, err, failedCmds) || internal.IsRedisError(err) { - continue - } - - return err - } - - // Parse number of replies. - line, err := rd.ReadLine() - if err != nil { - if err == Nil { - err = TxFailedErr - } - return err - } - - switch line[0] { - case proto.ErrorReply: - err := proto.ParseErrorReply(line) - for _, cmd := range cmds { - if !c.checkMovedErr(cmd, err, failedCmds) { - break - } - } - return err - case proto.ArrayReply: - // ok - default: - err := fmt.Errorf("redis: expected '*', but got line %q", line) - return err - } - - return nil -} - -func (c *ClusterClient) pubSub() *PubSub { - var node *clusterNode - pubsub := &PubSub{ - opt: c.opt.clientOptions(), - - newConn: func(channels []string) (*pool.Conn, error) { - if node != nil { - panic("node != nil") - } - - slot := hashtag.Slot(channels[0]) - - var err error - node, err = c.slotMasterNode(slot) - if err != nil { - return nil, err - } - - cn, err := node.Client.newConn() - if err != nil { - return nil, err - } - - return cn, nil - }, - closeConn: func(cn *pool.Conn) error { - err := node.Client.connPool.CloseConn(cn) - node = nil - return err - }, - } - pubsub.init() - - return pubsub -} - -// Subscribe subscribes the client to the specified channels. -// Channels can be omitted to create empty subscription. -func (c *ClusterClient) Subscribe(channels ...string) *PubSub { - pubsub := c.pubSub() - if len(channels) > 0 { - _ = pubsub.Subscribe(channels...) - } - return pubsub -} - -// PSubscribe subscribes the client to the given patterns. -// Patterns can be omitted to create empty subscription. -func (c *ClusterClient) PSubscribe(channels ...string) *PubSub { - pubsub := c.pubSub() - if len(channels) > 0 { - _ = pubsub.PSubscribe(channels...) - } - return pubsub -} - -func appendUniqueNode(nodes []*clusterNode, node *clusterNode) []*clusterNode { - for _, n := range nodes { - if n == node { - return nodes - } - } - return append(nodes, node) -} - -func appendIfNotExists(ss []string, es ...string) []string { -loop: - for _, e := range es { - for _, s := range ss { - if s == e { - continue loop - } - } - ss = append(ss, e) - } - return ss -} - -func remove(ss []string, es ...string) []string { - if len(es) == 0 { - return ss[:0] - } - for _, e := range es { - for i, s := range ss { - if s == e { - ss = append(ss[:i], ss[i+1:]...) - break - } - } - } - return ss -} diff --git a/vendor/github.com/go-redis/redis/cluster_commands.go b/vendor/github.com/go-redis/redis/cluster_commands.go deleted file mode 100644 index dff62c902d03dc9db86592fc82c228b45f969e84..0000000000000000000000000000000000000000 --- a/vendor/github.com/go-redis/redis/cluster_commands.go +++ /dev/null @@ -1,22 +0,0 @@ -package redis - -import "sync/atomic" - -func (c *ClusterClient) DBSize() *IntCmd { - cmd := NewIntCmd("dbsize") - var size int64 - err := c.ForEachMaster(func(master *Client) error { - n, err := master.DBSize().Result() - if err != nil { - return err - } - atomic.AddInt64(&size, n) - return nil - }) - if err != nil { - cmd.setErr(err) - return cmd - } - cmd.val = size - return cmd -} diff --git a/vendor/github.com/go-redis/redis/command.go b/vendor/github.com/go-redis/redis/command.go deleted file mode 100644 index cb4f94b122e658a5ea9fb05a686ceeda3586ad50..0000000000000000000000000000000000000000 --- a/vendor/github.com/go-redis/redis/command.go +++ /dev/null @@ -1,1936 +0,0 @@ -package redis - -import ( - "fmt" - "net" - "strconv" - "strings" - "time" - - "github.com/go-redis/redis/internal" - "github.com/go-redis/redis/internal/proto" -) - -type Cmder interface { - Name() string - Args() []interface{} - stringArg(int) string - - readReply(rd *proto.Reader) error - setErr(error) - - readTimeout() *time.Duration - - Err() error -} - -func setCmdsErr(cmds []Cmder, e error) { - for _, cmd := range cmds { - if cmd.Err() == nil { - cmd.setErr(e) - } - } -} - -func cmdsFirstErr(cmds []Cmder) error { - for _, cmd := range cmds { - if err := cmd.Err(); err != nil { - return err - } - } - return nil -} - -func writeCmd(wr *proto.Writer, cmds ...Cmder) error { - for _, cmd := range cmds { - err := wr.WriteArgs(cmd.Args()) - if err != nil { - return err - } - } - return nil -} - -func cmdString(cmd Cmder, val interface{}) string { - var ss []string - for _, arg := range cmd.Args() { - ss = append(ss, fmt.Sprint(arg)) - } - s := strings.Join(ss, " ") - if err := cmd.Err(); err != nil { - return s + ": " + err.Error() - } - if val != nil { - switch vv := val.(type) { - case []byte: - return s + ": " + string(vv) - default: - return s + ": " + fmt.Sprint(val) - } - } - return s - -} - -func cmdFirstKeyPos(cmd Cmder, info *CommandInfo) int { - switch cmd.Name() { - case "eval", "evalsha": - if cmd.stringArg(2) != "0" { - return 3 - } - - return 0 - case "publish": - return 1 - } - if info == nil { - return 0 - } - return int(info.FirstKeyPos) -} - -//------------------------------------------------------------------------------ - -type baseCmd struct { - _args []interface{} - err error - - _readTimeout *time.Duration -} - -var _ Cmder = (*Cmd)(nil) - -func (cmd *baseCmd) Err() error { - return cmd.err -} - -func (cmd *baseCmd) Args() []interface{} { - return cmd._args -} - -func (cmd *baseCmd) stringArg(pos int) string { - if pos < 0 || pos >= len(cmd._args) { - return "" - } - s, _ := cmd._args[pos].(string) - return s -} - -func (cmd *baseCmd) Name() string { - if len(cmd._args) > 0 { - // Cmd name must be lower cased. - s := internal.ToLower(cmd.stringArg(0)) - cmd._args[0] = s - return s - } - return "" -} - -func (cmd *baseCmd) readTimeout() *time.Duration { - return cmd._readTimeout -} - -func (cmd *baseCmd) setReadTimeout(d time.Duration) { - cmd._readTimeout = &d -} - -func (cmd *baseCmd) setErr(e error) { - cmd.err = e -} - -//------------------------------------------------------------------------------ - -type Cmd struct { - baseCmd - - val interface{} -} - -func NewCmd(args ...interface{}) *Cmd { - return &Cmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *Cmd) Val() interface{} { - return cmd.val -} - -func (cmd *Cmd) Result() (interface{}, error) { - return cmd.val, cmd.err -} - -func (cmd *Cmd) String() (string, error) { - if cmd.err != nil { - return "", cmd.err - } - switch val := cmd.val.(type) { - case string: - return val, nil - default: - err := fmt.Errorf("redis: unexpected type=%T for String", val) - return "", err - } -} - -func (cmd *Cmd) Int() (int, error) { - if cmd.err != nil { - return 0, cmd.err - } - switch val := cmd.val.(type) { - case int64: - return int(val), nil - case string: - return strconv.Atoi(val) - default: - err := fmt.Errorf("redis: unexpected type=%T for Int", val) - return 0, err - } -} - -func (cmd *Cmd) Int64() (int64, error) { - if cmd.err != nil { - return 0, cmd.err - } - switch val := cmd.val.(type) { - case int64: - return val, nil - case string: - return strconv.ParseInt(val, 10, 64) - default: - err := fmt.Errorf("redis: unexpected type=%T for Int64", val) - return 0, err - } -} - -func (cmd *Cmd) Uint64() (uint64, error) { - if cmd.err != nil { - return 0, cmd.err - } - switch val := cmd.val.(type) { - case int64: - return uint64(val), nil - case string: - return strconv.ParseUint(val, 10, 64) - default: - err := fmt.Errorf("redis: unexpected type=%T for Uint64", val) - return 0, err - } -} - -func (cmd *Cmd) Float64() (float64, error) { - if cmd.err != nil { - return 0, cmd.err - } - switch val := cmd.val.(type) { - case int64: - return float64(val), nil - case string: - return strconv.ParseFloat(val, 64) - default: - err := fmt.Errorf("redis: unexpected type=%T for Float64", val) - return 0, err - } -} - -func (cmd *Cmd) Bool() (bool, error) { - if cmd.err != nil { - return false, cmd.err - } - switch val := cmd.val.(type) { - case int64: - return val != 0, nil - case string: - return strconv.ParseBool(val) - default: - err := fmt.Errorf("redis: unexpected type=%T for Bool", val) - return false, err - } -} - -func (cmd *Cmd) readReply(rd *proto.Reader) error { - cmd.val, cmd.err = rd.ReadReply(sliceParser) - return cmd.err -} - -// Implements proto.MultiBulkParse -func sliceParser(rd *proto.Reader, n int64) (interface{}, error) { - vals := make([]interface{}, 0, n) - for i := int64(0); i < n; i++ { - v, err := rd.ReadReply(sliceParser) - if err != nil { - if err == Nil { - vals = append(vals, nil) - continue - } - if err, ok := err.(proto.RedisError); ok { - vals = append(vals, err) - continue - } - return nil, err - } - - switch v := v.(type) { - case string: - vals = append(vals, v) - default: - vals = append(vals, v) - } - } - return vals, nil -} - -//------------------------------------------------------------------------------ - -type SliceCmd struct { - baseCmd - - val []interface{} -} - -var _ Cmder = (*SliceCmd)(nil) - -func NewSliceCmd(args ...interface{}) *SliceCmd { - return &SliceCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *SliceCmd) Val() []interface{} { - return cmd.val -} - -func (cmd *SliceCmd) Result() ([]interface{}, error) { - return cmd.val, cmd.err -} - -func (cmd *SliceCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *SliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(sliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]interface{}) - return nil -} - -//------------------------------------------------------------------------------ - -type StatusCmd struct { - baseCmd - - val string -} - -var _ Cmder = (*StatusCmd)(nil) - -func NewStatusCmd(args ...interface{}) *StatusCmd { - return &StatusCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *StatusCmd) Val() string { - return cmd.val -} - -func (cmd *StatusCmd) Result() (string, error) { - return cmd.val, cmd.err -} - -func (cmd *StatusCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *StatusCmd) readReply(rd *proto.Reader) error { - cmd.val, cmd.err = rd.ReadString() - return cmd.err -} - -//------------------------------------------------------------------------------ - -type IntCmd struct { - baseCmd - - val int64 -} - -var _ Cmder = (*IntCmd)(nil) - -func NewIntCmd(args ...interface{}) *IntCmd { - return &IntCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *IntCmd) Val() int64 { - return cmd.val -} - -func (cmd *IntCmd) Result() (int64, error) { - return cmd.val, cmd.err -} - -func (cmd *IntCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *IntCmd) readReply(rd *proto.Reader) error { - cmd.val, cmd.err = rd.ReadIntReply() - return cmd.err -} - -//------------------------------------------------------------------------------ - -type DurationCmd struct { - baseCmd - - val time.Duration - precision time.Duration -} - -var _ Cmder = (*DurationCmd)(nil) - -func NewDurationCmd(precision time.Duration, args ...interface{}) *DurationCmd { - return &DurationCmd{ - baseCmd: baseCmd{_args: args}, - precision: precision, - } -} - -func (cmd *DurationCmd) Val() time.Duration { - return cmd.val -} - -func (cmd *DurationCmd) Result() (time.Duration, error) { - return cmd.val, cmd.err -} - -func (cmd *DurationCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *DurationCmd) readReply(rd *proto.Reader) error { - var n int64 - n, cmd.err = rd.ReadIntReply() - if cmd.err != nil { - return cmd.err - } - cmd.val = time.Duration(n) * cmd.precision - return nil -} - -//------------------------------------------------------------------------------ - -type TimeCmd struct { - baseCmd - - val time.Time -} - -var _ Cmder = (*TimeCmd)(nil) - -func NewTimeCmd(args ...interface{}) *TimeCmd { - return &TimeCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *TimeCmd) Val() time.Time { - return cmd.val -} - -func (cmd *TimeCmd) Result() (time.Time, error) { - return cmd.val, cmd.err -} - -func (cmd *TimeCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *TimeCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(timeParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(time.Time) - return nil -} - -// Implements proto.MultiBulkParse -func timeParser(rd *proto.Reader, n int64) (interface{}, error) { - if n != 2 { - return nil, fmt.Errorf("got %d elements, expected 2", n) - } - - sec, err := rd.ReadInt() - if err != nil { - return nil, err - } - - microsec, err := rd.ReadInt() - if err != nil { - return nil, err - } - - return time.Unix(sec, microsec*1000), nil -} - -//------------------------------------------------------------------------------ - -type BoolCmd struct { - baseCmd - - val bool -} - -var _ Cmder = (*BoolCmd)(nil) - -func NewBoolCmd(args ...interface{}) *BoolCmd { - return &BoolCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *BoolCmd) Val() bool { - return cmd.val -} - -func (cmd *BoolCmd) Result() (bool, error) { - return cmd.val, cmd.err -} - -func (cmd *BoolCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *BoolCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadReply(nil) - // `SET key value NX` returns nil when key already exists. But - // `SETNX key value` returns bool (0/1). So convert nil to bool. - // TODO: is this okay? - if cmd.err == Nil { - cmd.val = false - cmd.err = nil - return nil - } - if cmd.err != nil { - return cmd.err - } - switch v := v.(type) { - case int64: - cmd.val = v == 1 - return nil - case string: - cmd.val = v == "OK" - return nil - default: - cmd.err = fmt.Errorf("got %T, wanted int64 or string", v) - return cmd.err - } -} - -//------------------------------------------------------------------------------ - -type StringCmd struct { - baseCmd - - val string -} - -var _ Cmder = (*StringCmd)(nil) - -func NewStringCmd(args ...interface{}) *StringCmd { - return &StringCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *StringCmd) Val() string { - return cmd.val -} - -func (cmd *StringCmd) Result() (string, error) { - return cmd.Val(), cmd.err -} - -func (cmd *StringCmd) Bytes() ([]byte, error) { - return []byte(cmd.val), cmd.err -} - -func (cmd *StringCmd) Int() (int, error) { - if cmd.err != nil { - return 0, cmd.err - } - return strconv.Atoi(cmd.Val()) -} - -func (cmd *StringCmd) Int64() (int64, error) { - if cmd.err != nil { - return 0, cmd.err - } - return strconv.ParseInt(cmd.Val(), 10, 64) -} - -func (cmd *StringCmd) Uint64() (uint64, error) { - if cmd.err != nil { - return 0, cmd.err - } - return strconv.ParseUint(cmd.Val(), 10, 64) -} - -func (cmd *StringCmd) Float64() (float64, error) { - if cmd.err != nil { - return 0, cmd.err - } - return strconv.ParseFloat(cmd.Val(), 64) -} - -func (cmd *StringCmd) Scan(val interface{}) error { - if cmd.err != nil { - return cmd.err - } - return proto.Scan([]byte(cmd.val), val) -} - -func (cmd *StringCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *StringCmd) readReply(rd *proto.Reader) error { - cmd.val, cmd.err = rd.ReadString() - return cmd.err -} - -//------------------------------------------------------------------------------ - -type FloatCmd struct { - baseCmd - - val float64 -} - -var _ Cmder = (*FloatCmd)(nil) - -func NewFloatCmd(args ...interface{}) *FloatCmd { - return &FloatCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *FloatCmd) Val() float64 { - return cmd.val -} - -func (cmd *FloatCmd) Result() (float64, error) { - return cmd.Val(), cmd.Err() -} - -func (cmd *FloatCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *FloatCmd) readReply(rd *proto.Reader) error { - cmd.val, cmd.err = rd.ReadFloatReply() - return cmd.err -} - -//------------------------------------------------------------------------------ - -type StringSliceCmd struct { - baseCmd - - val []string -} - -var _ Cmder = (*StringSliceCmd)(nil) - -func NewStringSliceCmd(args ...interface{}) *StringSliceCmd { - return &StringSliceCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *StringSliceCmd) Val() []string { - return cmd.val -} - -func (cmd *StringSliceCmd) Result() ([]string, error) { - return cmd.Val(), cmd.Err() -} - -func (cmd *StringSliceCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *StringSliceCmd) ScanSlice(container interface{}) error { - return proto.ScanSlice(cmd.Val(), container) -} - -func (cmd *StringSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(stringSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]string) - return nil -} - -// Implements proto.MultiBulkParse -func stringSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - ss := make([]string, 0, n) - for i := int64(0); i < n; i++ { - s, err := rd.ReadString() - if err == Nil { - ss = append(ss, "") - } else if err != nil { - return nil, err - } else { - ss = append(ss, s) - } - } - return ss, nil -} - -//------------------------------------------------------------------------------ - -type BoolSliceCmd struct { - baseCmd - - val []bool -} - -var _ Cmder = (*BoolSliceCmd)(nil) - -func NewBoolSliceCmd(args ...interface{}) *BoolSliceCmd { - return &BoolSliceCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *BoolSliceCmd) Val() []bool { - return cmd.val -} - -func (cmd *BoolSliceCmd) Result() ([]bool, error) { - return cmd.val, cmd.err -} - -func (cmd *BoolSliceCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *BoolSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(boolSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]bool) - return nil -} - -// Implements proto.MultiBulkParse -func boolSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - bools := make([]bool, 0, n) - for i := int64(0); i < n; i++ { - n, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - bools = append(bools, n == 1) - } - return bools, nil -} - -//------------------------------------------------------------------------------ - -type StringStringMapCmd struct { - baseCmd - - val map[string]string -} - -var _ Cmder = (*StringStringMapCmd)(nil) - -func NewStringStringMapCmd(args ...interface{}) *StringStringMapCmd { - return &StringStringMapCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *StringStringMapCmd) Val() map[string]string { - return cmd.val -} - -func (cmd *StringStringMapCmd) Result() (map[string]string, error) { - return cmd.val, cmd.err -} - -func (cmd *StringStringMapCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *StringStringMapCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(stringStringMapParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(map[string]string) - return nil -} - -// Implements proto.MultiBulkParse -func stringStringMapParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]string, n/2) - for i := int64(0); i < n; i += 2 { - key, err := rd.ReadString() - if err != nil { - return nil, err - } - - value, err := rd.ReadString() - if err != nil { - return nil, err - } - - m[key] = value - } - return m, nil -} - -//------------------------------------------------------------------------------ - -type StringIntMapCmd struct { - baseCmd - - val map[string]int64 -} - -var _ Cmder = (*StringIntMapCmd)(nil) - -func NewStringIntMapCmd(args ...interface{}) *StringIntMapCmd { - return &StringIntMapCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *StringIntMapCmd) Val() map[string]int64 { - return cmd.val -} - -func (cmd *StringIntMapCmd) Result() (map[string]int64, error) { - return cmd.val, cmd.err -} - -func (cmd *StringIntMapCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *StringIntMapCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(stringIntMapParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(map[string]int64) - return nil -} - -// Implements proto.MultiBulkParse -func stringIntMapParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]int64, n/2) - for i := int64(0); i < n; i += 2 { - key, err := rd.ReadString() - if err != nil { - return nil, err - } - - n, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - m[key] = n - } - return m, nil -} - -//------------------------------------------------------------------------------ - -type StringStructMapCmd struct { - baseCmd - - val map[string]struct{} -} - -var _ Cmder = (*StringStructMapCmd)(nil) - -func NewStringStructMapCmd(args ...interface{}) *StringStructMapCmd { - return &StringStructMapCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *StringStructMapCmd) Val() map[string]struct{} { - return cmd.val -} - -func (cmd *StringStructMapCmd) Result() (map[string]struct{}, error) { - return cmd.val, cmd.err -} - -func (cmd *StringStructMapCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *StringStructMapCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(stringStructMapParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(map[string]struct{}) - return nil -} - -// Implements proto.MultiBulkParse -func stringStructMapParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]struct{}, n) - for i := int64(0); i < n; i++ { - key, err := rd.ReadString() - if err != nil { - return nil, err - } - - m[key] = struct{}{} - } - return m, nil -} - -//------------------------------------------------------------------------------ - -type XMessage struct { - ID string - Values map[string]interface{} -} - -type XMessageSliceCmd struct { - baseCmd - - val []XMessage -} - -var _ Cmder = (*XMessageSliceCmd)(nil) - -func NewXMessageSliceCmd(args ...interface{}) *XMessageSliceCmd { - return &XMessageSliceCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *XMessageSliceCmd) Val() []XMessage { - return cmd.val -} - -func (cmd *XMessageSliceCmd) Result() ([]XMessage, error) { - return cmd.val, cmd.err -} - -func (cmd *XMessageSliceCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *XMessageSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(xMessageSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]XMessage) - return nil -} - -// Implements proto.MultiBulkParse -func xMessageSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - msgs := make([]XMessage, 0, n) - for i := int64(0); i < n; i++ { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - id, err := rd.ReadString() - if err != nil { - return nil, err - } - - v, err := rd.ReadArrayReply(stringInterfaceMapParser) - if err != nil { - return nil, err - } - - msgs = append(msgs, XMessage{ - ID: id, - Values: v.(map[string]interface{}), - }) - return nil, nil - }) - if err != nil { - return nil, err - } - } - return msgs, nil -} - -// Implements proto.MultiBulkParse -func stringInterfaceMapParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]interface{}, n/2) - for i := int64(0); i < n; i += 2 { - key, err := rd.ReadString() - if err != nil { - return nil, err - } - - value, err := rd.ReadString() - if err != nil { - return nil, err - } - - m[key] = value - } - return m, nil -} - -//------------------------------------------------------------------------------ - -type XStream struct { - Stream string - Messages []XMessage -} - -type XStreamSliceCmd struct { - baseCmd - - val []XStream -} - -var _ Cmder = (*XStreamSliceCmd)(nil) - -func NewXStreamSliceCmd(args ...interface{}) *XStreamSliceCmd { - return &XStreamSliceCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *XStreamSliceCmd) Val() []XStream { - return cmd.val -} - -func (cmd *XStreamSliceCmd) Result() ([]XStream, error) { - return cmd.val, cmd.err -} - -func (cmd *XStreamSliceCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *XStreamSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(xStreamSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]XStream) - return nil -} - -// Implements proto.MultiBulkParse -func xStreamSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - ret := make([]XStream, 0, n) - for i := int64(0); i < n; i++ { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 2 { - return nil, fmt.Errorf("got %d, wanted 2", n) - } - - stream, err := rd.ReadString() - if err != nil { - return nil, err - } - - v, err := rd.ReadArrayReply(xMessageSliceParser) - if err != nil { - return nil, err - } - - ret = append(ret, XStream{ - Stream: stream, - Messages: v.([]XMessage), - }) - return nil, nil - }) - if err != nil { - return nil, err - } - } - return ret, nil -} - -//------------------------------------------------------------------------------ - -type XPending struct { - Count int64 - Lower string - Higher string - Consumers map[string]int64 -} - -type XPendingCmd struct { - baseCmd - val *XPending -} - -var _ Cmder = (*XPendingCmd)(nil) - -func NewXPendingCmd(args ...interface{}) *XPendingCmd { - return &XPendingCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *XPendingCmd) Val() *XPending { - return cmd.val -} - -func (cmd *XPendingCmd) Result() (*XPending, error) { - return cmd.val, cmd.err -} - -func (cmd *XPendingCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *XPendingCmd) readReply(rd *proto.Reader) error { - var info interface{} - info, cmd.err = rd.ReadArrayReply(xPendingParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = info.(*XPending) - return nil -} - -func xPendingParser(rd *proto.Reader, n int64) (interface{}, error) { - if n != 4 { - return nil, fmt.Errorf("got %d, wanted 4", n) - } - - count, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - lower, err := rd.ReadString() - if err != nil && err != Nil { - return nil, err - } - - higher, err := rd.ReadString() - if err != nil && err != Nil { - return nil, err - } - - pending := &XPending{ - Count: count, - Lower: lower, - Higher: higher, - } - _, err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - for i := int64(0); i < n; i++ { - _, err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 2 { - return nil, fmt.Errorf("got %d, wanted 2", n) - } - - consumerName, err := rd.ReadString() - if err != nil { - return nil, err - } - - consumerPending, err := rd.ReadInt() - if err != nil { - return nil, err - } - - if pending.Consumers == nil { - pending.Consumers = make(map[string]int64) - } - pending.Consumers[consumerName] = consumerPending - - return nil, nil - }) - if err != nil { - return nil, err - } - } - return nil, nil - }) - if err != nil && err != Nil { - return nil, err - } - - return pending, nil -} - -//------------------------------------------------------------------------------ - -type XPendingExt struct { - Id string - Consumer string - Idle time.Duration - RetryCount int64 -} - -type XPendingExtCmd struct { - baseCmd - val []XPendingExt -} - -var _ Cmder = (*XPendingExtCmd)(nil) - -func NewXPendingExtCmd(args ...interface{}) *XPendingExtCmd { - return &XPendingExtCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *XPendingExtCmd) Val() []XPendingExt { - return cmd.val -} - -func (cmd *XPendingExtCmd) Result() ([]XPendingExt, error) { - return cmd.val, cmd.err -} - -func (cmd *XPendingExtCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *XPendingExtCmd) readReply(rd *proto.Reader) error { - var info interface{} - info, cmd.err = rd.ReadArrayReply(xPendingExtSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = info.([]XPendingExt) - return nil -} - -func xPendingExtSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - ret := make([]XPendingExt, 0, n) - for i := int64(0); i < n; i++ { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 4 { - return nil, fmt.Errorf("got %d, wanted 4", n) - } - - id, err := rd.ReadString() - if err != nil { - return nil, err - } - - consumer, err := rd.ReadString() - if err != nil && err != Nil { - return nil, err - } - - idle, err := rd.ReadIntReply() - if err != nil && err != Nil { - return nil, err - } - - retryCount, err := rd.ReadIntReply() - if err != nil && err != Nil { - return nil, err - } - - ret = append(ret, XPendingExt{ - Id: id, - Consumer: consumer, - Idle: time.Duration(idle) * time.Millisecond, - RetryCount: retryCount, - }) - return nil, nil - }) - if err != nil { - return nil, err - } - } - return ret, nil -} - -//------------------------------------------------------------------------------ - -//------------------------------------------------------------------------------ - -type ZSliceCmd struct { - baseCmd - - val []Z -} - -var _ Cmder = (*ZSliceCmd)(nil) - -func NewZSliceCmd(args ...interface{}) *ZSliceCmd { - return &ZSliceCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *ZSliceCmd) Val() []Z { - return cmd.val -} - -func (cmd *ZSliceCmd) Result() ([]Z, error) { - return cmd.val, cmd.err -} - -func (cmd *ZSliceCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *ZSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(zSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]Z) - return nil -} - -// Implements proto.MultiBulkParse -func zSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - zz := make([]Z, n/2) - for i := int64(0); i < n; i += 2 { - var err error - - z := &zz[i/2] - - z.Member, err = rd.ReadString() - if err != nil { - return nil, err - } - - z.Score, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - } - return zz, nil -} - -//------------------------------------------------------------------------------ - -type ZWithKeyCmd struct { - baseCmd - - val ZWithKey -} - -var _ Cmder = (*ZWithKeyCmd)(nil) - -func NewZWithKeyCmd(args ...interface{}) *ZWithKeyCmd { - return &ZWithKeyCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *ZWithKeyCmd) Val() ZWithKey { - return cmd.val -} - -func (cmd *ZWithKeyCmd) Result() (ZWithKey, error) { - return cmd.Val(), cmd.Err() -} - -func (cmd *ZWithKeyCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *ZWithKeyCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(zWithKeyParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(ZWithKey) - return nil -} - -// Implements proto.MultiBulkParse -func zWithKeyParser(rd *proto.Reader, n int64) (interface{}, error) { - if n != 3 { - return nil, fmt.Errorf("got %d elements, expected 3", n) - } - - var z ZWithKey - var err error - - z.Key, err = rd.ReadString() - if err != nil { - return nil, err - } - z.Member, err = rd.ReadString() - if err != nil { - return nil, err - } - z.Score, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - return z, nil -} - -//------------------------------------------------------------------------------ - -type ScanCmd struct { - baseCmd - - page []string - cursor uint64 - - process func(cmd Cmder) error -} - -var _ Cmder = (*ScanCmd)(nil) - -func NewScanCmd(process func(cmd Cmder) error, args ...interface{}) *ScanCmd { - return &ScanCmd{ - baseCmd: baseCmd{_args: args}, - process: process, - } -} - -func (cmd *ScanCmd) Val() (keys []string, cursor uint64) { - return cmd.page, cmd.cursor -} - -func (cmd *ScanCmd) Result() (keys []string, cursor uint64, err error) { - return cmd.page, cmd.cursor, cmd.err -} - -func (cmd *ScanCmd) String() string { - return cmdString(cmd, cmd.page) -} - -func (cmd *ScanCmd) readReply(rd *proto.Reader) error { - cmd.page, cmd.cursor, cmd.err = rd.ReadScanReply() - return cmd.err -} - -// Iterator creates a new ScanIterator. -func (cmd *ScanCmd) Iterator() *ScanIterator { - return &ScanIterator{ - cmd: cmd, - } -} - -//------------------------------------------------------------------------------ - -type ClusterNode struct { - Id string - Addr string -} - -type ClusterSlot struct { - Start int - End int - Nodes []ClusterNode -} - -type ClusterSlotsCmd struct { - baseCmd - - val []ClusterSlot -} - -var _ Cmder = (*ClusterSlotsCmd)(nil) - -func NewClusterSlotsCmd(args ...interface{}) *ClusterSlotsCmd { - return &ClusterSlotsCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *ClusterSlotsCmd) Val() []ClusterSlot { - return cmd.val -} - -func (cmd *ClusterSlotsCmd) Result() ([]ClusterSlot, error) { - return cmd.Val(), cmd.Err() -} - -func (cmd *ClusterSlotsCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *ClusterSlotsCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(clusterSlotsParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]ClusterSlot) - return nil -} - -// Implements proto.MultiBulkParse -func clusterSlotsParser(rd *proto.Reader, n int64) (interface{}, error) { - slots := make([]ClusterSlot, n) - for i := 0; i < len(slots); i++ { - n, err := rd.ReadArrayLen() - if err != nil { - return nil, err - } - if n < 2 { - err := fmt.Errorf("redis: got %d elements in cluster info, expected at least 2", n) - return nil, err - } - - start, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - end, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - nodes := make([]ClusterNode, n-2) - for j := 0; j < len(nodes); j++ { - n, err := rd.ReadArrayLen() - if err != nil { - return nil, err - } - if n != 2 && n != 3 { - err := fmt.Errorf("got %d elements in cluster info address, expected 2 or 3", n) - return nil, err - } - - ip, err := rd.ReadString() - if err != nil { - return nil, err - } - - port, err := rd.ReadString() - if err != nil { - return nil, err - } - - nodes[j].Addr = net.JoinHostPort(ip, port) - - if n == 3 { - id, err := rd.ReadString() - if err != nil { - return nil, err - } - nodes[j].Id = id - } - } - - slots[i] = ClusterSlot{ - Start: int(start), - End: int(end), - Nodes: nodes, - } - } - return slots, nil -} - -//------------------------------------------------------------------------------ - -// GeoLocation is used with GeoAdd to add geospatial location. -type GeoLocation struct { - Name string - Longitude, Latitude, Dist float64 - GeoHash int64 -} - -// GeoRadiusQuery is used with GeoRadius to query geospatial index. -type GeoRadiusQuery struct { - Radius float64 - // Can be m, km, ft, or mi. Default is km. - Unit string - WithCoord bool - WithDist bool - WithGeoHash bool - Count int - // Can be ASC or DESC. Default is no sort order. - Sort string - Store string - StoreDist string -} - -type GeoLocationCmd struct { - baseCmd - - q *GeoRadiusQuery - locations []GeoLocation -} - -var _ Cmder = (*GeoLocationCmd)(nil) - -func NewGeoLocationCmd(q *GeoRadiusQuery, args ...interface{}) *GeoLocationCmd { - args = append(args, q.Radius) - if q.Unit != "" { - args = append(args, q.Unit) - } else { - args = append(args, "km") - } - if q.WithCoord { - args = append(args, "withcoord") - } - if q.WithDist { - args = append(args, "withdist") - } - if q.WithGeoHash { - args = append(args, "withhash") - } - if q.Count > 0 { - args = append(args, "count", q.Count) - } - if q.Sort != "" { - args = append(args, q.Sort) - } - if q.Store != "" { - args = append(args, "store") - args = append(args, q.Store) - } - if q.StoreDist != "" { - args = append(args, "storedist") - args = append(args, q.StoreDist) - } - return &GeoLocationCmd{ - baseCmd: baseCmd{_args: args}, - q: q, - } -} - -func (cmd *GeoLocationCmd) Val() []GeoLocation { - return cmd.locations -} - -func (cmd *GeoLocationCmd) Result() ([]GeoLocation, error) { - return cmd.locations, cmd.err -} - -func (cmd *GeoLocationCmd) String() string { - return cmdString(cmd, cmd.locations) -} - -func (cmd *GeoLocationCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(newGeoLocationSliceParser(cmd.q)) - if cmd.err != nil { - return cmd.err - } - cmd.locations = v.([]GeoLocation) - return nil -} - -func newGeoLocationParser(q *GeoRadiusQuery) proto.MultiBulkParse { - return func(rd *proto.Reader, n int64) (interface{}, error) { - var loc GeoLocation - var err error - - loc.Name, err = rd.ReadString() - if err != nil { - return nil, err - } - if q.WithDist { - loc.Dist, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - } - if q.WithGeoHash { - loc.GeoHash, err = rd.ReadIntReply() - if err != nil { - return nil, err - } - } - if q.WithCoord { - n, err := rd.ReadArrayLen() - if err != nil { - return nil, err - } - if n != 2 { - return nil, fmt.Errorf("got %d coordinates, expected 2", n) - } - - loc.Longitude, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - loc.Latitude, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - } - - return &loc, nil - } -} - -func newGeoLocationSliceParser(q *GeoRadiusQuery) proto.MultiBulkParse { - return func(rd *proto.Reader, n int64) (interface{}, error) { - locs := make([]GeoLocation, 0, n) - for i := int64(0); i < n; i++ { - v, err := rd.ReadReply(newGeoLocationParser(q)) - if err != nil { - return nil, err - } - switch vv := v.(type) { - case string: - locs = append(locs, GeoLocation{ - Name: vv, - }) - case *GeoLocation: - locs = append(locs, *vv) - default: - return nil, fmt.Errorf("got %T, expected string or *GeoLocation", v) - } - } - return locs, nil - } -} - -//------------------------------------------------------------------------------ - -type GeoPos struct { - Longitude, Latitude float64 -} - -type GeoPosCmd struct { - baseCmd - - positions []*GeoPos -} - -var _ Cmder = (*GeoPosCmd)(nil) - -func NewGeoPosCmd(args ...interface{}) *GeoPosCmd { - return &GeoPosCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *GeoPosCmd) Val() []*GeoPos { - return cmd.positions -} - -func (cmd *GeoPosCmd) Result() ([]*GeoPos, error) { - return cmd.Val(), cmd.Err() -} - -func (cmd *GeoPosCmd) String() string { - return cmdString(cmd, cmd.positions) -} - -func (cmd *GeoPosCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(geoPosSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.positions = v.([]*GeoPos) - return nil -} - -func geoPosSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - positions := make([]*GeoPos, 0, n) - for i := int64(0); i < n; i++ { - v, err := rd.ReadReply(geoPosParser) - if err != nil { - if err == Nil { - positions = append(positions, nil) - continue - } - return nil, err - } - switch v := v.(type) { - case *GeoPos: - positions = append(positions, v) - default: - return nil, fmt.Errorf("got %T, expected *GeoPos", v) - } - } - return positions, nil -} - -func geoPosParser(rd *proto.Reader, n int64) (interface{}, error) { - var pos GeoPos - var err error - - pos.Longitude, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - - pos.Latitude, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - - return &pos, nil -} - -//------------------------------------------------------------------------------ - -type CommandInfo struct { - Name string - Arity int8 - Flags []string - FirstKeyPos int8 - LastKeyPos int8 - StepCount int8 - ReadOnly bool -} - -type CommandsInfoCmd struct { - baseCmd - - val map[string]*CommandInfo -} - -var _ Cmder = (*CommandsInfoCmd)(nil) - -func NewCommandsInfoCmd(args ...interface{}) *CommandsInfoCmd { - return &CommandsInfoCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *CommandsInfoCmd) Val() map[string]*CommandInfo { - return cmd.val -} - -func (cmd *CommandsInfoCmd) Result() (map[string]*CommandInfo, error) { - return cmd.Val(), cmd.Err() -} - -func (cmd *CommandsInfoCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *CommandsInfoCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(commandInfoSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(map[string]*CommandInfo) - return nil -} - -// Implements proto.MultiBulkParse -func commandInfoSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]*CommandInfo, n) - for i := int64(0); i < n; i++ { - v, err := rd.ReadReply(commandInfoParser) - if err != nil { - return nil, err - } - vv := v.(*CommandInfo) - m[vv.Name] = vv - - } - return m, nil -} - -func commandInfoParser(rd *proto.Reader, n int64) (interface{}, error) { - var cmd CommandInfo - var err error - - if n != 6 { - return nil, fmt.Errorf("redis: got %d elements in COMMAND reply, wanted 6", n) - } - - cmd.Name, err = rd.ReadString() - if err != nil { - return nil, err - } - - arity, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - cmd.Arity = int8(arity) - - flags, err := rd.ReadReply(stringSliceParser) - if err != nil { - return nil, err - } - cmd.Flags = flags.([]string) - - firstKeyPos, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - cmd.FirstKeyPos = int8(firstKeyPos) - - lastKeyPos, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - cmd.LastKeyPos = int8(lastKeyPos) - - stepCount, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - cmd.StepCount = int8(stepCount) - - for _, flag := range cmd.Flags { - if flag == "readonly" { - cmd.ReadOnly = true - break - } - } - - return &cmd, nil -} - -//------------------------------------------------------------------------------ - -type cmdsInfoCache struct { - fn func() (map[string]*CommandInfo, error) - - once internal.Once - cmds map[string]*CommandInfo -} - -func newCmdsInfoCache(fn func() (map[string]*CommandInfo, error)) *cmdsInfoCache { - return &cmdsInfoCache{ - fn: fn, - } -} - -func (c *cmdsInfoCache) Get() (map[string]*CommandInfo, error) { - err := c.once.Do(func() error { - cmds, err := c.fn() - if err != nil { - return err - } - c.cmds = cmds - return nil - }) - return c.cmds, err -} diff --git a/vendor/github.com/go-redis/redis/commands.go b/vendor/github.com/go-redis/redis/commands.go deleted file mode 100644 index 653e4abe9633a1ee990bb9ba7c4ec3a85b17322f..0000000000000000000000000000000000000000 --- a/vendor/github.com/go-redis/redis/commands.go +++ /dev/null @@ -1,2583 +0,0 @@ -package redis - -import ( - "errors" - "io" - "time" - - "github.com/go-redis/redis/internal" -) - -func usePrecise(dur time.Duration) bool { - return dur < time.Second || dur%time.Second != 0 -} - -func formatMs(dur time.Duration) int64 { - if dur > 0 && dur < time.Millisecond { - internal.Logf( - "specified duration is %s, but minimal supported value is %s", - dur, time.Millisecond, - ) - } - return int64(dur / time.Millisecond) -} - -func formatSec(dur time.Duration) int64 { - if dur > 0 && dur < time.Second { - internal.Logf( - "specified duration is %s, but minimal supported value is %s", - dur, time.Second, - ) - } - return int64(dur / time.Second) -} - -func appendArgs(dst, src []interface{}) []interface{} { - if len(src) == 1 { - if ss, ok := src[0].([]string); ok { - for _, s := range ss { - dst = append(dst, s) - } - return dst - } - } - - for _, v := range src { - dst = append(dst, v) - } - return dst -} - -type Cmdable interface { - Pipeline() Pipeliner - Pipelined(fn func(Pipeliner) error) ([]Cmder, error) - - TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) - TxPipeline() Pipeliner - - Command() *CommandsInfoCmd - ClientGetName() *StringCmd - Echo(message interface{}) *StringCmd - Ping() *StatusCmd - Quit() *StatusCmd - Del(keys ...string) *IntCmd - Unlink(keys ...string) *IntCmd - Dump(key string) *StringCmd - Exists(keys ...string) *IntCmd - Expire(key string, expiration time.Duration) *BoolCmd - ExpireAt(key string, tm time.Time) *BoolCmd - Keys(pattern string) *StringSliceCmd - Migrate(host, port, key string, db int64, timeout time.Duration) *StatusCmd - Move(key string, db int64) *BoolCmd - ObjectRefCount(key string) *IntCmd - ObjectEncoding(key string) *StringCmd - ObjectIdleTime(key string) *DurationCmd - Persist(key string) *BoolCmd - PExpire(key string, expiration time.Duration) *BoolCmd - PExpireAt(key string, tm time.Time) *BoolCmd - PTTL(key string) *DurationCmd - RandomKey() *StringCmd - Rename(key, newkey string) *StatusCmd - RenameNX(key, newkey string) *BoolCmd - Restore(key string, ttl time.Duration, value string) *StatusCmd - RestoreReplace(key string, ttl time.Duration, value string) *StatusCmd - Sort(key string, sort *Sort) *StringSliceCmd - SortStore(key, store string, sort *Sort) *IntCmd - SortInterfaces(key string, sort *Sort) *SliceCmd - Touch(keys ...string) *IntCmd - TTL(key string) *DurationCmd - Type(key string) *StatusCmd - Scan(cursor uint64, match string, count int64) *ScanCmd - SScan(key string, cursor uint64, match string, count int64) *ScanCmd - HScan(key string, cursor uint64, match string, count int64) *ScanCmd - ZScan(key string, cursor uint64, match string, count int64) *ScanCmd - Append(key, value string) *IntCmd - BitCount(key string, bitCount *BitCount) *IntCmd - BitOpAnd(destKey string, keys ...string) *IntCmd - BitOpOr(destKey string, keys ...string) *IntCmd - BitOpXor(destKey string, keys ...string) *IntCmd - BitOpNot(destKey string, key string) *IntCmd - BitPos(key string, bit int64, pos ...int64) *IntCmd - Decr(key string) *IntCmd - DecrBy(key string, decrement int64) *IntCmd - Get(key string) *StringCmd - GetBit(key string, offset int64) *IntCmd - GetRange(key string, start, end int64) *StringCmd - GetSet(key string, value interface{}) *StringCmd - Incr(key string) *IntCmd - IncrBy(key string, value int64) *IntCmd - IncrByFloat(key string, value float64) *FloatCmd - MGet(keys ...string) *SliceCmd - MSet(pairs ...interface{}) *StatusCmd - MSetNX(pairs ...interface{}) *BoolCmd - Set(key string, value interface{}, expiration time.Duration) *StatusCmd - SetBit(key string, offset int64, value int) *IntCmd - SetNX(key string, value interface{}, expiration time.Duration) *BoolCmd - SetXX(key string, value interface{}, expiration time.Duration) *BoolCmd - SetRange(key string, offset int64, value string) *IntCmd - StrLen(key string) *IntCmd - HDel(key string, fields ...string) *IntCmd - HExists(key, field string) *BoolCmd - HGet(key, field string) *StringCmd - HGetAll(key string) *StringStringMapCmd - HIncrBy(key, field string, incr int64) *IntCmd - HIncrByFloat(key, field string, incr float64) *FloatCmd - HKeys(key string) *StringSliceCmd - HLen(key string) *IntCmd - HMGet(key string, fields ...string) *SliceCmd - HMSet(key string, fields map[string]interface{}) *StatusCmd - HSet(key, field string, value interface{}) *BoolCmd - HSetNX(key, field string, value interface{}) *BoolCmd - HVals(key string) *StringSliceCmd - BLPop(timeout time.Duration, keys ...string) *StringSliceCmd - BRPop(timeout time.Duration, keys ...string) *StringSliceCmd - BRPopLPush(source, destination string, timeout time.Duration) *StringCmd - LIndex(key string, index int64) *StringCmd - LInsert(key, op string, pivot, value interface{}) *IntCmd - LInsertBefore(key string, pivot, value interface{}) *IntCmd - LInsertAfter(key string, pivot, value interface{}) *IntCmd - LLen(key string) *IntCmd - LPop(key string) *StringCmd - LPush(key string, values ...interface{}) *IntCmd - LPushX(key string, value interface{}) *IntCmd - LRange(key string, start, stop int64) *StringSliceCmd - LRem(key string, count int64, value interface{}) *IntCmd - LSet(key string, index int64, value interface{}) *StatusCmd - LTrim(key string, start, stop int64) *StatusCmd - RPop(key string) *StringCmd - RPopLPush(source, destination string) *StringCmd - RPush(key string, values ...interface{}) *IntCmd - RPushX(key string, value interface{}) *IntCmd - SAdd(key string, members ...interface{}) *IntCmd - SCard(key string) *IntCmd - SDiff(keys ...string) *StringSliceCmd - SDiffStore(destination string, keys ...string) *IntCmd - SInter(keys ...string) *StringSliceCmd - SInterStore(destination string, keys ...string) *IntCmd - SIsMember(key string, member interface{}) *BoolCmd - SMembers(key string) *StringSliceCmd - SMembersMap(key string) *StringStructMapCmd - SMove(source, destination string, member interface{}) *BoolCmd - SPop(key string) *StringCmd - SPopN(key string, count int64) *StringSliceCmd - SRandMember(key string) *StringCmd - SRandMemberN(key string, count int64) *StringSliceCmd - SRem(key string, members ...interface{}) *IntCmd - SUnion(keys ...string) *StringSliceCmd - SUnionStore(destination string, keys ...string) *IntCmd - XAdd(a *XAddArgs) *StringCmd - XDel(stream string, ids ...string) *IntCmd - XLen(stream string) *IntCmd - XRange(stream, start, stop string) *XMessageSliceCmd - XRangeN(stream, start, stop string, count int64) *XMessageSliceCmd - XRevRange(stream string, start, stop string) *XMessageSliceCmd - XRevRangeN(stream string, start, stop string, count int64) *XMessageSliceCmd - XRead(a *XReadArgs) *XStreamSliceCmd - XReadStreams(streams ...string) *XStreamSliceCmd - XGroupCreate(stream, group, start string) *StatusCmd - XGroupCreateMkStream(stream, group, start string) *StatusCmd - XGroupSetID(stream, group, start string) *StatusCmd - XGroupDestroy(stream, group string) *IntCmd - XGroupDelConsumer(stream, group, consumer string) *IntCmd - XReadGroup(a *XReadGroupArgs) *XStreamSliceCmd - XAck(stream, group string, ids ...string) *IntCmd - XPending(stream, group string) *XPendingCmd - XPendingExt(a *XPendingExtArgs) *XPendingExtCmd - XClaim(a *XClaimArgs) *XMessageSliceCmd - XClaimJustID(a *XClaimArgs) *StringSliceCmd - XTrim(key string, maxLen int64) *IntCmd - XTrimApprox(key string, maxLen int64) *IntCmd - BZPopMax(timeout time.Duration, keys ...string) *ZWithKeyCmd - BZPopMin(timeout time.Duration, keys ...string) *ZWithKeyCmd - ZAdd(key string, members ...Z) *IntCmd - ZAddNX(key string, members ...Z) *IntCmd - ZAddXX(key string, members ...Z) *IntCmd - ZAddCh(key string, members ...Z) *IntCmd - ZAddNXCh(key string, members ...Z) *IntCmd - ZAddXXCh(key string, members ...Z) *IntCmd - ZIncr(key string, member Z) *FloatCmd - ZIncrNX(key string, member Z) *FloatCmd - ZIncrXX(key string, member Z) *FloatCmd - ZCard(key string) *IntCmd - ZCount(key, min, max string) *IntCmd - ZLexCount(key, min, max string) *IntCmd - ZIncrBy(key string, increment float64, member string) *FloatCmd - ZInterStore(destination string, store ZStore, keys ...string) *IntCmd - ZPopMax(key string, count ...int64) *ZSliceCmd - ZPopMin(key string, count ...int64) *ZSliceCmd - ZRange(key string, start, stop int64) *StringSliceCmd - ZRangeWithScores(key string, start, stop int64) *ZSliceCmd - ZRangeByScore(key string, opt ZRangeBy) *StringSliceCmd - ZRangeByLex(key string, opt ZRangeBy) *StringSliceCmd - ZRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd - ZRank(key, member string) *IntCmd - ZRem(key string, members ...interface{}) *IntCmd - ZRemRangeByRank(key string, start, stop int64) *IntCmd - ZRemRangeByScore(key, min, max string) *IntCmd - ZRemRangeByLex(key, min, max string) *IntCmd - ZRevRange(key string, start, stop int64) *StringSliceCmd - ZRevRangeWithScores(key string, start, stop int64) *ZSliceCmd - ZRevRangeByScore(key string, opt ZRangeBy) *StringSliceCmd - ZRevRangeByLex(key string, opt ZRangeBy) *StringSliceCmd - ZRevRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd - ZRevRank(key, member string) *IntCmd - ZScore(key, member string) *FloatCmd - ZUnionStore(dest string, store ZStore, keys ...string) *IntCmd - PFAdd(key string, els ...interface{}) *IntCmd - PFCount(keys ...string) *IntCmd - PFMerge(dest string, keys ...string) *StatusCmd - BgRewriteAOF() *StatusCmd - BgSave() *StatusCmd - ClientKill(ipPort string) *StatusCmd - ClientKillByFilter(keys ...string) *IntCmd - ClientList() *StringCmd - ClientPause(dur time.Duration) *BoolCmd - ClientID() *IntCmd - ConfigGet(parameter string) *SliceCmd - ConfigResetStat() *StatusCmd - ConfigSet(parameter, value string) *StatusCmd - ConfigRewrite() *StatusCmd - DBSize() *IntCmd - FlushAll() *StatusCmd - FlushAllAsync() *StatusCmd - FlushDB() *StatusCmd - FlushDBAsync() *StatusCmd - Info(section ...string) *StringCmd - LastSave() *IntCmd - Save() *StatusCmd - Shutdown() *StatusCmd - ShutdownSave() *StatusCmd - ShutdownNoSave() *StatusCmd - SlaveOf(host, port string) *StatusCmd - Time() *TimeCmd - Eval(script string, keys []string, args ...interface{}) *Cmd - EvalSha(sha1 string, keys []string, args ...interface{}) *Cmd - ScriptExists(hashes ...string) *BoolSliceCmd - ScriptFlush() *StatusCmd - ScriptKill() *StatusCmd - ScriptLoad(script string) *StringCmd - DebugObject(key string) *StringCmd - Publish(channel string, message interface{}) *IntCmd - PubSubChannels(pattern string) *StringSliceCmd - PubSubNumSub(channels ...string) *StringIntMapCmd - PubSubNumPat() *IntCmd - ClusterSlots() *ClusterSlotsCmd - ClusterNodes() *StringCmd - ClusterMeet(host, port string) *StatusCmd - ClusterForget(nodeID string) *StatusCmd - ClusterReplicate(nodeID string) *StatusCmd - ClusterResetSoft() *StatusCmd - ClusterResetHard() *StatusCmd - ClusterInfo() *StringCmd - ClusterKeySlot(key string) *IntCmd - ClusterGetKeysInSlot(slot int, count int) *StringSliceCmd - ClusterCountFailureReports(nodeID string) *IntCmd - ClusterCountKeysInSlot(slot int) *IntCmd - ClusterDelSlots(slots ...int) *StatusCmd - ClusterDelSlotsRange(min, max int) *StatusCmd - ClusterSaveConfig() *StatusCmd - ClusterSlaves(nodeID string) *StringSliceCmd - ClusterFailover() *StatusCmd - ClusterAddSlots(slots ...int) *StatusCmd - ClusterAddSlotsRange(min, max int) *StatusCmd - GeoAdd(key string, geoLocation ...*GeoLocation) *IntCmd - GeoPos(key string, members ...string) *GeoPosCmd - GeoRadius(key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd - GeoRadiusRO(key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd - GeoRadiusByMember(key, member string, query *GeoRadiusQuery) *GeoLocationCmd - GeoRadiusByMemberRO(key, member string, query *GeoRadiusQuery) *GeoLocationCmd - GeoDist(key string, member1, member2, unit string) *FloatCmd - GeoHash(key string, members ...string) *StringSliceCmd - ReadOnly() *StatusCmd - ReadWrite() *StatusCmd - MemoryUsage(key string, samples ...int) *IntCmd -} - -type StatefulCmdable interface { - Cmdable - Auth(password string) *StatusCmd - Select(index int) *StatusCmd - SwapDB(index1, index2 int) *StatusCmd - ClientSetName(name string) *BoolCmd -} - -var _ Cmdable = (*Client)(nil) -var _ Cmdable = (*Tx)(nil) -var _ Cmdable = (*Ring)(nil) -var _ Cmdable = (*ClusterClient)(nil) - -type cmdable struct { - process func(cmd Cmder) error -} - -func (c *cmdable) setProcessor(fn func(Cmder) error) { - c.process = fn -} - -type statefulCmdable struct { - cmdable - process func(cmd Cmder) error -} - -func (c *statefulCmdable) setProcessor(fn func(Cmder) error) { - c.process = fn - c.cmdable.setProcessor(fn) -} - -//------------------------------------------------------------------------------ - -func (c *statefulCmdable) Auth(password string) *StatusCmd { - cmd := NewStatusCmd("auth", password) - c.process(cmd) - return cmd -} - -func (c *cmdable) Echo(message interface{}) *StringCmd { - cmd := NewStringCmd("echo", message) - c.process(cmd) - return cmd -} - -func (c *cmdable) Ping() *StatusCmd { - cmd := NewStatusCmd("ping") - c.process(cmd) - return cmd -} - -func (c *cmdable) Wait(numSlaves int, timeout time.Duration) *IntCmd { - cmd := NewIntCmd("wait", numSlaves, int(timeout/time.Millisecond)) - c.process(cmd) - return cmd -} - -func (c *cmdable) Quit() *StatusCmd { - panic("not implemented") -} - -func (c *statefulCmdable) Select(index int) *StatusCmd { - cmd := NewStatusCmd("select", index) - c.process(cmd) - return cmd -} - -func (c *statefulCmdable) SwapDB(index1, index2 int) *StatusCmd { - cmd := NewStatusCmd("swapdb", index1, index2) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -func (c *cmdable) Command() *CommandsInfoCmd { - cmd := NewCommandsInfoCmd("command") - c.process(cmd) - return cmd -} - -func (c *cmdable) Del(keys ...string) *IntCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "del" - for i, key := range keys { - args[1+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) Unlink(keys ...string) *IntCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "unlink" - for i, key := range keys { - args[1+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) Dump(key string) *StringCmd { - cmd := NewStringCmd("dump", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) Exists(keys ...string) *IntCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "exists" - for i, key := range keys { - args[1+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) Expire(key string, expiration time.Duration) *BoolCmd { - cmd := NewBoolCmd("expire", key, formatSec(expiration)) - c.process(cmd) - return cmd -} - -func (c *cmdable) ExpireAt(key string, tm time.Time) *BoolCmd { - cmd := NewBoolCmd("expireat", key, tm.Unix()) - c.process(cmd) - return cmd -} - -func (c *cmdable) Keys(pattern string) *StringSliceCmd { - cmd := NewStringSliceCmd("keys", pattern) - c.process(cmd) - return cmd -} - -func (c *cmdable) Migrate(host, port, key string, db int64, timeout time.Duration) *StatusCmd { - cmd := NewStatusCmd( - "migrate", - host, - port, - key, - db, - formatMs(timeout), - ) - cmd.setReadTimeout(timeout) - c.process(cmd) - return cmd -} - -func (c *cmdable) Move(key string, db int64) *BoolCmd { - cmd := NewBoolCmd("move", key, db) - c.process(cmd) - return cmd -} - -func (c *cmdable) ObjectRefCount(key string) *IntCmd { - cmd := NewIntCmd("object", "refcount", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) ObjectEncoding(key string) *StringCmd { - cmd := NewStringCmd("object", "encoding", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) ObjectIdleTime(key string) *DurationCmd { - cmd := NewDurationCmd(time.Second, "object", "idletime", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) Persist(key string) *BoolCmd { - cmd := NewBoolCmd("persist", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) PExpire(key string, expiration time.Duration) *BoolCmd { - cmd := NewBoolCmd("pexpire", key, formatMs(expiration)) - c.process(cmd) - return cmd -} - -func (c *cmdable) PExpireAt(key string, tm time.Time) *BoolCmd { - cmd := NewBoolCmd( - "pexpireat", - key, - tm.UnixNano()/int64(time.Millisecond), - ) - c.process(cmd) - return cmd -} - -func (c *cmdable) PTTL(key string) *DurationCmd { - cmd := NewDurationCmd(time.Millisecond, "pttl", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) RandomKey() *StringCmd { - cmd := NewStringCmd("randomkey") - c.process(cmd) - return cmd -} - -func (c *cmdable) Rename(key, newkey string) *StatusCmd { - cmd := NewStatusCmd("rename", key, newkey) - c.process(cmd) - return cmd -} - -func (c *cmdable) RenameNX(key, newkey string) *BoolCmd { - cmd := NewBoolCmd("renamenx", key, newkey) - c.process(cmd) - return cmd -} - -func (c *cmdable) Restore(key string, ttl time.Duration, value string) *StatusCmd { - cmd := NewStatusCmd( - "restore", - key, - formatMs(ttl), - value, - ) - c.process(cmd) - return cmd -} - -func (c *cmdable) RestoreReplace(key string, ttl time.Duration, value string) *StatusCmd { - cmd := NewStatusCmd( - "restore", - key, - formatMs(ttl), - value, - "replace", - ) - c.process(cmd) - return cmd -} - -type Sort struct { - By string - Offset, Count int64 - Get []string - Order string - Alpha bool -} - -func (sort *Sort) args(key string) []interface{} { - args := []interface{}{"sort", key} - if sort.By != "" { - args = append(args, "by", sort.By) - } - if sort.Offset != 0 || sort.Count != 0 { - args = append(args, "limit", sort.Offset, sort.Count) - } - for _, get := range sort.Get { - args = append(args, "get", get) - } - if sort.Order != "" { - args = append(args, sort.Order) - } - if sort.Alpha { - args = append(args, "alpha") - } - return args -} - -func (c *cmdable) Sort(key string, sort *Sort) *StringSliceCmd { - cmd := NewStringSliceCmd(sort.args(key)...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SortStore(key, store string, sort *Sort) *IntCmd { - args := sort.args(key) - if store != "" { - args = append(args, "store", store) - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SortInterfaces(key string, sort *Sort) *SliceCmd { - cmd := NewSliceCmd(sort.args(key)...) - c.process(cmd) - return cmd -} - -func (c *cmdable) Touch(keys ...string) *IntCmd { - args := make([]interface{}, len(keys)+1) - args[0] = "touch" - for i, key := range keys { - args[i+1] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) TTL(key string) *DurationCmd { - cmd := NewDurationCmd(time.Second, "ttl", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) Type(key string) *StatusCmd { - cmd := NewStatusCmd("type", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) Scan(cursor uint64, match string, count int64) *ScanCmd { - args := []interface{}{"scan", cursor} - if match != "" { - args = append(args, "match", match) - } - if count > 0 { - args = append(args, "count", count) - } - cmd := NewScanCmd(c.process, args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SScan(key string, cursor uint64, match string, count int64) *ScanCmd { - args := []interface{}{"sscan", key, cursor} - if match != "" { - args = append(args, "match", match) - } - if count > 0 { - args = append(args, "count", count) - } - cmd := NewScanCmd(c.process, args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) HScan(key string, cursor uint64, match string, count int64) *ScanCmd { - args := []interface{}{"hscan", key, cursor} - if match != "" { - args = append(args, "match", match) - } - if count > 0 { - args = append(args, "count", count) - } - cmd := NewScanCmd(c.process, args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZScan(key string, cursor uint64, match string, count int64) *ScanCmd { - args := []interface{}{"zscan", key, cursor} - if match != "" { - args = append(args, "match", match) - } - if count > 0 { - args = append(args, "count", count) - } - cmd := NewScanCmd(c.process, args...) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -func (c *cmdable) Append(key, value string) *IntCmd { - cmd := NewIntCmd("append", key, value) - c.process(cmd) - return cmd -} - -type BitCount struct { - Start, End int64 -} - -func (c *cmdable) BitCount(key string, bitCount *BitCount) *IntCmd { - args := []interface{}{"bitcount", key} - if bitCount != nil { - args = append( - args, - bitCount.Start, - bitCount.End, - ) - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) bitOp(op, destKey string, keys ...string) *IntCmd { - args := make([]interface{}, 3+len(keys)) - args[0] = "bitop" - args[1] = op - args[2] = destKey - for i, key := range keys { - args[3+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) BitOpAnd(destKey string, keys ...string) *IntCmd { - return c.bitOp("and", destKey, keys...) -} - -func (c *cmdable) BitOpOr(destKey string, keys ...string) *IntCmd { - return c.bitOp("or", destKey, keys...) -} - -func (c *cmdable) BitOpXor(destKey string, keys ...string) *IntCmd { - return c.bitOp("xor", destKey, keys...) -} - -func (c *cmdable) BitOpNot(destKey string, key string) *IntCmd { - return c.bitOp("not", destKey, key) -} - -func (c *cmdable) BitPos(key string, bit int64, pos ...int64) *IntCmd { - args := make([]interface{}, 3+len(pos)) - args[0] = "bitpos" - args[1] = key - args[2] = bit - switch len(pos) { - case 0: - case 1: - args[3] = pos[0] - case 2: - args[3] = pos[0] - args[4] = pos[1] - default: - panic("too many arguments") - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) Decr(key string) *IntCmd { - cmd := NewIntCmd("decr", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) DecrBy(key string, decrement int64) *IntCmd { - cmd := NewIntCmd("decrby", key, decrement) - c.process(cmd) - return cmd -} - -// Redis `GET key` command. It returns redis.Nil error when key does not exist. -func (c *cmdable) Get(key string) *StringCmd { - cmd := NewStringCmd("get", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) GetBit(key string, offset int64) *IntCmd { - cmd := NewIntCmd("getbit", key, offset) - c.process(cmd) - return cmd -} - -func (c *cmdable) GetRange(key string, start, end int64) *StringCmd { - cmd := NewStringCmd("getrange", key, start, end) - c.process(cmd) - return cmd -} - -func (c *cmdable) GetSet(key string, value interface{}) *StringCmd { - cmd := NewStringCmd("getset", key, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) Incr(key string) *IntCmd { - cmd := NewIntCmd("incr", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) IncrBy(key string, value int64) *IntCmd { - cmd := NewIntCmd("incrby", key, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) IncrByFloat(key string, value float64) *FloatCmd { - cmd := NewFloatCmd("incrbyfloat", key, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) MGet(keys ...string) *SliceCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "mget" - for i, key := range keys { - args[1+i] = key - } - cmd := NewSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) MSet(pairs ...interface{}) *StatusCmd { - args := make([]interface{}, 1, 1+len(pairs)) - args[0] = "mset" - args = appendArgs(args, pairs) - cmd := NewStatusCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) MSetNX(pairs ...interface{}) *BoolCmd { - args := make([]interface{}, 1, 1+len(pairs)) - args[0] = "msetnx" - args = appendArgs(args, pairs) - cmd := NewBoolCmd(args...) - c.process(cmd) - return cmd -} - -// Redis `SET key value [expiration]` command. -// -// Use expiration for `SETEX`-like behavior. -// Zero expiration means the key has no expiration time. -func (c *cmdable) Set(key string, value interface{}, expiration time.Duration) *StatusCmd { - args := make([]interface{}, 3, 4) - args[0] = "set" - args[1] = key - args[2] = value - if expiration > 0 { - if usePrecise(expiration) { - args = append(args, "px", formatMs(expiration)) - } else { - args = append(args, "ex", formatSec(expiration)) - } - } - cmd := NewStatusCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SetBit(key string, offset int64, value int) *IntCmd { - cmd := NewIntCmd( - "setbit", - key, - offset, - value, - ) - c.process(cmd) - return cmd -} - -// Redis `SET key value [expiration] NX` command. -// -// Zero expiration means the key has no expiration time. -func (c *cmdable) SetNX(key string, value interface{}, expiration time.Duration) *BoolCmd { - var cmd *BoolCmd - if expiration == 0 { - // Use old `SETNX` to support old Redis versions. - cmd = NewBoolCmd("setnx", key, value) - } else { - if usePrecise(expiration) { - cmd = NewBoolCmd("set", key, value, "px", formatMs(expiration), "nx") - } else { - cmd = NewBoolCmd("set", key, value, "ex", formatSec(expiration), "nx") - } - } - c.process(cmd) - return cmd -} - -// Redis `SET key value [expiration] XX` command. -// -// Zero expiration means the key has no expiration time. -func (c *cmdable) SetXX(key string, value interface{}, expiration time.Duration) *BoolCmd { - var cmd *BoolCmd - if expiration == 0 { - cmd = NewBoolCmd("set", key, value, "xx") - } else { - if usePrecise(expiration) { - cmd = NewBoolCmd("set", key, value, "px", formatMs(expiration), "xx") - } else { - cmd = NewBoolCmd("set", key, value, "ex", formatSec(expiration), "xx") - } - } - c.process(cmd) - return cmd -} - -func (c *cmdable) SetRange(key string, offset int64, value string) *IntCmd { - cmd := NewIntCmd("setrange", key, offset, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) StrLen(key string) *IntCmd { - cmd := NewIntCmd("strlen", key) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -func (c *cmdable) HDel(key string, fields ...string) *IntCmd { - args := make([]interface{}, 2+len(fields)) - args[0] = "hdel" - args[1] = key - for i, field := range fields { - args[2+i] = field - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) HExists(key, field string) *BoolCmd { - cmd := NewBoolCmd("hexists", key, field) - c.process(cmd) - return cmd -} - -func (c *cmdable) HGet(key, field string) *StringCmd { - cmd := NewStringCmd("hget", key, field) - c.process(cmd) - return cmd -} - -func (c *cmdable) HGetAll(key string) *StringStringMapCmd { - cmd := NewStringStringMapCmd("hgetall", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) HIncrBy(key, field string, incr int64) *IntCmd { - cmd := NewIntCmd("hincrby", key, field, incr) - c.process(cmd) - return cmd -} - -func (c *cmdable) HIncrByFloat(key, field string, incr float64) *FloatCmd { - cmd := NewFloatCmd("hincrbyfloat", key, field, incr) - c.process(cmd) - return cmd -} - -func (c *cmdable) HKeys(key string) *StringSliceCmd { - cmd := NewStringSliceCmd("hkeys", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) HLen(key string) *IntCmd { - cmd := NewIntCmd("hlen", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) HMGet(key string, fields ...string) *SliceCmd { - args := make([]interface{}, 2+len(fields)) - args[0] = "hmget" - args[1] = key - for i, field := range fields { - args[2+i] = field - } - cmd := NewSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) HMSet(key string, fields map[string]interface{}) *StatusCmd { - args := make([]interface{}, 2+len(fields)*2) - args[0] = "hmset" - args[1] = key - i := 2 - for k, v := range fields { - args[i] = k - args[i+1] = v - i += 2 - } - cmd := NewStatusCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) HSet(key, field string, value interface{}) *BoolCmd { - cmd := NewBoolCmd("hset", key, field, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) HSetNX(key, field string, value interface{}) *BoolCmd { - cmd := NewBoolCmd("hsetnx", key, field, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) HVals(key string) *StringSliceCmd { - cmd := NewStringSliceCmd("hvals", key) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -func (c *cmdable) BLPop(timeout time.Duration, keys ...string) *StringSliceCmd { - args := make([]interface{}, 1+len(keys)+1) - args[0] = "blpop" - for i, key := range keys { - args[1+i] = key - } - args[len(args)-1] = formatSec(timeout) - cmd := NewStringSliceCmd(args...) - cmd.setReadTimeout(timeout) - c.process(cmd) - return cmd -} - -func (c *cmdable) BRPop(timeout time.Duration, keys ...string) *StringSliceCmd { - args := make([]interface{}, 1+len(keys)+1) - args[0] = "brpop" - for i, key := range keys { - args[1+i] = key - } - args[len(keys)+1] = formatSec(timeout) - cmd := NewStringSliceCmd(args...) - cmd.setReadTimeout(timeout) - c.process(cmd) - return cmd -} - -func (c *cmdable) BRPopLPush(source, destination string, timeout time.Duration) *StringCmd { - cmd := NewStringCmd( - "brpoplpush", - source, - destination, - formatSec(timeout), - ) - cmd.setReadTimeout(timeout) - c.process(cmd) - return cmd -} - -func (c *cmdable) LIndex(key string, index int64) *StringCmd { - cmd := NewStringCmd("lindex", key, index) - c.process(cmd) - return cmd -} - -func (c *cmdable) LInsert(key, op string, pivot, value interface{}) *IntCmd { - cmd := NewIntCmd("linsert", key, op, pivot, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) LInsertBefore(key string, pivot, value interface{}) *IntCmd { - cmd := NewIntCmd("linsert", key, "before", pivot, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) LInsertAfter(key string, pivot, value interface{}) *IntCmd { - cmd := NewIntCmd("linsert", key, "after", pivot, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) LLen(key string) *IntCmd { - cmd := NewIntCmd("llen", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) LPop(key string) *StringCmd { - cmd := NewStringCmd("lpop", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) LPush(key string, values ...interface{}) *IntCmd { - args := make([]interface{}, 2, 2+len(values)) - args[0] = "lpush" - args[1] = key - args = appendArgs(args, values) - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) LPushX(key string, value interface{}) *IntCmd { - cmd := NewIntCmd("lpushx", key, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) LRange(key string, start, stop int64) *StringSliceCmd { - cmd := NewStringSliceCmd( - "lrange", - key, - start, - stop, - ) - c.process(cmd) - return cmd -} - -func (c *cmdable) LRem(key string, count int64, value interface{}) *IntCmd { - cmd := NewIntCmd("lrem", key, count, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) LSet(key string, index int64, value interface{}) *StatusCmd { - cmd := NewStatusCmd("lset", key, index, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) LTrim(key string, start, stop int64) *StatusCmd { - cmd := NewStatusCmd( - "ltrim", - key, - start, - stop, - ) - c.process(cmd) - return cmd -} - -func (c *cmdable) RPop(key string) *StringCmd { - cmd := NewStringCmd("rpop", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) RPopLPush(source, destination string) *StringCmd { - cmd := NewStringCmd("rpoplpush", source, destination) - c.process(cmd) - return cmd -} - -func (c *cmdable) RPush(key string, values ...interface{}) *IntCmd { - args := make([]interface{}, 2, 2+len(values)) - args[0] = "rpush" - args[1] = key - args = appendArgs(args, values) - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) RPushX(key string, value interface{}) *IntCmd { - cmd := NewIntCmd("rpushx", key, value) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -func (c *cmdable) SAdd(key string, members ...interface{}) *IntCmd { - args := make([]interface{}, 2, 2+len(members)) - args[0] = "sadd" - args[1] = key - args = appendArgs(args, members) - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SCard(key string) *IntCmd { - cmd := NewIntCmd("scard", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) SDiff(keys ...string) *StringSliceCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "sdiff" - for i, key := range keys { - args[1+i] = key - } - cmd := NewStringSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SDiffStore(destination string, keys ...string) *IntCmd { - args := make([]interface{}, 2+len(keys)) - args[0] = "sdiffstore" - args[1] = destination - for i, key := range keys { - args[2+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SInter(keys ...string) *StringSliceCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "sinter" - for i, key := range keys { - args[1+i] = key - } - cmd := NewStringSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SInterStore(destination string, keys ...string) *IntCmd { - args := make([]interface{}, 2+len(keys)) - args[0] = "sinterstore" - args[1] = destination - for i, key := range keys { - args[2+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SIsMember(key string, member interface{}) *BoolCmd { - cmd := NewBoolCmd("sismember", key, member) - c.process(cmd) - return cmd -} - -// Redis `SMEMBERS key` command output as a slice -func (c *cmdable) SMembers(key string) *StringSliceCmd { - cmd := NewStringSliceCmd("smembers", key) - c.process(cmd) - return cmd -} - -// Redis `SMEMBERS key` command output as a map -func (c *cmdable) SMembersMap(key string) *StringStructMapCmd { - cmd := NewStringStructMapCmd("smembers", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) SMove(source, destination string, member interface{}) *BoolCmd { - cmd := NewBoolCmd("smove", source, destination, member) - c.process(cmd) - return cmd -} - -// Redis `SPOP key` command. -func (c *cmdable) SPop(key string) *StringCmd { - cmd := NewStringCmd("spop", key) - c.process(cmd) - return cmd -} - -// Redis `SPOP key count` command. -func (c *cmdable) SPopN(key string, count int64) *StringSliceCmd { - cmd := NewStringSliceCmd("spop", key, count) - c.process(cmd) - return cmd -} - -// Redis `SRANDMEMBER key` command. -func (c *cmdable) SRandMember(key string) *StringCmd { - cmd := NewStringCmd("srandmember", key) - c.process(cmd) - return cmd -} - -// Redis `SRANDMEMBER key count` command. -func (c *cmdable) SRandMemberN(key string, count int64) *StringSliceCmd { - cmd := NewStringSliceCmd("srandmember", key, count) - c.process(cmd) - return cmd -} - -func (c *cmdable) SRem(key string, members ...interface{}) *IntCmd { - args := make([]interface{}, 2, 2+len(members)) - args[0] = "srem" - args[1] = key - args = appendArgs(args, members) - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SUnion(keys ...string) *StringSliceCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "sunion" - for i, key := range keys { - args[1+i] = key - } - cmd := NewStringSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SUnionStore(destination string, keys ...string) *IntCmd { - args := make([]interface{}, 2+len(keys)) - args[0] = "sunionstore" - args[1] = destination - for i, key := range keys { - args[2+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -type XAddArgs struct { - Stream string - MaxLen int64 // MAXLEN N - MaxLenApprox int64 // MAXLEN ~ N - ID string - Values map[string]interface{} -} - -func (c *cmdable) XAdd(a *XAddArgs) *StringCmd { - args := make([]interface{}, 0, 6+len(a.Values)*2) - args = append(args, "xadd") - args = append(args, a.Stream) - if a.MaxLen > 0 { - args = append(args, "maxlen", a.MaxLen) - } else if a.MaxLenApprox > 0 { - args = append(args, "maxlen", "~", a.MaxLenApprox) - } - if a.ID != "" { - args = append(args, a.ID) - } else { - args = append(args, "*") - } - for k, v := range a.Values { - args = append(args, k) - args = append(args, v) - } - - cmd := NewStringCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) XDel(stream string, ids ...string) *IntCmd { - args := []interface{}{"xdel", stream} - for _, id := range ids { - args = append(args, id) - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) XLen(stream string) *IntCmd { - cmd := NewIntCmd("xlen", stream) - c.process(cmd) - return cmd -} - -func (c *cmdable) XRange(stream, start, stop string) *XMessageSliceCmd { - cmd := NewXMessageSliceCmd("xrange", stream, start, stop) - c.process(cmd) - return cmd -} - -func (c *cmdable) XRangeN(stream, start, stop string, count int64) *XMessageSliceCmd { - cmd := NewXMessageSliceCmd("xrange", stream, start, stop, "count", count) - c.process(cmd) - return cmd -} - -func (c *cmdable) XRevRange(stream, start, stop string) *XMessageSliceCmd { - cmd := NewXMessageSliceCmd("xrevrange", stream, start, stop) - c.process(cmd) - return cmd -} - -func (c *cmdable) XRevRangeN(stream, start, stop string, count int64) *XMessageSliceCmd { - cmd := NewXMessageSliceCmd("xrevrange", stream, start, stop, "count", count) - c.process(cmd) - return cmd -} - -type XReadArgs struct { - Streams []string - Count int64 - Block time.Duration -} - -func (c *cmdable) XRead(a *XReadArgs) *XStreamSliceCmd { - args := make([]interface{}, 0, 5+len(a.Streams)) - args = append(args, "xread") - if a.Count > 0 { - args = append(args, "count") - args = append(args, a.Count) - } - if a.Block >= 0 { - args = append(args, "block") - args = append(args, int64(a.Block/time.Millisecond)) - } - args = append(args, "streams") - for _, s := range a.Streams { - args = append(args, s) - } - - cmd := NewXStreamSliceCmd(args...) - if a.Block >= 0 { - cmd.setReadTimeout(a.Block) - } - c.process(cmd) - return cmd -} - -func (c *cmdable) XReadStreams(streams ...string) *XStreamSliceCmd { - return c.XRead(&XReadArgs{ - Streams: streams, - Block: -1, - }) -} - -func (c *cmdable) XGroupCreate(stream, group, start string) *StatusCmd { - cmd := NewStatusCmd("xgroup", "create", stream, group, start) - c.process(cmd) - return cmd -} - -func (c *cmdable) XGroupCreateMkStream(stream, group, start string) *StatusCmd { - cmd := NewStatusCmd("xgroup", "create", stream, group, start, "mkstream") - c.process(cmd) - return cmd -} - -func (c *cmdable) XGroupSetID(stream, group, start string) *StatusCmd { - cmd := NewStatusCmd("xgroup", "setid", stream, group, start) - c.process(cmd) - return cmd -} - -func (c *cmdable) XGroupDestroy(stream, group string) *IntCmd { - cmd := NewIntCmd("xgroup", "destroy", stream, group) - c.process(cmd) - return cmd -} - -func (c *cmdable) XGroupDelConsumer(stream, group, consumer string) *IntCmd { - cmd := NewIntCmd("xgroup", "delconsumer", stream, group, consumer) - c.process(cmd) - return cmd -} - -type XReadGroupArgs struct { - Group string - Consumer string - // List of streams and ids. - Streams []string - Count int64 - Block time.Duration - NoAck bool -} - -func (c *cmdable) XReadGroup(a *XReadGroupArgs) *XStreamSliceCmd { - args := make([]interface{}, 0, 8+len(a.Streams)) - args = append(args, "xreadgroup", "group", a.Group, a.Consumer) - if a.Count > 0 { - args = append(args, "count", a.Count) - } - if a.Block >= 0 { - args = append(args, "block", int64(a.Block/time.Millisecond)) - } - if a.NoAck { - args = append(args, "noack") - } - args = append(args, "streams") - for _, s := range a.Streams { - args = append(args, s) - } - - cmd := NewXStreamSliceCmd(args...) - if a.Block >= 0 { - cmd.setReadTimeout(a.Block) - } - c.process(cmd) - return cmd -} - -func (c *cmdable) XAck(stream, group string, ids ...string) *IntCmd { - args := []interface{}{"xack", stream, group} - for _, id := range ids { - args = append(args, id) - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) XPending(stream, group string) *XPendingCmd { - cmd := NewXPendingCmd("xpending", stream, group) - c.process(cmd) - return cmd -} - -type XPendingExtArgs struct { - Stream string - Group string - Start string - End string - Count int64 - Consumer string -} - -func (c *cmdable) XPendingExt(a *XPendingExtArgs) *XPendingExtCmd { - args := make([]interface{}, 0, 7) - args = append(args, "xpending", a.Stream, a.Group, a.Start, a.End, a.Count) - if a.Consumer != "" { - args = append(args, a.Consumer) - } - cmd := NewXPendingExtCmd(args...) - c.process(cmd) - return cmd -} - -type XClaimArgs struct { - Stream string - Group string - Consumer string - MinIdle time.Duration - Messages []string -} - -func (c *cmdable) XClaim(a *XClaimArgs) *XMessageSliceCmd { - args := xClaimArgs(a) - cmd := NewXMessageSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) XClaimJustID(a *XClaimArgs) *StringSliceCmd { - args := xClaimArgs(a) - args = append(args, "justid") - cmd := NewStringSliceCmd(args...) - c.process(cmd) - return cmd -} - -func xClaimArgs(a *XClaimArgs) []interface{} { - args := make([]interface{}, 0, 4+len(a.Messages)) - args = append(args, - "xclaim", - a.Stream, - a.Group, a.Consumer, - int64(a.MinIdle/time.Millisecond)) - for _, id := range a.Messages { - args = append(args, id) - } - return args -} - -func (c *cmdable) XTrim(key string, maxLen int64) *IntCmd { - cmd := NewIntCmd("xtrim", key, "maxlen", maxLen) - c.process(cmd) - return cmd -} - -func (c *cmdable) XTrimApprox(key string, maxLen int64) *IntCmd { - cmd := NewIntCmd("xtrim", key, "maxlen", "~", maxLen) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -// Z represents sorted set member. -type Z struct { - Score float64 - Member interface{} -} - -// ZWithKey represents sorted set member including the name of the key where it was popped. -type ZWithKey struct { - Z - Key string -} - -// ZStore is used as an arg to ZInterStore and ZUnionStore. -type ZStore struct { - Weights []float64 - // Can be SUM, MIN or MAX. - Aggregate string -} - -// Redis `BZPOPMAX key [key ...] timeout` command. -func (c *cmdable) BZPopMax(timeout time.Duration, keys ...string) *ZWithKeyCmd { - args := make([]interface{}, 1+len(keys)+1) - args[0] = "bzpopmax" - for i, key := range keys { - args[1+i] = key - } - args[len(args)-1] = formatSec(timeout) - cmd := NewZWithKeyCmd(args...) - cmd.setReadTimeout(timeout) - c.process(cmd) - return cmd -} - -// Redis `BZPOPMIN key [key ...] timeout` command. -func (c *cmdable) BZPopMin(timeout time.Duration, keys ...string) *ZWithKeyCmd { - args := make([]interface{}, 1+len(keys)+1) - args[0] = "bzpopmin" - for i, key := range keys { - args[1+i] = key - } - args[len(args)-1] = formatSec(timeout) - cmd := NewZWithKeyCmd(args...) - cmd.setReadTimeout(timeout) - c.process(cmd) - return cmd -} - -func (c *cmdable) zAdd(a []interface{}, n int, members ...Z) *IntCmd { - for i, m := range members { - a[n+2*i] = m.Score - a[n+2*i+1] = m.Member - } - cmd := NewIntCmd(a...) - c.process(cmd) - return cmd -} - -// Redis `ZADD key score member [score member ...]` command. -func (c *cmdable) ZAdd(key string, members ...Z) *IntCmd { - const n = 2 - a := make([]interface{}, n+2*len(members)) - a[0], a[1] = "zadd", key - return c.zAdd(a, n, members...) -} - -// Redis `ZADD key NX score member [score member ...]` command. -func (c *cmdable) ZAddNX(key string, members ...Z) *IntCmd { - const n = 3 - a := make([]interface{}, n+2*len(members)) - a[0], a[1], a[2] = "zadd", key, "nx" - return c.zAdd(a, n, members...) -} - -// Redis `ZADD key XX score member [score member ...]` command. -func (c *cmdable) ZAddXX(key string, members ...Z) *IntCmd { - const n = 3 - a := make([]interface{}, n+2*len(members)) - a[0], a[1], a[2] = "zadd", key, "xx" - return c.zAdd(a, n, members...) -} - -// Redis `ZADD key CH score member [score member ...]` command. -func (c *cmdable) ZAddCh(key string, members ...Z) *IntCmd { - const n = 3 - a := make([]interface{}, n+2*len(members)) - a[0], a[1], a[2] = "zadd", key, "ch" - return c.zAdd(a, n, members...) -} - -// Redis `ZADD key NX CH score member [score member ...]` command. -func (c *cmdable) ZAddNXCh(key string, members ...Z) *IntCmd { - const n = 4 - a := make([]interface{}, n+2*len(members)) - a[0], a[1], a[2], a[3] = "zadd", key, "nx", "ch" - return c.zAdd(a, n, members...) -} - -// Redis `ZADD key XX CH score member [score member ...]` command. -func (c *cmdable) ZAddXXCh(key string, members ...Z) *IntCmd { - const n = 4 - a := make([]interface{}, n+2*len(members)) - a[0], a[1], a[2], a[3] = "zadd", key, "xx", "ch" - return c.zAdd(a, n, members...) -} - -func (c *cmdable) zIncr(a []interface{}, n int, members ...Z) *FloatCmd { - for i, m := range members { - a[n+2*i] = m.Score - a[n+2*i+1] = m.Member - } - cmd := NewFloatCmd(a...) - c.process(cmd) - return cmd -} - -// Redis `ZADD key INCR score member` command. -func (c *cmdable) ZIncr(key string, member Z) *FloatCmd { - const n = 3 - a := make([]interface{}, n+2) - a[0], a[1], a[2] = "zadd", key, "incr" - return c.zIncr(a, n, member) -} - -// Redis `ZADD key NX INCR score member` command. -func (c *cmdable) ZIncrNX(key string, member Z) *FloatCmd { - const n = 4 - a := make([]interface{}, n+2) - a[0], a[1], a[2], a[3] = "zadd", key, "incr", "nx" - return c.zIncr(a, n, member) -} - -// Redis `ZADD key XX INCR score member` command. -func (c *cmdable) ZIncrXX(key string, member Z) *FloatCmd { - const n = 4 - a := make([]interface{}, n+2) - a[0], a[1], a[2], a[3] = "zadd", key, "incr", "xx" - return c.zIncr(a, n, member) -} - -func (c *cmdable) ZCard(key string) *IntCmd { - cmd := NewIntCmd("zcard", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZCount(key, min, max string) *IntCmd { - cmd := NewIntCmd("zcount", key, min, max) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZLexCount(key, min, max string) *IntCmd { - cmd := NewIntCmd("zlexcount", key, min, max) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZIncrBy(key string, increment float64, member string) *FloatCmd { - cmd := NewFloatCmd("zincrby", key, increment, member) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZInterStore(destination string, store ZStore, keys ...string) *IntCmd { - args := make([]interface{}, 3+len(keys)) - args[0] = "zinterstore" - args[1] = destination - args[2] = len(keys) - for i, key := range keys { - args[3+i] = key - } - if len(store.Weights) > 0 { - args = append(args, "weights") - for _, weight := range store.Weights { - args = append(args, weight) - } - } - if store.Aggregate != "" { - args = append(args, "aggregate", store.Aggregate) - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZPopMax(key string, count ...int64) *ZSliceCmd { - args := []interface{}{ - "zpopmax", - key, - } - - switch len(count) { - case 0: - break - case 1: - args = append(args, count[0]) - default: - panic("too many arguments") - } - - cmd := NewZSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZPopMin(key string, count ...int64) *ZSliceCmd { - args := []interface{}{ - "zpopmin", - key, - } - - switch len(count) { - case 0: - break - case 1: - args = append(args, count[0]) - default: - panic("too many arguments") - } - - cmd := NewZSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) zRange(key string, start, stop int64, withScores bool) *StringSliceCmd { - args := []interface{}{ - "zrange", - key, - start, - stop, - } - if withScores { - args = append(args, "withscores") - } - cmd := NewStringSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRange(key string, start, stop int64) *StringSliceCmd { - return c.zRange(key, start, stop, false) -} - -func (c *cmdable) ZRangeWithScores(key string, start, stop int64) *ZSliceCmd { - cmd := NewZSliceCmd("zrange", key, start, stop, "withscores") - c.process(cmd) - return cmd -} - -type ZRangeBy struct { - Min, Max string - Offset, Count int64 -} - -func (c *cmdable) zRangeBy(zcmd, key string, opt ZRangeBy, withScores bool) *StringSliceCmd { - args := []interface{}{zcmd, key, opt.Min, opt.Max} - if withScores { - args = append(args, "withscores") - } - if opt.Offset != 0 || opt.Count != 0 { - args = append( - args, - "limit", - opt.Offset, - opt.Count, - ) - } - cmd := NewStringSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRangeByScore(key string, opt ZRangeBy) *StringSliceCmd { - return c.zRangeBy("zrangebyscore", key, opt, false) -} - -func (c *cmdable) ZRangeByLex(key string, opt ZRangeBy) *StringSliceCmd { - return c.zRangeBy("zrangebylex", key, opt, false) -} - -func (c *cmdable) ZRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd { - args := []interface{}{"zrangebyscore", key, opt.Min, opt.Max, "withscores"} - if opt.Offset != 0 || opt.Count != 0 { - args = append( - args, - "limit", - opt.Offset, - opt.Count, - ) - } - cmd := NewZSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRank(key, member string) *IntCmd { - cmd := NewIntCmd("zrank", key, member) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRem(key string, members ...interface{}) *IntCmd { - args := make([]interface{}, 2, 2+len(members)) - args[0] = "zrem" - args[1] = key - args = appendArgs(args, members) - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRemRangeByRank(key string, start, stop int64) *IntCmd { - cmd := NewIntCmd( - "zremrangebyrank", - key, - start, - stop, - ) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRemRangeByScore(key, min, max string) *IntCmd { - cmd := NewIntCmd("zremrangebyscore", key, min, max) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRemRangeByLex(key, min, max string) *IntCmd { - cmd := NewIntCmd("zremrangebylex", key, min, max) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRevRange(key string, start, stop int64) *StringSliceCmd { - cmd := NewStringSliceCmd("zrevrange", key, start, stop) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRevRangeWithScores(key string, start, stop int64) *ZSliceCmd { - cmd := NewZSliceCmd("zrevrange", key, start, stop, "withscores") - c.process(cmd) - return cmd -} - -func (c *cmdable) zRevRangeBy(zcmd, key string, opt ZRangeBy) *StringSliceCmd { - args := []interface{}{zcmd, key, opt.Max, opt.Min} - if opt.Offset != 0 || opt.Count != 0 { - args = append( - args, - "limit", - opt.Offset, - opt.Count, - ) - } - cmd := NewStringSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRevRangeByScore(key string, opt ZRangeBy) *StringSliceCmd { - return c.zRevRangeBy("zrevrangebyscore", key, opt) -} - -func (c *cmdable) ZRevRangeByLex(key string, opt ZRangeBy) *StringSliceCmd { - return c.zRevRangeBy("zrevrangebylex", key, opt) -} - -func (c *cmdable) ZRevRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd { - args := []interface{}{"zrevrangebyscore", key, opt.Max, opt.Min, "withscores"} - if opt.Offset != 0 || opt.Count != 0 { - args = append( - args, - "limit", - opt.Offset, - opt.Count, - ) - } - cmd := NewZSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRevRank(key, member string) *IntCmd { - cmd := NewIntCmd("zrevrank", key, member) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZScore(key, member string) *FloatCmd { - cmd := NewFloatCmd("zscore", key, member) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZUnionStore(dest string, store ZStore, keys ...string) *IntCmd { - args := make([]interface{}, 3+len(keys)) - args[0] = "zunionstore" - args[1] = dest - args[2] = len(keys) - for i, key := range keys { - args[3+i] = key - } - if len(store.Weights) > 0 { - args = append(args, "weights") - for _, weight := range store.Weights { - args = append(args, weight) - } - } - if store.Aggregate != "" { - args = append(args, "aggregate", store.Aggregate) - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -func (c *cmdable) PFAdd(key string, els ...interface{}) *IntCmd { - args := make([]interface{}, 2, 2+len(els)) - args[0] = "pfadd" - args[1] = key - args = appendArgs(args, els) - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) PFCount(keys ...string) *IntCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "pfcount" - for i, key := range keys { - args[1+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) PFMerge(dest string, keys ...string) *StatusCmd { - args := make([]interface{}, 2+len(keys)) - args[0] = "pfmerge" - args[1] = dest - for i, key := range keys { - args[2+i] = key - } - cmd := NewStatusCmd(args...) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -func (c *cmdable) BgRewriteAOF() *StatusCmd { - cmd := NewStatusCmd("bgrewriteaof") - c.process(cmd) - return cmd -} - -func (c *cmdable) BgSave() *StatusCmd { - cmd := NewStatusCmd("bgsave") - c.process(cmd) - return cmd -} - -func (c *cmdable) ClientKill(ipPort string) *StatusCmd { - cmd := NewStatusCmd("client", "kill", ipPort) - c.process(cmd) - return cmd -} - -// ClientKillByFilter is new style synx, while the ClientKill is old -// CLIENT KILL