diff options
author | Valery Piashchynski <[email protected]> | 2021-01-27 16:43:39 +0300 |
---|---|---|
committer | Valery Piashchynski <[email protected]> | 2021-01-27 16:43:39 +0300 |
commit | fdf9ecce67becbcd45e4813ca0253a90e771c198 (patch) | |
tree | 3300238cf9cc6710eeac5ee662d99a3873546a21 | |
parent | e9d6ed13fef70486b4edf2b59fdb592046cd25bc (diff) |
Roadrunner binary branch
256 files changed, 672 insertions, 26422 deletions
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..785f40ad --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,139 @@ +name: release + +on: + release: # Docs: <https://help.github.com/en/articles/events-that-trigger-workflows#release-event-release> + types: [published] + +jobs: + build: + name: Build for ${{ matrix.os }} (${{ matrix.arch }}, ${{ matrix.compiler }}) + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + os: [windows, freebsd, darwin] # linux, freebsd, darwin, windows + compiler: [gcc] # gcc, musl-gcc + archiver: [zip] # tar, zip + arch: [amd64] # amd64, 386 + include: + - os: linux + compiler: gcc + archiver: tar + arch: amd64 + - os: '' + compiler: musl-gcc # more info: <https://musl.libc.org/> + archiver: zip + arch: amd64 + steps: + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.15.6 + + - name: Check out code + uses: actions/checkout@v2 + + - name: Install musl + if: matrix.compiler == 'musl-gcc' + run: sudo apt-get install -y musl-tools + + - name: Download dependencies + run: go mod download # `-x` means "verbose" mode + + - name: Generate builder values + id: values + run: | + echo "::set-output name=version::`echo ${GITHUB_REF##*/} | sed -e 's/^[vV ]*//'`" + echo "::set-output name=timestamp::`date +%FT%T%z`" + echo "::set-output name=binary-name::rr`[ ${{ matrix.os }} = 'windows' ] && echo '.exe'`" + + - name: Compile binary file + env: + GOOS: ${{ matrix.os }} + GOARCH: ${{ matrix.arch }} + CC: ${{ matrix.compiler }} + CGO_ENABLED: 0 + LDFLAGS: >- + -s + -X github.com/spiral/roadrunner/cmd/cli.Version=${{ steps.values.outputs.version }} + -X github.com/spiral/roadrunner/cmd/cli.BuildTime=${{ steps.values.outputs.timestamp }} + run: | + go build -trimpath -ldflags "$LDFLAGS" -o "./${{ steps.values.outputs.binary-name }}" ./cmd/main.go + stat "./${{ steps.values.outputs.binary-name }}" + + - name: Generate distributive directory name + id: dist-dir + run: > + echo "::set-output name=name::roadrunner-${{ steps.values.outputs.version }}-$( + [ ${{ matrix.os }} != '' ] && echo '${{ matrix.os }}' || echo 'unknown' + )$( + [ ${{ matrix.compiler }} = 'musl-gcc' ] && echo '-musl' + )-${{ matrix.arch }}" + + - name: Generate distributive archive name + id: dist-arch + run: > + echo "::set-output name=name::${{ steps.dist-dir.outputs.name }}.$( + case ${{ matrix.archiver }} in + zip) echo 'zip';; + tar) echo 'tar.gz';; + *) exit 10; + esac + )" + + - name: Create distributive + run: | + mkdir ${{ steps.dist-dir.outputs.name }} + mv "./${{ steps.values.outputs.binary-name }}" ./${{ steps.dist-dir.outputs.name }}/ + cp ./README.md ./CHANGELOG.md ./LICENSE ./${{ steps.dist-dir.outputs.name }}/ + + - name: Pack distributive using tar + if: matrix.archiver == 'tar' + run: tar -zcf "${{ steps.dist-arch.outputs.name }}" "${{ steps.dist-dir.outputs.name }}" + + - name: Pack distributive using zip + if: matrix.archiver == 'zip' + run: zip -r -q "${{ steps.dist-arch.outputs.name }}" "${{ steps.dist-dir.outputs.name }}" + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: ${{ steps.dist-dir.outputs.name }} + path: ${{ steps.dist-arch.outputs.name }} + if-no-files-found: error + retention-days: 30 + + - name: Upload binaries to release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: ${{ steps.dist-arch.outputs.name }} + asset_name: ${{ steps.dist-arch.outputs.name }} + tag: ${{ github.ref }} + + docker: + name: Build docker image + runs-on: ubuntu-20.04 + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Make docker login + run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_LOGIN }}" --password-stdin + + - name: Generate builder values + id: values + run: | + echo "::set-output name=version::`echo ${GITHUB_REF##*/} | sed -e 's/^[vV ]*//'`" + echo "::set-output name=timestamp::`date +%FT%T%z`" + + - name: Build image + run: | + docker build \ + --tag "spiralscout/roadrunner:${{ steps.values.outputs.version }}" \ + --build-arg "APP_VERSION=${{ steps.values.outputs.version }}" \ + --build-arg "BUILD_TIME=${{ steps.values.outputs.timestamp }}" \ + . + + - name: Push image into registry + run: docker push "spiralscout/roadrunner:${{ steps.values.outputs.version }}" @@ -1,5 +1,5 @@ # Image page: <https://hub.docker.com/_/golang> -FROM golang:1.15.6 as builder +FROM golang:1.15.7 as builder # app version and build date must be passed during image building (version without any prefix). # e.g.: `docker build --build-arg "APP_VERSION=1.2.3" --build-arg "BUILD_TIME=$(date +%FT%T%z)" .` @@ -8,14 +8,19 @@ ARG BUILD_TIME="undefined" # arguments to pass on each go tool link invocation ENV LDFLAGS="-s \ --X github.com/spiral/roadrunner/cmd/rr/cmd.Version=$APP_VERSION \ --X github.com/spiral/roadrunner/cmd/rr/cmd.BuildTime=$BUILD_TIME" +-X github.com/spiral/roadrunner/v2/cmd/cli.Version=$APP_VERSION \ +-X github.com/spiral/roadrunner/v2/cmd/cli.BuildTime=$BUILD_TIME" RUN mkdir /src WORKDIR /src +RUN mkdir -p /src/cli -COPY ./go.mod ./go.sum ./ +COPY cmd/go.mod ./ +COPY cmd/go.sum ./ +COPY cmd/main.go ./ +COPY cmd/cli/* /src/cli/ +COPY .rr.yaml ./ # Burn modules cache RUN set -x \ @@ -23,10 +28,8 @@ RUN set -x \ && go mod download \ && go mod verify -COPY . . - # compile binary file -RUN CGO_ENABLED=0 go build -trimpath -ldflags "$LDFLAGS" -o ./rr ./cmd/main.go +RUN CGO_ENABLED=0 go build -trimpath -ldflags "$LDFLAGS" -o rr main.go # Image page: <https://hub.docker.com/_/alpine> FROM alpine:3.13 diff --git a/Makefile b/Makefile deleted file mode 100755 index fead8744..00000000 --- a/Makefile +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/make -# Makefile readme (ru): <http://linux.yaroslavl.ru/docs/prog/gnu_make_3-79_russian_manual.html> -# Makefile readme (en): <https://www.gnu.org/software/make/manual/html_node/index.html#SEC_Contents> - -SHELL = /bin/sh - -.DEFAULT_GOAL := build - -# This will output the help for each task. thanks to https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html -help: ## Show this help - @printf "\033[33m%s:\033[0m\n" 'Available commands' - @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z0-9_-]+:.*?## / {printf " \033[32m%-14s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) - -build: ## Build RR binary file for local os/arch - CGO_ENABLED=0 go build -trimpath -ldflags "-s" -o ./rr ./cmd/main.go - -clean: ## Make some clean - rm ./rr - -install: build ## Build and install RR locally - cp rr /usr/local/bin/rr - -uninstall: ## Uninstall locally installed RR - rm -f /usr/local/bin/rr - -test_coverage: - docker-compose -f tests/docker-compose.yaml up -d - rm -rf coverage - mkdir coverage - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/pipe.out -covermode=atomic ./pkg/transport/pipe - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/socket.out -covermode=atomic ./pkg/transport/socket - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/pool.out -covermode=atomic ./pkg/pool - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/worker.out -covermode=atomic ./pkg/worker - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/worker_stack.out -covermode=atomic ./pkg/worker_watcher - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/http.out -covermode=atomic ./tests/plugins/http - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/informer.out -covermode=atomic ./tests/plugins/informer - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/reload.out -covermode=atomic ./tests/plugins/reload - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/server.out -covermode=atomic ./tests/plugins/server - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/checker.out -covermode=atomic ./tests/plugins/checker - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/config.out -covermode=atomic ./tests/plugins/config - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/gzip.out -covermode=atomic ./tests/plugins/gzip - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/headers.out -covermode=atomic ./tests/plugins/headers - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/logger.out -covermode=atomic ./tests/plugins/logger - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/metrics.out -covermode=atomic ./tests/plugins/metrics - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/redis.out -covermode=atomic ./tests/plugins/redis - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/resetter.out -covermode=atomic ./tests/plugins/resetter - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/rpc.out -covermode=atomic ./tests/plugins/rpc - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/static.out -covermode=atomic ./tests/plugins/static - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/boltdb_unit.out -covermode=atomic ./plugins/kv/boltdb - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/kv_unit.out -covermode=atomic ./plugins/kv/memory - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/http_config.out -covermode=atomic ./plugins/http/config - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/memcached_unit.out -covermode=atomic ./plugins/kv/memcached - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/boltdb.out -covermode=atomic ./tests/plugins/kv/boltdb - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/memory.out -covermode=atomic ./tests/plugins/kv/memory - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage/memcached.out -covermode=atomic ./tests/plugins/kv/memcached - cat ./coverage/*.out > ./coverage/summary.out - docker-compose -f tests/docker-compose.yaml down - -test: ## Run application tests - docker-compose -f tests/docker-compose.yaml up -d - go test -v -race -tags=debug ./pkg/transport/pipe - go test -v -race -tags=debug ./pkg/transport/socket - go test -v -race -tags=debug ./pkg/pool - go test -v -race -tags=debug ./pkg/worker - go test -v -race -tags=debug ./pkg/worker_watcher - go test -v -race -tags=debug ./tests/plugins/http - go test -v -race -tags=debug ./plugins/http/config - go test -v -race -tags=debug ./tests/plugins/informer - go test -v -race -tags=debug ./tests/plugins/reload - go test -v -race -tags=debug ./tests/plugins/server - go test -v -race -tags=debug ./tests/plugins/checker - go test -v -race -tags=debug ./tests/plugins/config - go test -v -race -tags=debug ./tests/plugins/gzip - go test -v -race -tags=debug ./tests/plugins/headers - go test -v -race -tags=debug ./tests/plugins/logger - go test -v -race -tags=debug ./tests/plugins/metrics - go test -v -race -tags=debug ./tests/plugins/redis - go test -v -race -tags=debug ./tests/plugins/resetter - go test -v -race -tags=debug ./tests/plugins/rpc - go test -v -race -tags=debug ./tests/plugins/static - go test -v -race -tags=debug ./plugins/kv/boltdb - go test -v -race -tags=debug ./plugins/kv/memory - go test -v -race -tags=debug ./plugins/kv/memcached - go test -v -race -tags=debug ./tests/plugins/kv/boltdb - go test -v -race -tags=debug ./tests/plugins/kv/memory - go test -v -race -tags=debug ./tests/plugins/kv/memcached - docker-compose -f tests/docker-compose.yaml down - -test_1.14: ## Run application tests - docker-compose -f tests/docker-compose.yaml up -d - go1.14.14 test -v -race -tags=debug ./pkg/transport/pipe - go1.14.14 test -v -race -tags=debug ./pkg/transport/socket - go1.14.14 test -v -race -tags=debug ./pkg/pool - go1.14.14 test -v -race -tags=debug ./pkg/worker - go1.14.14 test -v -race -tags=debug ./pkg/worker_watcher - go1.14.14 test -v -race -tags=debug ./tests/plugins/temporal - go1.14.14 test -v -race -tags=debug ./plugins/temporal/protocol - go1.14.14 test -v -race -tags=debug ./plugins/temporal/workflow - go1.14.14 test -v -race -tags=debug ./tests/plugins/http - go1.14.14 test -v -race -tags=debug ./plugins/http/config - go1.14.14 test -v -race -tags=debug ./tests/plugins/informer - go1.14.14 test -v -race -tags=debug ./tests/plugins/reload - go1.14.14 test -v -race -tags=debug ./tests/plugins/server - go1.14.14 test -v -race -tags=debug ./tests/plugins/checker - go1.14.14 test -v -race -tags=debug ./tests/plugins/config - go1.14.14 test -v -race -tags=debug ./tests/plugins/gzip - go1.14.14 test -v -race -tags=debug ./tests/plugins/headers - go1.14.14 test -v -race -tags=debug ./tests/plugins/logger - go1.14.14 test -v -race -tags=debug ./tests/plugins/metrics - go1.14.14 test -v -race -tags=debug ./tests/plugins/redis - go1.14.14 test -v -race -tags=debug ./tests/plugins/resetter - go1.14.14 test -v -race -tags=debug ./tests/plugins/rpc - go1.14.14 test -v -race -tags=debug ./tests/plugins/static - go1.14.14 test -v -race -tags=debug ./plugins/kv/boltdb - go1.14.14 test -v -race -tags=debug ./plugins/kv/memory - go1.14.14 test -v -race -tags=debug ./plugins/kv/memcached - go1.14.14 test -v -race -tags=debug ./tests/plugins/kv/boltdb - go1.14.14 test -v -race -tags=debug ./tests/plugins/kv/memory - go1.14.14 test -v -race -tags=debug ./tests/plugins/kv/memcached - docker-compose -f tests/docker-compose.yaml down - -test_pipeline: test_1.14 test diff --git a/cmd/cli/reset.go b/cmd/cli/reset.go new file mode 100644 index 00000000..a5055a53 --- /dev/null +++ b/cmd/cli/reset.go @@ -0,0 +1,107 @@ +package cli + +import ( + "fmt" + "sync" + + "github.com/fatih/color" + "github.com/mattn/go-runewidth" + "github.com/spf13/cobra" + "github.com/spiral/errors" + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" +) + +// List is the resetter.List RPC method +const List string = "resetter.List" + +// Reset is the resetter.Reset RPC method +const Reset string = "resetter.Reset" + +func init() { + root.AddCommand(&cobra.Command{ + Use: "reset", + Short: "Reset workers of all or specific RoadRunner service", + RunE: resetHandler, + }) +} + +func resetHandler(_ *cobra.Command, args []string) error { + const op = errors.Op("reset_handler") + client, err := RPCClient() + if err != nil { + return err + } + defer func() { + _ = client.Close() + }() + + var services []string + if len(args) != 0 { + services = args + } else { + err = client.Call(List, true, &services) + if err != nil { + return errors.E(op, err) + } + } + + var wg sync.WaitGroup + pr := mpb.New(mpb.WithWaitGroup(&wg), mpb.WithWidth(6)) + wg.Add(len(services)) + + for _, service := range services { + var ( + bar *mpb.Bar + name = runewidth.FillRight(fmt.Sprintf("Resetting plugin: [%s]", color.HiYellowString(service)), 27) + result = make(chan interface{}) + ) + + bar = pr.AddSpinner( + 1, + mpb.SpinnerOnMiddle, + mpb.SpinnerStyle([]string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"}), + mpb.PrependDecorators(decor.Name(name)), + mpb.AppendDecorators(onComplete(result)), + ) + + // simulating some work + go func(service string, result chan interface{}) { + defer wg.Done() + defer bar.Increment() + + var done bool + err = client.Call(Reset, service, &done) + if err != nil { + result <- errors.E(op, err) + return + } + result <- nil + }(service, result) + } + + pr.Wait() + return nil +} + +func onComplete(result chan interface{}) decor.Decorator { + var ( + msg = "" + fn = func(s decor.Statistics) string { + select { + case r := <-result: + if err, ok := r.(error); ok { + msg = color.HiRedString(err.Error()) + return msg + } + + msg = color.HiGreenString("done") + return msg + default: + return msg + } + } + ) + + return decor.Any(fn) +} diff --git a/cmd/cli/root.go b/cmd/cli/root.go new file mode 100644 index 00000000..6f73aecf --- /dev/null +++ b/cmd/cli/root.go @@ -0,0 +1,129 @@ +package cli + +import ( + "log" + "net/http/pprof" + "net/rpc" + "os" + "path/filepath" + + "github.com/spiral/errors" + goridgeRpc "github.com/spiral/goridge/v3/pkg/rpc" + rpcPlugin "github.com/spiral/roadrunner/v2/plugins/rpc" + + "github.com/spiral/roadrunner/v2/plugins/config" + + "net/http" + + "github.com/spf13/cobra" + endure "github.com/spiral/endure/pkg/container" +) + +var ( + // WorkDir is working directory + WorkDir string + // CfgFile is path to the .rr.yaml + CfgFile string + // Debug mode + Debug bool + // Container is the pointer to the Endure container + Container *endure.Endure + cfg *config.Viper + root = &cobra.Command{ + Use: "rr", + SilenceErrors: true, + SilenceUsage: true, + Version: Version, + } +) + +func Execute() { + if err := root.Execute(); err != nil { + // exit with error, fatal invoke os.Exit(1) + log.Fatal(err) + } +} + +func init() { + root.PersistentFlags().StringVarP(&CfgFile, "config", "c", ".rr.yaml", "config file (default is .rr.yaml)") + root.PersistentFlags().StringVarP(&WorkDir, "WorkDir", "w", "", "work directory") + root.PersistentFlags().BoolVarP(&Debug, "debug", "d", false, "debug mode") + cobra.OnInitialize(func() { + if CfgFile != "" { + if absPath, err := filepath.Abs(CfgFile); err == nil { + CfgFile = absPath + + // force working absPath related to config file + if err := os.Chdir(filepath.Dir(absPath)); err != nil { + panic(err) + } + } + } + + if WorkDir != "" { + if err := os.Chdir(WorkDir); err != nil { + panic(err) + } + } + + cfg = &config.Viper{} + cfg.Path = CfgFile + cfg.Prefix = "rr" + + // register config + err := Container.Register(cfg) + if err != nil { + panic(err) + } + + // if debug mode is on - run debug server + if Debug { + runDebugServer() + } + }) +} + +// RPCClient is using to make a requests to the ./rr reset, ./rr workers +func RPCClient() (*rpc.Client, error) { + rpcConfig := &rpcPlugin.Config{} + + err := cfg.Init() + if err != nil { + return nil, err + } + + if !cfg.Has(rpcPlugin.PluginName) { + return nil, errors.E("rpc service disabled") + } + + err = cfg.UnmarshalKey(rpcPlugin.PluginName, rpcConfig) + if err != nil { + return nil, err + } + rpcConfig.InitDefaults() + + conn, err := rpcConfig.Dialer() + if err != nil { + return nil, err + } + + return rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)), nil +} + +// debug server +func runDebugServer() { + mux := http.NewServeMux() + mux.HandleFunc("/debug/pprof/", pprof.Index) + mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + mux.HandleFunc("/debug/pprof/profile", pprof.Profile) + mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + mux.HandleFunc("/debug/pprof/trace", pprof.Trace) + srv := http.Server{ + Addr: ":6061", + Handler: mux, + } + + if err := srv.ListenAndServe(); err != nil { + log.Fatal(err) + } +} diff --git a/cmd/cli/serve.go b/cmd/cli/serve.go new file mode 100644 index 00000000..993ec477 --- /dev/null +++ b/cmd/cli/serve.go @@ -0,0 +1,63 @@ +package cli + +import ( + "log" + "os" + "os/signal" + "syscall" + + "github.com/spf13/cobra" + "github.com/spiral/errors" + "go.uber.org/multierr" +) + +func init() { + root.AddCommand(&cobra.Command{ + Use: "serve", + Short: "Start RoadRunner server", + RunE: handler, + }) +} + +func handler(_ *cobra.Command, _ []string) error { + const op = errors.Op("handle_serve_command") + /* + We need to have path to the config at the RegisterTarget stage + But after cobra.Execute, because cobra fills up cli variables on this stage + */ + + err := Container.Init() + if err != nil { + return errors.E(op, err) + } + + errCh, err := Container.Serve() + if err != nil { + return errors.E(op, err) + } + + // https://golang.org/pkg/os/signal/#Notify + // should be of buffer size at least 1 + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) + + for { + select { + case e := <-errCh: + err = multierr.Append(err, e.Error) + log.Printf("error occurred: %v, service: %s", e.Error.Error(), e.VertexID) + er := Container.Stop() + if er != nil { + err = multierr.Append(err, er) + return errors.E(op, err) + } + return errors.E(op, err) + case <-c: + err = Container.Stop() + if err != nil { + return errors.E(op, err) + } + return nil + } + } +} diff --git a/cmd/cli/version.go b/cmd/cli/version.go new file mode 100644 index 00000000..89728bd2 --- /dev/null +++ b/cmd/cli/version.go @@ -0,0 +1,9 @@ +package cli + +var ( + // Version - defines build version. + Version string = "local" + + // BuildTime - defined build time. + BuildTime string = "development" +) diff --git a/cmd/cli/workers.go b/cmd/cli/workers.go new file mode 100644 index 00000000..09642a58 --- /dev/null +++ b/cmd/cli/workers.go @@ -0,0 +1,109 @@ +package cli + +import ( + "fmt" + "log" + "net/rpc" + "os" + "os/signal" + "syscall" + "time" + + tm "github.com/buger/goterm" + "github.com/fatih/color" + "github.com/spf13/cobra" + "github.com/spiral/errors" + "github.com/spiral/roadrunner/v2/plugins/informer" + "github.com/spiral/roadrunner/v2/tools" +) + +// use interactive mode +var interactive bool + +const InformerList string = "informer.List" +const InformerWorkers string = "informer.Workers" + +func init() { + workersCommand := &cobra.Command{ + Use: "workers", + Short: "Show information about active roadrunner workers", + RunE: workersHandler, + } + + workersCommand.Flags().BoolVarP( + &interactive, + "interactive", + "i", + false, + "render interactive workers table", + ) + + root.AddCommand(workersCommand) +} + +func workersHandler(_ *cobra.Command, args []string) error { + const op = errors.Op("handle_workers_command") + // get RPC client + client, err := RPCClient() + if err != nil { + return err + } + defer func() { + err := client.Close() + if err != nil { + log.Printf("error when closing RPCClient: error %v", err) + } + }() + + var plugins []string + // assume user wants to show workers from particular plugin + if len(args) != 0 { + plugins = args + } else { + err = client.Call(InformerList, true, &plugins) + if err != nil { + return errors.E(op, err) + } + } + + if !interactive { + return showWorkers(plugins, client) + } + + // https://golang.org/pkg/os/signal/#Notify + // should be of buffer size at least 1 + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) + + tm.Clear() + tt := time.NewTicker(time.Second) + defer tt.Stop() + for { + select { + case <-c: + return nil + case <-tt.C: + tm.MoveCursor(1, 1) + err := showWorkers(plugins, client) + if err != nil { + return errors.E(op, err) + } + tm.Flush() + } + } +} + +func showWorkers(plugins []string, client *rpc.Client) error { + const op = errors.Op("show_workers") + for _, plugin := range plugins { + list := &informer.WorkerList{} + err := client.Call(InformerWorkers, plugin, &list) + if err != nil { + return errors.E(op, err) + } + + fmt.Printf("Workers of [%s]:\n", color.HiYellowString(plugin)) + tools.WorkerTable(os.Stdout, list.Workers).Render() + } + return nil +} diff --git a/cmd/go.mod b/cmd/go.mod new file mode 100644 index 00000000..4c02ac57 --- /dev/null +++ b/cmd/go.mod @@ -0,0 +1,17 @@ +module github.com/spiral/roadrunner/v2/cmd + +go 1.15 + +require ( + github.com/buger/goterm v0.0.0-20200322175922-2f3e71b85129 + github.com/fatih/color v1.10.0 + github.com/mattn/go-runewidth v0.0.10 + github.com/spf13/cobra v1.1.1 + github.com/spiral/endure v1.0.0-beta21 + github.com/spiral/errors v1.0.9 + github.com/spiral/goridge/v3 v3.0.0 + github.com/spiral/roadrunner/v2 v2.0.0-beta13 + github.com/temporalio/roadrunner-temporal v1.0.0-beta2 + github.com/vbauerster/mpb/v5 v5.4.0 + go.uber.org/multierr v1.6.0 +) @@ -15,7 +15,6 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= @@ -33,9 +32,7 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= -github.com/alicebob/miniredis/v2 v2.14.1 h1:GjlbSeoJ24bzdLRs13HoMEeaRZx9kg5nHoRW7QV/nCs= github.com/alicebob/miniredis/v2 v2.14.1/go.mod h1:uS970Sw5Gs9/iK3yBg0l9Uj9s25wXxSpQUE9EaJ/Blg= github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4= github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= @@ -49,7 +46,6 @@ github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQ github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -111,13 +107,11 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -132,6 +126,7 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-redis/redis/v8 v8.4.10/go.mod h1:d5yY/TlkQyYBSBHnXUmnf1OrHbyQere5JV4dLKwvXmo= github.com/go-redis/redis/v8 v8.4.11 h1:t2lToev01VTrqYQcv+QFbxtGgcf64K+VUMgf9Ap6A/E= github.com/go-redis/redis/v8 v8.4.11/go.mod h1:d5yY/TlkQyYBSBHnXUmnf1OrHbyQere5JV4dLKwvXmo= github.com/go-restit/lzjson v0.0.0-20161206095556-efe3c53acc68/go.mod h1:7vXSKQt83WmbPeyVjCfNT9YDJ5BUFmcwFsEjI9SCvYM= @@ -145,7 +140,6 @@ github.com/gogo/googleapis v1.4.0 h1:zgVt4UpGxcqVOw97aRGxT4svlcmdK35fynLNctY32zI github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -157,13 +151,11 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -171,7 +163,6 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= @@ -218,7 +209,6 @@ github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= @@ -261,7 +251,6 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.10.7 h1:7rix8v8GpI3ZBb0nSozFRgbtXKv+hOe+qfEpZqybrAg= github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -277,19 +266,15 @@ github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0U github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= @@ -362,7 +347,6 @@ github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -372,17 +356,14 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= -github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= -github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.9.0 h1:Rrch9mh17XcxvEu9D9DEpb4isxjGBtcevQjKvxPRQIU= github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -390,21 +371,17 @@ github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= -github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tyspaKM= github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= @@ -423,9 +400,7 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/shirou/gopsutil v3.20.12+incompatible h1:6VEGkOXP/eP4o2Ilk8cSsX0PhOEfX6leqAnD+urrp9M= github.com/shirou/gopsutil v3.20.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= @@ -441,6 +416,7 @@ github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= @@ -451,19 +427,20 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spiral/endure v1.0.0-beta21 h1:YW3gD6iNhRByG/yFkm/Ko+nj+oTBsjBtPVHFA2nt67k= github.com/spiral/endure v1.0.0-beta21/go.mod h1:GsItn+dYSO4O5uwvfki23xyxRnmBhxEyL6jBeJQoFPw= -github.com/spiral/errors v1.0.5 h1:TwlR9cZtTgnZrSngcEUpyiMO9yJ45gdQ+XcrCRoCCAM= github.com/spiral/errors v1.0.5/go.mod h1:SwMSZVdZkkJVgXNNafccqOaxWg0XPzVU/dEdUEInE0o= github.com/spiral/errors v1.0.7/go.mod h1:SwMSZVdZkkJVgXNNafccqOaxWg0XPzVU/dEdUEInE0o= github.com/spiral/errors v1.0.9 h1:RcVZ7a1RYkaT3HWFGDuQiDB02pG6yqh7715Uwd7urwM= github.com/spiral/errors v1.0.9/go.mod h1:SwMSZVdZkkJVgXNNafccqOaxWg0XPzVU/dEdUEInE0o= github.com/spiral/goridge/v3 v3.0.0 h1:FIz6wHaob5KynpOfzVpzj4bmqbEelGPFyuEf4i2+CG8= github.com/spiral/goridge/v3 v3.0.0/go.mod h1:XFQGc42KNzo/hPIXPki7mEkFTf9v/T7qFk/TYJjMtzE= +github.com/spiral/roadrunner/v2 v2.0.0-beta12/go.mod h1:mYU5iBLK9M8dNFWO6fnBu52CVbkCVnYPqrwpL+mT7Wg= +github.com/spiral/roadrunner/v2 v2.0.0-beta13 h1:+GhMRg9huYQv8MPzWs015woAJpRla0lTvliFzx6TI5s= +github.com/spiral/roadrunner/v2 v2.0.0-beta13/go.mod h1:fNvBA+44gkdmf1CSpfl5j9fIBonlchWeS07zuIgT6xw= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= @@ -475,12 +452,13 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/temporalio/roadrunner-temporal v1.0.0-beta2 h1:1npTp/Je1yybBqcwcknpqFgpyBYWwZyPpH8YjTbOA3U= +github.com/temporalio/roadrunner-temporal v1.0.0-beta2/go.mod h1:/YtEJ4enOQA2zHDzMeOa6EzLxGFEnNSvpIqTjxmvpWM= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/uber-go/tally v3.3.17+incompatible h1:nFHIuW3VQ22wItiE9kPXic8dEgExWOsVOHwpmoIvsMw= @@ -502,12 +480,10 @@ github.com/vbauerster/mpb/v5 v5.4.0/go.mod h1:fi4wVo7BVQ22QcvFObm+VwliQXlV1eBT8J github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/yookoala/gofast v0.4.0 h1:dLBjghcsbbZNOEHN8N1X/gh9S6srmJed4WQfG7DlKwo= github.com/yookoala/gofast v0.4.0/go.mod h1:rfbkoKaQG1bnuTUZcmV3vAlnfpF4FTq8WbQJf2vcpg8= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= -go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= @@ -518,14 +494,13 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/otel v0.16.0 h1:uIWEbdeb4vpKPGITLsRVUS44L5oDbDUCZxn8lkxhmgw= go.opentelemetry.io/otel v0.16.0/go.mod h1:e4GKElweB8W2gWUqbghw0B8t5MCTccc9212eNHnOHwA= go.temporal.io/api v1.4.0 h1:Ga1Ih8YE5ULs+UGt7u6Ppcf5SUMLyh4BATAs6SyPO0w= go.temporal.io/api v1.4.0/go.mod h1:H0yXehwGE9Sn9zVruyy9aumq17SMsK1WmIy4GX3MIKw= -go.temporal.io/sdk v1.4.0 h1:IDIgfhakfgMv+zOMCQkXyqR7zHH+T4Nt2VAH5qW4w3w= go.temporal.io/sdk v1.4.0/go.mod h1:hZ3Jd/Aoom1ao+fRyFf6y3MfHwXdPlhJiykX/gGmBeA= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.temporal.io/sdk v1.4.1 h1:1zRRMAJNxy5lh1+QnDzuLXXbj0jJKagvyVzA2bkkqD4= +go.temporal.io/sdk v1.4.1/go.mod h1:hZ3Jd/Aoom1ao+fRyFf6y3MfHwXdPlhJiykX/gGmBeA= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -547,11 +522,9 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -568,15 +541,17 @@ golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -593,15 +568,14 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201216054612-986b41b23924 h1:QsnDpLLOKwHBBDa8nDws4DYNc/ryVW2vCpxCs09d4PY= golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -612,9 +586,9 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -633,7 +607,6 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -645,7 +618,6 @@ golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -655,16 +627,13 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201210223839-7e3030f88018/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201218084310-7d0127a74742 h1:+CBz4km/0KPU3RGTwARGh/noP3bEwtHcq+0YcBQM2JQ= golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201221093633-bc327ba9c2f0 h1:n+DPcgTwkgWzIFpLmoimYR2K2b0Ga5+Os4kayIN0vGo= golang.org/x/sys v0.0.0-20201221093633-bc327ba9c2f0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 h1:/dSxr6gT0FNI1MO5WLJo8mTmItROeOKTkDn+7OwWBos= golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -699,18 +668,20 @@ golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114 h1:DnSr2mCsxyCE6ZgIkmcWUQY2R5cH/6wL7eIxEmQOMSE= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200605181038-cef9fc3bc8f0 h1:gxU2P+MOOGAWge5BKP+BzqSeegxvDBRib5rk3yZDDuI= golang.org/x/tools v0.0.0-20200605181038-cef9fc3bc8f0/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210115202250-e0d201561e39 h1:BTs2GMGSMWpgtCpv1CE7vkJTv7XcHdcLLnAMu7UbgTY= +golang.org/x/tools v0.0.0-20210115202250-e0d201561e39/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -733,7 +704,6 @@ google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dT google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -745,7 +715,6 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= @@ -769,7 +738,6 @@ google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -789,10 +757,8 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 00000000..5f8ae611 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,69 @@ +package main + +import ( + "log" + + endure "github.com/spiral/endure/pkg/container" + // plugins + "github.com/spiral/roadrunner/v2/cmd/cli" + httpPlugin "github.com/spiral/roadrunner/v2/plugins/http" + "github.com/spiral/roadrunner/v2/plugins/informer" + "github.com/spiral/roadrunner/v2/plugins/kv/boltdb" + "github.com/spiral/roadrunner/v2/plugins/kv/memcached" + "github.com/spiral/roadrunner/v2/plugins/kv/memory" + "github.com/spiral/roadrunner/v2/plugins/logger" + "github.com/spiral/roadrunner/v2/plugins/metrics" + "github.com/spiral/roadrunner/v2/plugins/redis" + "github.com/spiral/roadrunner/v2/plugins/reload" + "github.com/spiral/roadrunner/v2/plugins/resetter" + "github.com/spiral/roadrunner/v2/plugins/rpc" + "github.com/spiral/roadrunner/v2/plugins/server" + "github.com/temporalio/roadrunner-temporal/activity" + temporalClient "github.com/temporalio/roadrunner-temporal/client" + "github.com/temporalio/roadrunner-temporal/workflow" +) + +func main() { + var err error + cli.Container, err = endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel), endure.RetryOnFail(false)) + if err != nil { + log.Fatal(err) + } + + err = cli.Container.RegisterAll( + // logger plugin + &logger.ZapLogger{}, + // metrics plugin + &metrics.Plugin{}, + // redis plugin (internal) + &redis.Plugin{}, + // http server plugin + &httpPlugin.Plugin{}, + // reload plugin + &reload.Plugin{}, + // informer plugin (./rr workers, ./rr workers -i) + &informer.Plugin{}, + // resetter plugin (./rr reset) + &resetter.Plugin{}, + // rpc plugin (workers, reset) + &rpc.Plugin{}, + // server plugin (NewWorker, NewWorkerPool) + &server.Plugin{}, + // memcached kv plugin + &memcached.Plugin{}, + // in-memory kv plugin + &memory.Plugin{}, + // boltdb driver + &boltdb.Plugin{}, + + // temporal plugins + &temporalClient.Plugin{}, + &activity.Plugin{}, + &workflow.Plugin{}, + ) + if err != nil { + log.Fatal(err) + } + + cli.Execute() +} diff --git a/composer.json b/composer.json deleted file mode 100644 index 6216b686..00000000 --- a/composer.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "spiral/roadrunner", - "type": "server", - "description": "RoadRunner: High-performance PHP application server, load-balancer and process manager written in Golang", - "license": "MIT", - "authors": [ - { - "name": "Anton Titov / Wolfy-J", - "email": "[email protected]" - }, - { - "name": "RoadRunner Community", - "homepage": "https://github.com/spiral/roadrunner/graphs/contributors" - } - ], - "require": { - "spiral/roadrunner-worker": ">=2.0", - "symfony/console": "^2.5.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", - "composer/package-versions-deprecated": "^1.8" - }, - "bin": [ - "bin/rr" - ] -} diff --git a/dput.cf b/dput.cf deleted file mode 100644 index d784a825..00000000 --- a/dput.cf +++ /dev/null @@ -1,5 +0,0 @@ -[roadrunner] -fqdn = ppa.launchpad.net -method = ftp -incoming = 48d90782/ubuntu/roadrunner -login = anonymous diff --git a/go.mod b/go.mod deleted file mode 100644 index a9835b1b..00000000 --- a/go.mod +++ /dev/null @@ -1,34 +0,0 @@ -module github.com/spiral/roadrunner/v2 - -go 1.15 - -require ( - github.com/NYTimes/gziphandler v1.1.1 - github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect - github.com/alicebob/miniredis/v2 v2.14.1 - github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b - github.com/dustin/go-humanize v1.0.0 - github.com/fatih/color v1.10.0 - github.com/go-ole/go-ole v1.2.5 // indirect - github.com/go-redis/redis/v8 v8.4.11 - github.com/gofiber/fiber/v2 v2.3.3 - github.com/golang/mock v1.4.4 - github.com/hashicorp/go-multierror v1.1.0 - github.com/json-iterator/go v1.1.10 - github.com/olekukonko/tablewriter v0.0.4 - github.com/prometheus/client_golang v1.9.0 - github.com/shirou/gopsutil v3.20.12+incompatible - github.com/spf13/viper v1.7.1 - github.com/spiral/endure v1.0.0-beta21 - github.com/spiral/errors v1.0.9 - github.com/spiral/goridge/v3 v3.0.0 - github.com/stretchr/testify v1.7.0 - github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a - github.com/yookoala/gofast v0.4.0 - go.etcd.io/bbolt v1.3.5 - go.uber.org/multierr v1.6.0 - go.uber.org/zap v1.16.0 - golang.org/x/net v0.0.0-20201224014010-6772e930b67b - golang.org/x/sync v0.0.0-20201207232520-09787c993a3a - golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 -) diff --git a/internal/protocol.go b/internal/protocol.go deleted file mode 100755 index 19678067..00000000 --- a/internal/protocol.go +++ /dev/null @@ -1,94 +0,0 @@ -package internal - -import ( - "os" - - j "github.com/json-iterator/go" - "github.com/spiral/errors" - "github.com/spiral/goridge/v3/interfaces/relay" - "github.com/spiral/goridge/v3/pkg/frame" -) - -var json = j.ConfigCompatibleWithStandardLibrary - -type StopCommand struct { - Stop bool `json:"stop"` -} - -type pidCommand struct { - Pid int `json:"pid"` -} - -func SendControl(rl relay.Relay, payload interface{}) error { - const op = errors.Op("send_control") - fr := frame.NewFrame() - fr.WriteVersion(frame.VERSION_1) - fr.WriteFlags(frame.CONTROL) - - if data, ok := payload.([]byte); ok { - // check if payload no more that 4Gb - if uint32(len(data)) > ^uint32(0) { - return errors.E(op, errors.Str("payload is more that 4gb")) - } - - fr.WritePayloadLen(uint32(len(data))) - fr.WritePayload(data) - fr.WriteCRC() - - err := rl.Send(fr) - if err != nil { - return errors.E(op, err) - } - return nil - } - - data, err := json.Marshal(payload) - if err != nil { - return errors.E(op, errors.Errorf("invalid payload: %s", err)) - } - - fr.WritePayloadLen(uint32(len(data))) - fr.WritePayload(data) - fr.WriteCRC() - - err = rl.Send(fr) - if err != nil { - return errors.E(op, err) - } - - return nil -} - -func FetchPID(rl relay.Relay) (int64, error) { - const op = errors.Op("fetch_pid") - err := SendControl(rl, pidCommand{Pid: os.Getpid()}) - if err != nil { - return 0, errors.E(op, err) - } - - frameR := frame.NewFrame() - err = rl.Receive(frameR) - if !frameR.VerifyCRC() { - return 0, errors.E(op, errors.Str("CRC mismatch")) - } - if err != nil { - return 0, errors.E(op, err) - } - if frameR == nil { - return 0, errors.E(op, errors.Str("nil frame received")) - } - - flags := frameR.ReadFlags() - - if flags&(byte(frame.CONTROL)) == 0 { - return 0, errors.E(op, errors.Str("unexpected response, header is missing, no CONTROL flag")) - } - - link := &pidCommand{} - err = json.Unmarshal(frameR.Payload(), link) - if err != nil { - return 0, errors.E(op, err) - } - - return int64(link.Pid), nil -} diff --git a/internal/state.go b/internal/state.go deleted file mode 100755 index a14a6937..00000000 --- a/internal/state.go +++ /dev/null @@ -1,122 +0,0 @@ -package internal - -import ( - "fmt" - "sync/atomic" -) - -// State represents WorkerProcess status and updated time. -type State interface { - fmt.Stringer - // Value returns WorkerState value - Value() int64 - // Set sets the WorkerState - Set(value int64) - // NumJobs shows how many times WorkerProcess was invoked - NumExecs() uint64 - // IsActive returns true if WorkerProcess not Inactive or Stopped - IsActive() bool - // RegisterExec using to registering php executions - RegisterExec() - // SetLastUsed sets worker last used time - SetLastUsed(lu uint64) - // LastUsed return worker last used time - LastUsed() uint64 -} - -const ( - // StateInactive - no associated process - StateInactive int64 = iota - - // StateReady - ready for job. - StateReady - - // StateWorking - working on given payload. - StateWorking - - // StateInvalid - indicates that WorkerProcess is being disabled and will be removed. - StateInvalid - - // StateStopping - process is being softly stopped. - StateStopping - - StateKilling - - // State of worker, when no need to allocate new one - StateDestroyed - - // StateStopped - process has been terminated. - StateStopped - - // StateErrored - error WorkerState (can't be used). - StateErrored - - StateRemove -) - -type WorkerState struct { - value int64 - numExecs uint64 - // to be lightweight, use UnixNano - lastUsed uint64 -} - -// Thread safe -func NewWorkerState(value int64) *WorkerState { - return &WorkerState{value: value} -} - -// String returns current WorkerState as string. -func (s *WorkerState) String() string { - switch s.Value() { - case StateInactive: - return "inactive" - case StateReady: - return "ready" - case StateWorking: - return "working" - case StateInvalid: - return "invalid" - case StateStopped: - return "stopped" - case StateErrored: - return "errored" - } - - return "undefined" -} - -// NumExecs returns number of registered WorkerProcess execs. -func (s *WorkerState) NumExecs() uint64 { - return atomic.LoadUint64(&s.numExecs) -} - -// Value WorkerState returns WorkerState value -func (s *WorkerState) Value() int64 { - return atomic.LoadInt64(&s.value) -} - -// IsActive returns true if WorkerProcess not Inactive or Stopped -func (s *WorkerState) IsActive() bool { - val := s.Value() - return val == StateWorking || val == StateReady -} - -// change WorkerState value (status) -func (s *WorkerState) Set(value int64) { - atomic.StoreInt64(&s.value, value) -} - -// register new execution atomically -func (s *WorkerState) RegisterExec() { - atomic.AddUint64(&s.numExecs, 1) -} - -// Update last used time -func (s *WorkerState) SetLastUsed(lu uint64) { - atomic.StoreUint64(&s.lastUsed, lu) -} - -func (s *WorkerState) LastUsed() uint64 { - return atomic.LoadUint64(&s.lastUsed) -} diff --git a/internal/state_test.go b/internal/state_test.go deleted file mode 100755 index bdb05825..00000000 --- a/internal/state_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package internal - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_NewState(t *testing.T) { - st := NewWorkerState(StateErrored) - - assert.Equal(t, "errored", st.String()) - - assert.Equal(t, "inactive", NewWorkerState(StateInactive).String()) - assert.Equal(t, "ready", NewWorkerState(StateReady).String()) - assert.Equal(t, "working", NewWorkerState(StateWorking).String()) - assert.Equal(t, "stopped", NewWorkerState(StateStopped).String()) - assert.Equal(t, "undefined", NewWorkerState(1000).String()) -} - -func Test_IsActive(t *testing.T) { - assert.False(t, NewWorkerState(StateInactive).IsActive()) - assert.True(t, NewWorkerState(StateReady).IsActive()) - assert.True(t, NewWorkerState(StateWorking).IsActive()) - assert.False(t, NewWorkerState(StateStopped).IsActive()) - assert.False(t, NewWorkerState(StateErrored).IsActive()) -} diff --git a/pkg/doc/README.md b/pkg/doc/README.md deleted file mode 100644 index 4f726f4a..00000000 --- a/pkg/doc/README.md +++ /dev/null @@ -1,21 +0,0 @@ -This is the drawio diagrams showing basic workflows inside RoadRunner 2.0 - -Simple HTTP workflow description: -![alt text](pool_workflow.svg) - -1. Allocate sync workers. When plugin starts (which use workers pool), then it allocates required number of processes - via `cmd.exec` command. - -2. When user send HTTP request to the RR2, HTTP plugin receive it and transfer to the workers pool `Exec/ExecWithContex` -method. And workers pool ask Worker watcher to get free worker. - -3. Workers watcher uses stack data structure under the hood and making POP operation to get first free worker. If there are -no workers in the `stack`, watcher waits for the specified via config (`allocate_timeout`) time. - -4. Stack returns free worker to the watcher. -5. Watcher returns that worker to the `pool`. -6. Pool invoke `Exec/ExecWithTimeout` method on the golang worker with provided request payload. -7. Golang worker send that request to the PHP worker via various set of transports (`pkg/transport` package). -8. PHP worker send back response to the golang worker (or error via stderr). -9. Golang worker return response payload to the pool. -10. Pool process this response and return answer to the user.
\ No newline at end of file diff --git a/pkg/doc/pool_workflow.drawio b/pkg/doc/pool_workflow.drawio deleted file mode 100644 index 3f74d0fc..00000000 --- a/pkg/doc/pool_workflow.drawio +++ /dev/null @@ -1 +0,0 @@ -<mxfile host="Electron" modified="2021-01-24T16:29:37.978Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.1.8 Chrome/87.0.4280.141 Electron/11.2.1 Safari/537.36" etag="8v25I0qLU_vMqqVrx7cN" version="14.1.8" type="device"><diagram id="8w40hpb1-UDYxj1ewOsN" name="Page-1">7Vxbd6M4Ev41OWfnoX0Qkrg8Jul09+zOTGc6vSfTjwRkm22MvCB37Pn1IwGydcExsTGO03HOiUEgLqqqr6o+lXwBr2fLj0U0n/5OE5JduE6yvIDvL1wXAMflX6Jl1bQ4jlO3TIo0qduUhrv0byJPbFoXaULKpq1uYpRmLJ3rjTHNcxIzrS0qCvqonzamWaI1zKMJsRru4iizW+/ThE3li3nh5sAnkk6m8tYeRvWRWSTPbl6lnEYJfVSa4M0FvC4oZfXWbHlNMjF8+sB82HJ0/WQFyVmXDn/hCPvfPk++/Ocm+HyPr5Z/TsfvvOYyP6Js0bzyhetl/IJXU37Im4itu3Q25wPiOv8tScG/CvL/BSmZPJHfcnNu865sJUewoIs8IeIZHH74cZoycjePYnH0kSuNuBGbZXwP8M0xzdmHaJZmQl+u6aJIqxv+Qfi4XUVZOsl5e0bG4k4/SMFSLqnLppnR+fr+6qjIN+Snk6XS1IzSR0JnhBUrfkpzFGNcd2mUFrmNFj+qGhA2GjBVpB9ItY0atZusL74RDN9oZPMMOYHQGliScE1tdmnBpnRC8yi72bRe6UO/Oec3KoaqGvD/EcZWjdlFC0Z1cZBlyv4S3Ucubna/KYfeL5tLVzsruZPzF1Z7if1v6sFNv2pPdtSE/2/CHooozUv+0r/TnD4l2pIrSkyeGr0GOaJiQtgT50lTFUP7pKYUJItY+kMHiTahN11vacqfea1hoedpGgYc2SCvUT9q083QnfVz7K9Orm32H8XrcNgS/8pVHgt9p8X3yv7+dfv5ln/ROSn4a9P8l1Zl/C164PCvKZA02ZiLjF/JNtpZmiS1rpIy/Tt6qK4nlGEuXr0aDHx1gd/vpR5PWpKFB2sn0jyFBtNtOPHOGTke0iUZ1HsH6sc7AJ2RDkL8ZsB1lA80VYaOxyU5jrZgS1vgmwbUQnE939e9RT8agAz5hyMHDSZv/7TOxledjdPV2fiaswEv3tnAvn3NQQGGRC7FxvEZ27hU4cNtnJs4QlDHYtiPjUPDxvEogEPZOHTsCEDcae3030RfwTvwdNm7vYgemaJ/B9DIw0MJ347+7iupi8G+j1g8bVGA5+VxaZZd04wWVV84HhMvjnl7yQr6nShHEj98cA7C4+55XQgNlwqbCErN63BjF2pa5x0tqwPWMA/qaPdJ6rSMbrT2uYO7WbejmwV9u9mq62VRRCvlhAbNthp8YOpeE71vtKe+Yr9m7lp2fpllNI6YleSVZwz4YBsS7AP4GHlHiecD3Y0gA1KOGMxDSwnaUecnFDaAGGhi6SmwQ7pvxyPgDyZuS9q/5ilLuSS5N+jTpycRCcatPt2LA/IwHsan+4GOq4HT4tOdFp9uGmB/Pr09bXpjajt5dXgqr36YzD3L6lQ69Y572vtzT6ukXvfEmjm6P/T7AV7fzKu8keerrKk7GA4jSyXcN+nX0oc8/NWl1I/0PYsz9Qfzu/CksA8UzFf40+flcv7pkrmusA/dF4X70MZ974yNHPZm5Dy0xrgnqwZoBAejRVE7SJ/V1Acf/B2GXO3dkiLlwyV076VYN+rbuvfiaoADA82PyKTiQ9cOPtrVwceHdcAONtT/CPwRaqEOZDHTOeKbNO4+8M1xJROxL74NAGZ2DHpH8qR65nJO81IkJxyL+L+p2FyUZ52h9CfeutjCmKHphwj0Xf2qbjAabNrHNuebJYn5pcTXfcqm11wQZMksHTh/nsic+2nlidrmfo7GE21JB88q0jhdkYWs1DuT6R/gID1C4A27PP6OHsdx+a6d0aAzdgnSyPrw+BAiA7t7mi1wDdoCgAHrQNzQEvivAgKjir4cF0R8recIryt0LETbii7EAxb2rMJPqh6uo08cyqmYA9UDADxCoakgCKmsJhhMW+yqoTsWxd8rR8r96Pbp5INCCEyCBLWFEIH7IOpgTxJCACc4df0ItI33tOsENBrS7UhDrqMItdtpaEi3Y1TRtXZzky46h9JhA6QHrqVNRn7wNZ0RuniN+QFwPd25tyYIshZwkAQB2umaNfDlNJqLzXFGlpdiuVtlLEmz+T7OorJMY8NYN9F8r5O++5SV7W+qOOzZVBU5t2G4bDvUmzuSQZPMHzCWcdRvbq38aSMdDQ7RGXYNEbTZJXN94BZtXcyyy5hRNQas4sVbWqZieRE/5YEyRmctQWK9ym/bksEjef7QWBAo1/4pOgMH9ft2sf6a2GuGXuX1tpR0V1TP0UJ0ESWUdcAwkJgAMDFcptAqhoeBLSizPKs3QSG70voLiUldG7IhYV+9YHyj2iN0bMHI9SbDCMZe0maJgV8mnZdEl4TEsJKjK+O5DyN2SFMnZW0hzXjcHBlg1D3PoDzlhMUO1ILHQi1kc0v2mO9MVhRZmEawdhpKCETnJK9bmuwleFIuL6/OXWrq7sXLHQOdQycwjNXxARo27EC+pUW32WKS5rxtXeparVJ+9bjqOUYsGUIbVuUvPAwCq7Kc+q0K6qj0g+QDX0gZFLYz1fYVxOfBCuNthrhXpSuSBnho5ojaS1kNtvn4NBE+7erwM6cYUWfeond/fpiN2ylncM423uP6cBA6ejlzP/OCAIhCaeWjzwfLepQByojsWYYtqxkOWiOMxV8rD1x9OsheHFf61Z+eUliMXtpCI2zzbzVdz990ldEoefXxL0BGjUQb4SOXGgwT/9q0whfy46fje5Br1MMGLUTcoHwPtjHs09ev4ket5k3yeBCU7QImA+qCmLT/HMJDwHHmyYmRZ7DW2FhkIxfDaIjVwoZCdCQheHb1gDXuL2lGa6sQdgZvuGuCdqJZJ+yH+jW6zzo54QgHys+Q6TpmTnkcmQyShL9KBlGadUBY1SRzmpNngW4bHOiAsUu1LMtfc8Z94K9jBCt+S6UKalE391iximczBB9pFuUT3rYllvzJRAbd3SJrjS+PJzM7lrn9JDxm5+nEVy0wLHmdJwQWoF7kxXc3PxNcQ+fm55bhzT8=</diagram></mxfile>
\ No newline at end of file diff --git a/pkg/doc/pool_workflow.svg b/pkg/doc/pool_workflow.svg deleted file mode 100644 index 1e043eaa..00000000 --- a/pkg/doc/pool_workflow.svg +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1200px" height="811px" viewBox="-0.5 -0.5 1200 811" content="<mxfile host="Electron" modified="2021-01-24T14:08:35.229Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.1.8 Chrome/87.0.4280.141 Electron/11.2.1 Safari/537.36" etag="XUF1gTxKr8mJfXGLcVd8" version="14.1.8" type="device"><diagram id="8w40hpb1-UDYxj1ewOsN" name="Page-1">7Vxbd6M4Ev41OWfnoX0Qkrg8Jul09+zOTGc6vSfTjwRkm22MvCB37Pn1IwGydcExsTGO03HOiUEgLqqqr6o+lXwBr2fLj0U0n/5OE5JduE6yvIDvL1wXAMflX6Jl1bQ4jlO3TIo0qduUhrv0byJPbFoXaULKpq1uYpRmLJ3rjTHNcxIzrS0qCvqonzamWaI1zKMJsRru4iizW+/ThE3li3nh5sAnkk6m8tYeRvWRWSTPbl6lnEYJfVSa4M0FvC4oZfXWbHlNMjF8+sB82HJ0/WQFyVmXDn/hCPvfPk++/Ocm+HyPr5Z/TsfvvOYyP6Js0bzyhetl/IJXU37Im4itu3Q25wPiOv8tScG/CvL/BSmZPJHfcnNu865sJUewoIs8IeIZHH74cZoycjePYnH0kSuNuBGbZXwP8M0xzdmHaJZmQl+u6aJIqxv+Qfi4XUVZOsl5e0bG4k4/SMFSLqnLppnR+fr+6qjIN+Snk6XS1IzSR0JnhBUrfkpzFGNcd2mUFrmNFj+qGhA2GjBVpB9ItY0atZusL74RDN9oZPMMOYHQGliScE1tdmnBpnRC8yi72bRe6UO/Oec3KoaqGvD/EcZWjdlFC0Z1cZBlyv4S3Ucubna/KYfeL5tLVzsruZPzF1Z7if1v6sFNv2pPdtSE/2/CHooozUv+0r/TnD4l2pIrSkyeGr0GOaJiQtgT50lTFUP7pKYUJItY+kMHiTahN11vacqfea1hoedpGgYc2SCvUT9q083QnfVz7K9Orm32H8XrcNgS/8pVHgt9p8X3yv7+dfv5ln/ROSn4a9P8l1Zl/C164PCvKZA02ZiLjF/JNtpZmiS1rpIy/Tt6qK4nlGEuXr0aDHx1gd/vpR5PWpKFB2sn0jyFBtNtOPHOGTke0iUZ1HsH6sc7AJ2RDkL8ZsB1lA80VYaOxyU5jrZgS1vgmwbUQnE939e9RT8agAz5hyMHDSZv/7TOxledjdPV2fiaswEv3tnAvn3NQQGGRC7FxvEZ27hU4cNtnJs4QlDHYtiPjUPDxvEogEPZOHTsCEDcae3030RfwTvwdNm7vYgemaJ/B9DIw0MJ347+7iupi8G+j1g8bVGA5+VxaZZd04wWVV84HhMvjnl7yQr6nShHEj98cA7C4+55XQgNlwqbCErN63BjF2pa5x0tqwPWMA/qaPdJ6rSMbrT2uYO7WbejmwV9u9mq62VRRCvlhAbNthp8YOpeE71vtKe+Yr9m7lp2fpllNI6YleSVZwz4YBsS7AP4GHlHiecD3Y0gA1KOGMxDSwnaUecnFDaAGGhi6SmwQ7pvxyPgDyZuS9q/5ilLuSS5N+jTpycRCcatPt2LA/IwHsan+4GOq4HT4tOdFp9uGmB/Pr09bXpjajt5dXgqr36YzD3L6lQ69Y572vtzT6ukXvfEmjm6P/T7AV7fzKu8keerrKk7GA4jSyXcN+nX0oc8/NWl1I/0PYsz9Qfzu/CksA8UzFf40+flcv7pkrmusA/dF4X70MZ974yNHPZm5Dy0xrgnqwZoBAejRVE7SJ/V1Acf/B2GXO3dkiLlwyV076VYN+rbuvfiaoADA82PyKTiQ9cOPtrVwceHdcAONtT/CPwRaqEOZDHTOeKbNO4+8M1xJROxL74NAGZ2DHpH8qR65nJO81IkJxyL+L+p2FyUZ52h9CfeutjCmKHphwj0Xf2qbjAabNrHNuebJYn5pcTXfcqm11wQZMksHTh/nsic+2nlidrmfo7GE21JB88q0jhdkYWs1DuT6R/gID1C4A27PP6OHsdx+a6d0aAzdgnSyPrw+BAiA7t7mi1wDdoCgAHrQNzQEvivAgKjir4cF0R8recIryt0LETbii7EAxb2rMJPqh6uo08cyqmYA9UDADxCoakgCKmsJhhMW+yqoTsWxd8rR8r96Pbp5INCCEyCBLWFEIH7IOpgTxJCACc4df0ItI33tOsENBrS7UhDrqMItdtpaEi3Y1TRtXZzky46h9JhA6QHrqVNRn7wNZ0RuniN+QFwPd25tyYIshZwkAQB2umaNfDlNJqLzXFGlpdiuVtlLEmz+T7OorJMY8NYN9F8r5O++5SV7W+qOOzZVBU5t2G4bDvUmzuSQZPMHzCWcdRvbq38aSMdDQ7RGXYNEbTZJXN94BZtXcyyy5hRNQas4sVbWqZieRE/5YEyRmctQWK9ym/bksEjef7QWBAo1/4pOgMH9ft2sf6a2GuGXuX1tpR0V1TP0UJ0ESWUdcAwkJgAMDFcptAqhoeBLSizPKs3QSG70voLiUldG7IhYV+9YHyj2iN0bMHI9SbDCMZe0maJgV8mnZdEl4TEsJKjK+O5DyN2SFMnZW0hzXjcHBlg1D3PoDzlhMUO1ILHQi1kc0v2mO9MVhRZmEawdhpKCETnJK9bmuwleFIuL6/OXWrq7sXLHQOdQycwjNXxARo27EC+pUW32WKS5rxtXeparVJ+9bjqOUYsGUIbVuUvPAwCq7Kc+q0K6qj0g+QDX0gZFLYz1fYVxOfBCuNthrhXpSuSBnho5ojaS1kNtvn4NBE+7erwM6cYUWfeond/fpiN2ylncM423uP6cBA6ejlzP/OCAIhCaeWjzwfLepQByojsWYYtqxkOWiOMxV8rD1x9OsheHFf61Z+eUliMXtpCI2zzbzVdz990ldEoefXxL0BGjUQb4SOXGgwT/9q0whfy46fje5Br1MMGLUTcoHwPtjHs09ev4ket5k3yeBCU7QImA+qCmLT/HMJDwHHmyYmRZ7DW2FhkIxfDaIjVwoZCdCQheHb1gDXuL2lGa6sQdgZvuGuCdqJZJ+yH+jW6zzo54QgHys+Q6TpmTnkcmQyShL9KBlGadUBY1SRzmpNngW4bHOiAsUu1LMtfc8Z94K9jBCt+S6UKalE391iximczBB9pFuUT3rYllvzJRAbd3SJrjS+PJzM7lrn9JDxm5+nEVy0wLHmdJwQWoF7kxXc3PxNcQ+fm55bhzT8=</diagram></mxfile>" style="background-color: rgb(255, 255, 255);"><defs/><g><rect x="0" y="0" width="1199" height="810" fill="#ffffff" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-start; width: 1197px; height: 1px; padding-top: 7px; margin-left: 2px;"><div style="box-sizing: border-box; font-size: 0; text-align: left; "><div style="display: inline-block; font-size: 12px; font-family: Courier New; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><h1>Simple User request</h1></div></div></div></foreignObject><text x="2" y="19" fill="#000000" font-family="Courier New" font-size="12px">Simple User request</text></switch></g><path d="M 417.5 574 L 417.5 657.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 417.5 662.88 L 414 655.88 L 417.5 657.63 L 421 655.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 616px; margin-left: 296px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">Give me sync worker (POP operation)</div></div></div></foreignObject><text x="296" y="620" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">Give me sync worker (POP operation)</text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 617px; margin-left: 418px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">3</div></div></div></foreignObject><text x="418" y="620" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">3</text></switch></g><path d="M 492.5 514 L 492.5 430.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 492.5 425.12 L 496 432.12 L 492.5 430.37 L 489 432.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 464px; margin-left: 493px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">5</div></div></div></foreignObject><text x="493" y="468" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">5</text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 465px; margin-left: 535px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">Get worker</div></div></div></foreignObject><text x="535" y="468" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">Get worker</text></switch></g><rect x="380" y="514" width="150" height="60" fill="#ffe6cc" stroke="#d79b00" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 544px; margin-left: 381px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Workers Watcher</div></div></div></foreignObject><text x="455" y="548" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Workers Watcher</text></switch></g><path d="M 280 424 L 280 544 L 373.63 544" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 378.88 544 L 371.88 547.5 L 373.63 544 L 371.88 540.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 515px; margin-left: 202px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">Allocate sync workers</div></div></div></foreignObject><text x="202" y="518" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">Allocate sync workers</text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 514px; margin-left: 280px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">1</div></div></div></foreignObject><text x="280" y="518" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">1</text></switch></g><rect x="230" y="384" width="100" height="40" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 404px; margin-left: 231px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Initialize</div></div></div></foreignObject><text x="280" y="408" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Initialize</text></switch></g><path d="M 417.5 424 L 417.5 507.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 417.5 512.88 L 414 505.88 L 417.5 507.63 L 421 505.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 464px; margin-left: 352px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">Give me SyncWorker</div></div></div></foreignObject><text x="352" y="467" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">Give me SyncWorker</text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 464px; margin-left: 418px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">2</div></div></div></foreignObject><text x="418" y="468" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">2</text></switch></g><path d="M 530 414 L 700.63 414" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 705.88 414 L 698.88 417.5 L 700.63 414 L 698.88 410.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 415px; margin-left: 618px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">6</div></div></div></foreignObject><text x="618" y="418" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">6</text></switch></g><path d="M 492.5 384 L 483 384 L 483 324 L 520 324 L 520 83 L 468.87 83" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 463.62 83 L 470.62 79.5 L 468.87 83 L 470.62 86.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 224px; margin-left: 521px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">10</div></div></div></foreignObject><text x="521" y="227" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">10</text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 225px; margin-left: 597px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">Send response to the user</div></div></div></foreignObject><text x="597" y="228" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">Send response to the user</text></switch></g><rect x="380" y="384" width="150" height="40" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 404px; margin-left: 381px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Exec/ExecWithContext</div></div></div></foreignObject><text x="455" y="408" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Exec/ExecWithContext</text></switch></g><path d="M 492.5 664 L 492.5 624 L 492.5 580.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 492.5 575.12 L 496 582.12 L 492.5 580.37 L 489 582.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 616px; margin-left: 494px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">4</div></div></div></foreignObject><text x="494" y="620" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">4</text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 617px; margin-left: 610px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">I have free workers, here you are</div></div></div></foreignObject><text x="610" y="620" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">I have free workers, here you are</text></switch></g><rect x="380" y="664" width="150" height="60" fill="#d5e8d4" stroke="#82b366" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 694px; margin-left: 381px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Stack with workers</div></div></div></foreignObject><text x="455" y="698" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Stack with workers</text></switch></g><path d="M 707 394 L 536.37 394" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 531.12 394 L 538.12 390.5 L 536.37 394 L 538.12 397.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 394px; margin-left: 618px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">9</div></div></div></foreignObject><text x="618" y="397" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">9</text></switch></g><rect x="707" y="384" width="163" height="40" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 161px; height: 1px; padding-top: 404px; margin-left: 708px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Exec/ExecWithTimeout</div></div></div></foreignObject><text x="789" y="408" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Exec/ExecWithTimeout</text></switch></g><path d="M 450 289.5 L 460 289.5 L 460 364.5 L 470.5 364.5 L 455 383.5 L 439.5 364.5 L 450 364.5 Z" fill="none" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/><ellipse cx="455" cy="84.5" rx="7.5" ry="7.5" fill="#ffffff" stroke="#000000" pointer-events="all"/><path d="M 455 92 L 455 117 M 455 97 L 440 97 M 455 97 L 470 97 M 455 117 L 440 137 M 455 117 L 470 137" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 144px; margin-left: 455px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">User request</div></div></div></foreignObject><text x="455" y="156" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">User...</text></switch></g><rect x="607" y="426" width="198" height="17" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 435px; margin-left: 706px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">Send request to the worker</div></div></div></foreignObject><text x="706" y="438" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Send request to the worker</text></switch></g><rect x="618" y="368" width="125" height="17" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 377px; margin-left: 681px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">Receive response</div></div></div></foreignObject><text x="681" y="380" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Receive response</text></switch></g><ellipse cx="125" cy="404" rx="11" ry="11" fill="#000000" stroke="#ff0000" pointer-events="all"/><path d="M 140 404 L 227.76 404" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 219.88 408.5 L 228.88 404 L 219.88 399.5" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><rect x="45" y="371" width="161" height="17" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 380px; margin-left: 126px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">Plugin Initialization</div></div></div></foreignObject><text x="126" y="383" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Plugin Initialization</text></switch></g><path d="M 870 414 L 983.63 414" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 988.88 414 L 981.88 417.5 L 983.63 414 L 981.88 410.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 413px; margin-left: 930px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">7</div></div></div></foreignObject><text x="930" y="416" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">7</text></switch></g><path d="M 990 394 L 876.37 394" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 871.12 394 L 878.12 390.5 L 876.37 394 L 878.12 397.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 394px; margin-left: 931px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">8</div></div></div></foreignObject><text x="931" y="397" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">8</text></switch></g><rect x="990" y="384" width="100" height="40" fill="#f5f5f5" stroke="#666666" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 404px; margin-left: 991px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Worker</div></div></div></foreignObject><text x="1040" y="408" fill="#333333" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Worker</text></switch></g><rect x="893" y="426" width="96" height="17" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 435px; margin-left: 941px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">Exec payload</div></div></div></foreignObject><text x="941" y="438" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Exec payload</text></switch></g><rect x="873" y="366" width="125" height="17" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 375px; margin-left: 936px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">Reveive response</div></div></div></foreignObject><text x="936" y="378" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Reveive response</text></switch></g><rect x="401" y="255" width="108" height="34" fill="#f8cecc" stroke="#b85450" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 106px; height: 1px; padding-top: 272px; margin-left: 402px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">HTTP plugin</div></div></div></foreignObject><text x="455" y="276" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">HTTP plugin</text></switch></g><path d="M 450 157.5 L 460 157.5 L 460 235.5 L 470.5 235.5 L 455 254.5 L 439.5 235.5 L 450 235.5 Z" fill="none" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/><rect x="490" y="364" width="40" height="20" fill="none" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 38px; height: 1px; padding-top: 374px; margin-left: 491px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Pool</div></div></div></foreignObject><text x="510" y="378" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Pool</text></switch></g><rect x="770" y="364" width="100" height="20" fill="none" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 374px; margin-left: 771px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Golang Worker</div></div></div></foreignObject><text x="820" y="378" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Golang Worker</text></switch></g><rect x="1006" y="364" width="84" height="20" fill="none" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 82px; height: 1px; padding-top: 374px; margin-left: 1007px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">PHP worker</div></div></div></foreignObject><text x="1048" y="378" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">PHP worker</text></switch></g></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Viewer does not support full SVG 1.1</text></a></switch></svg>
\ No newline at end of file diff --git a/pkg/events/general.go b/pkg/events/general.go deleted file mode 100755 index a09a8759..00000000 --- a/pkg/events/general.go +++ /dev/null @@ -1,39 +0,0 @@ -package events - -import ( - "sync" -) - -// HandlerImpl helps to broadcast events to multiple listeners. -type HandlerImpl struct { - listeners []Listener - sync.RWMutex // all receivers should be pointers -} - -func NewEventsHandler() Handler { - return &HandlerImpl{listeners: make([]Listener, 0, 2)} -} - -// NumListeners returns number of event listeners. -func (eb *HandlerImpl) NumListeners() int { - eb.Lock() - defer eb.Unlock() - return len(eb.listeners) -} - -// AddListener registers new event listener. -func (eb *HandlerImpl) AddListener(listener Listener) { - eb.Lock() - defer eb.Unlock() - eb.listeners = append(eb.listeners, listener) -} - -// Push broadcast events across all event listeners. -func (eb *HandlerImpl) Push(e interface{}) { - // ReadLock here because we are not changing listeners - eb.RLock() - defer eb.RUnlock() - for k := range eb.listeners { - eb.listeners[k](e) - } -} diff --git a/pkg/events/interface.go b/pkg/events/interface.go deleted file mode 100644 index ac6c15a4..00000000 --- a/pkg/events/interface.go +++ /dev/null @@ -1,14 +0,0 @@ -package events - -// Handler interface -type Handler interface { - // Return number of active listeners - NumListeners() int - // AddListener adds lister to the publisher - AddListener(listener Listener) - // Push pushes event to the listeners - Push(e interface{}) -} - -// Event listener listens for the events produced by worker, worker pool or other service. -type Listener func(event interface{}) diff --git a/pkg/events/pool_events.go b/pkg/events/pool_events.go deleted file mode 100644 index 3925df56..00000000 --- a/pkg/events/pool_events.go +++ /dev/null @@ -1,66 +0,0 @@ -package events - -// TODO event numbers -const ( - // EventWorkerConstruct thrown when new worker is spawned. - EventWorkerConstruct P = iota + 10000 - - // EventWorkerDestruct thrown after worker destruction. - EventWorkerDestruct - - // EventPoolError caused on pool wide errors. - EventPoolError - - // EventSupervisorError triggered when supervisor can not complete work. - EventSupervisorError - - // EventNoFreeWorkers triggered when there are no free workers in the stack and timeout for worker allocate elapsed - EventNoFreeWorkers - - // EventMaxMemory caused when worker consumes more memory than allowed. - EventMaxMemory - - // EventTTL thrown when worker is removed due TTL being reached. TTL defines maximum time worker is allowed to live (seconds) - EventTTL - - // EventIdleTTL triggered when worker spends too much time at rest. - EventIdleTTL - - // EventExecTTL triggered when worker spends too much time doing the task (max_execution_time). - EventExecTTL -) - -type P int64 - -func (ev P) String() string { - switch ev { - case EventWorkerConstruct: - return "EventWorkerConstruct" - case EventWorkerDestruct: - return "EventWorkerDestruct" - case EventPoolError: - return "EventPoolError" - case EventSupervisorError: - return "EventSupervisorError" - case EventNoFreeWorkers: - return "EventNoFreeWorkers" - case EventMaxMemory: - return "EventMaxMemory" - case EventTTL: - return "EventTTL" - case EventIdleTTL: - return "EventIdleTTL" - case EventExecTTL: - return "EventExecTTL" - } - return "Unknown event type" -} - -// PoolEvent triggered by pool on different events. Pool as also trigger WorkerEvent in case of log. -type PoolEvent struct { - // Event type, see below. - Event P - - // Payload depends on event type, typically it's worker or error. - Payload interface{} -} diff --git a/pkg/events/worker_events.go b/pkg/events/worker_events.go deleted file mode 100644 index 9d428f7d..00000000 --- a/pkg/events/worker_events.go +++ /dev/null @@ -1,33 +0,0 @@ -package events - -const ( - // EventWorkerError triggered after WorkerProcess. Except payload to be error. - EventWorkerError W = iota + 11000 - - // EventWorkerLog triggered on every write to WorkerProcess StdErr pipe (batched). Except payload to be []byte string. - EventWorkerLog -) - -type W int64 - -func (ev W) String() string { - switch ev { - case EventWorkerError: - return "EventWorkerError" - case EventWorkerLog: - return "EventWorkerLog" - } - return "Unknown event type" -} - -// WorkerEvent wraps worker events. -type WorkerEvent struct { - // Event id, see below. - Event W - - // Worker triggered the event. - Worker interface{} - - // Event specific payload. - Payload interface{} -} diff --git a/pkg/payload/payload.go b/pkg/payload/payload.go deleted file mode 100755 index fac36852..00000000 --- a/pkg/payload/payload.go +++ /dev/null @@ -1,16 +0,0 @@ -package payload - -// Payload carries binary header and body to stack and -// back to the server. -type Payload struct { - // Context represent payload context, might be omitted. - Context []byte - - // body contains binary payload to be processed by WorkerProcess. - Body []byte -} - -// String returns payload body as string -func (p *Payload) String() string { - return string(p.Body) -} diff --git a/pkg/pool/config.go b/pkg/pool/config.go deleted file mode 100644 index 782f7ce9..00000000 --- a/pkg/pool/config.go +++ /dev/null @@ -1,75 +0,0 @@ -package pool - -import ( - "runtime" - "time" -) - -// Configures the pool behaviour. -type Config struct { - // Debug flag creates new fresh worker before every request. - Debug bool - - // NumWorkers defines how many sub-processes can be run at once. This value - // might be doubled by Swapper while hot-swap. Defaults to number of CPU cores. - NumWorkers uint64 `mapstructure:"num_workers"` - - // MaxJobs defines how many executions is allowed for the worker until - // it's destruction. set 1 to create new process for each new task, 0 to let - // worker handle as many tasks as it can. - MaxJobs uint64 `mapstructure:"max_jobs"` - - // AllocateTimeout defines for how long pool will be waiting for a worker to - // be freed to handle the task. Defaults to 60s. - AllocateTimeout time.Duration `mapstructure:"allocate_timeout"` - - // DestroyTimeout defines for how long pool should be waiting for worker to - // properly destroy, if timeout reached worker will be killed. Defaults to 60s. - DestroyTimeout time.Duration `mapstructure:"destroy_timeout"` - - // Supervision config to limit worker and pool memory usage. - Supervisor *SupervisorConfig `mapstructure:"supervisor"` -} - -// InitDefaults enables default config values. -func (cfg *Config) InitDefaults() { - if cfg.NumWorkers == 0 { - cfg.NumWorkers = uint64(runtime.NumCPU()) - } - - if cfg.AllocateTimeout == 0 { - cfg.AllocateTimeout = time.Minute - } - - if cfg.DestroyTimeout == 0 { - cfg.DestroyTimeout = time.Minute - } - if cfg.Supervisor == nil { - return - } - cfg.Supervisor.InitDefaults() -} - -type SupervisorConfig struct { - // WatchTick defines how often to check the state of worker. - WatchTick time.Duration `mapstructure:"watch_tick"` - - // TTL defines maximum time worker is allowed to live. - TTL time.Duration `mapstructure:"ttl"` - - // IdleTTL defines maximum duration worker can spend in idle mode. Disabled when 0. - IdleTTL time.Duration `mapstructure:"idle_ttl"` - - // ExecTTL defines maximum lifetime per job. - ExecTTL time.Duration `mapstructure:"exec_ttl"` - - // MaxWorkerMemory limits memory per worker. - MaxWorkerMemory uint64 `mapstructure:"max_worker_memory"` -} - -// InitDefaults enables default config values. -func (cfg *SupervisorConfig) InitDefaults() { - if cfg.WatchTick == 0 { - cfg.WatchTick = time.Second - } -} diff --git a/pkg/pool/interface.go b/pkg/pool/interface.go deleted file mode 100644 index 4f7ae595..00000000 --- a/pkg/pool/interface.go +++ /dev/null @@ -1,29 +0,0 @@ -package pool - -import ( - "context" - - "github.com/spiral/roadrunner/v2/pkg/payload" - "github.com/spiral/roadrunner/v2/pkg/worker" -) - -// Pool managed set of inner worker processes. -type Pool interface { - // GetConfig returns pool configuration. - GetConfig() interface{} - - // Exec executes task with payload - Exec(rqs payload.Payload) (payload.Payload, error) - - // ExecWithContext executes task with context which is used with timeout - ExecWithContext(ctx context.Context, rqs payload.Payload) (payload.Payload, error) - - // Workers returns worker list associated with the pool. - Workers() (workers []worker.SyncWorker) - - // Remove worker from the pool. - RemoveWorker(worker worker.SyncWorker) error - - // Destroy all underlying stack (but let them to complete the task). - Destroy(ctx context.Context) -} diff --git a/pkg/pool/static_pool.go b/pkg/pool/static_pool.go deleted file mode 100755 index 44adf9c0..00000000 --- a/pkg/pool/static_pool.go +++ /dev/null @@ -1,327 +0,0 @@ -package pool - -import ( - "context" - "os/exec" - "time" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/internal" - "github.com/spiral/roadrunner/v2/pkg/events" - "github.com/spiral/roadrunner/v2/pkg/payload" - "github.com/spiral/roadrunner/v2/pkg/transport" - "github.com/spiral/roadrunner/v2/pkg/worker" - workerWatcher "github.com/spiral/roadrunner/v2/pkg/worker_watcher" -) - -// StopRequest can be sent by worker to indicate that restart is required. -const StopRequest = "{\"stop\":true}" - -// ErrorEncoder encode error or make a decision based on the error type -type ErrorEncoder func(err error, w worker.SyncWorker) (payload.Payload, error) - -type Options func(p *StaticPool) - -type Command func() *exec.Cmd - -// StaticPool controls worker creation, destruction and task routing. Pool uses fixed amount of stack. -type StaticPool struct { - cfg Config - - // worker command creator - cmd Command - - // creates and connects to stack - factory transport.Factory - - // distributes the events - events events.Handler - - // saved list of event listeners - listeners []events.Listener - - // manages worker states and TTLs - ww workerWatcher.Watcher - - // allocate new worker - allocator worker.Allocator - - // errEncoder is the default Exec error encoder - errEncoder ErrorEncoder -} - -// Initialize creates new worker pool and task multiplexer. StaticPool will initiate with one worker. -func Initialize(ctx context.Context, cmd Command, factory transport.Factory, cfg Config, options ...Options) (Pool, error) { - const op = errors.Op("static_pool_initialize") - if factory == nil { - return nil, errors.E(op, errors.Str("no factory initialized")) - } - cfg.InitDefaults() - - if cfg.Debug { - cfg.NumWorkers = 0 - cfg.MaxJobs = 1 - } - - p := &StaticPool{ - cfg: cfg, - cmd: cmd, - factory: factory, - events: events.NewEventsHandler(), - } - - // add pool options - for i := 0; i < len(options); i++ { - options[i](p) - } - - p.allocator = p.newPoolAllocator(ctx, p.cfg.AllocateTimeout, factory, cmd) - p.ww = workerWatcher.NewSyncWorkerWatcher(p.allocator, p.cfg.NumWorkers, p.events) - - workers, err := p.allocateWorkers(p.cfg.NumWorkers) - if err != nil { - return nil, errors.E(op, err) - } - - // put stack in the pool - err = p.ww.AddToWatch(workers) - if err != nil { - return nil, errors.E(op, err) - } - - p.errEncoder = defaultErrEncoder(p) - - // if supervised config not nil, guess, that pool wanted to be supervised - if cfg.Supervisor != nil { - sp := supervisorWrapper(p, p.events, p.cfg.Supervisor) - // start watcher timer - sp.Start() - return sp, nil - } - - return p, nil -} - -func AddListeners(listeners ...events.Listener) Options { - return func(p *StaticPool) { - p.listeners = listeners - for i := 0; i < len(listeners); i++ { - p.addListener(listeners[i]) - } - } -} - -// AddListener connects event listener to the pool. -func (sp *StaticPool) addListener(listener events.Listener) { - sp.events.AddListener(listener) -} - -// Config returns associated pool configuration. Immutable. -func (sp *StaticPool) GetConfig() interface{} { - return sp.cfg -} - -// Workers returns worker list associated with the pool. -func (sp *StaticPool) Workers() (workers []worker.SyncWorker) { - return sp.ww.WorkersList() -} - -func (sp *StaticPool) RemoveWorker(wb worker.SyncWorker) error { - return sp.ww.RemoveWorker(wb) -} - -// Be careful, sync Exec with ExecWithContext -func (sp *StaticPool) Exec(p payload.Payload) (payload.Payload, error) { - const op = errors.Op("static_pool_exec") - if sp.cfg.Debug { - return sp.execDebug(p) - } - ctxGetFree, cancel := context.WithTimeout(context.Background(), sp.cfg.AllocateTimeout) - defer cancel() - w, err := sp.getWorker(ctxGetFree, op) - if err != nil { - return payload.Payload{}, errors.E(op, err) - } - - rsp, err := w.Exec(p) - if err != nil { - return sp.errEncoder(err, w) - } - - // worker want's to be terminated - // TODO careful with string(rsp.Context) - if len(rsp.Body) == 0 && string(rsp.Context) == StopRequest { - sp.stopWorker(w) - - return sp.Exec(p) - } - - err = sp.checkMaxJobs(w) - if err != nil { - return payload.Payload{}, errors.E(op, err) - } - - return rsp, nil -} - -// Be careful, sync with pool.Exec method -func (sp *StaticPool) ExecWithContext(ctx context.Context, p payload.Payload) (payload.Payload, error) { - const op = errors.Op("static_pool_exec_with_context") - ctxGetFree, cancel := context.WithTimeout(ctx, sp.cfg.AllocateTimeout) - defer cancel() - w, err := sp.getWorker(ctxGetFree, op) - if err != nil { - return payload.Payload{}, errors.E(op, err) - } - - rsp, err := w.ExecWithTimeout(ctx, p) - if err != nil { - return sp.errEncoder(err, w) - } - - // worker want's to be terminated - if len(rsp.Body) == 0 && string(rsp.Context) == StopRequest { - sp.stopWorker(w) - return sp.ExecWithContext(ctx, p) - } - - err = sp.checkMaxJobs(w) - if err != nil { - return payload.Payload{}, errors.E(op, err) - } - - return rsp, nil -} - -func (sp *StaticPool) stopWorker(w worker.SyncWorker) { - const op = errors.Op("static_pool_stop_worker") - w.State().Set(internal.StateInvalid) - err := w.Stop() - if err != nil { - sp.events.Push(events.WorkerEvent{Event: events.EventWorkerError, Worker: w, Payload: errors.E(op, err)}) - } -} - -// checkMaxJobs check for worker number of executions and kill workers if that number more than sp.cfg.MaxJobs -func (sp *StaticPool) checkMaxJobs(w worker.SyncWorker) error { - const op = errors.Op("static_pool_check_max_jobs") - if sp.cfg.MaxJobs != 0 && w.State().NumExecs() >= sp.cfg.MaxJobs { - err := sp.ww.AllocateNew() - if err != nil { - return errors.E(op, err) - } - } else { - sp.ww.PushWorker(w) - } - return nil -} - -func (sp *StaticPool) getWorker(ctxGetFree context.Context, op errors.Op) (worker.SyncWorker, error) { - // GetFreeWorker function consumes context with timeout - w, err := sp.ww.GetFreeWorker(ctxGetFree) - if err != nil { - // if the error is of kind NoFreeWorkers, it means, that we can't get worker from the stack during the allocate timeout - if errors.Is(errors.NoFreeWorkers, err) { - sp.events.Push(events.PoolEvent{Event: events.EventNoFreeWorkers, Payload: errors.E(op, err)}) - return nil, errors.E(op, err) - } - // else if err not nil - return error - return nil, errors.E(op, err) - } - return w, nil -} - -// Destroy all underlying stack (but let them to complete the task). -func (sp *StaticPool) Destroy(ctx context.Context) { - sp.ww.Destroy(ctx) -} - -func defaultErrEncoder(sp *StaticPool) ErrorEncoder { - return func(err error, w worker.SyncWorker) (payload.Payload, error) { - const op = errors.Op("error encoder") - // just push event if on any stage was timeout error - if errors.Is(errors.ExecTTL, err) { - sp.events.Push(events.PoolEvent{Event: events.EventExecTTL, Payload: errors.E(op, err)}) - } - // soft job errors are allowed - if errors.Is(errors.SoftJob, err) { - if sp.cfg.MaxJobs != 0 && w.State().NumExecs() >= sp.cfg.MaxJobs { - err = sp.ww.AllocateNew() - if err != nil { - sp.events.Push(events.PoolEvent{Event: events.EventWorkerConstruct, Payload: errors.E(op, err)}) - } - - w.State().Set(internal.StateInvalid) - err = w.Stop() - if err != nil { - sp.events.Push(events.WorkerEvent{Event: events.EventWorkerError, Worker: w, Payload: errors.E(op, err)}) - } - } else { - sp.ww.PushWorker(w) - } - - return payload.Payload{}, errors.E(op, err) - } - - w.State().Set(internal.StateInvalid) - sp.events.Push(events.PoolEvent{Event: events.EventWorkerDestruct, Payload: w}) - errS := w.Stop() - - if errS != nil { - return payload.Payload{}, errors.E(op, errors.Errorf("%v, %v", err, errS)) - } - - return payload.Payload{}, errors.E(op, err) - } -} - -func (sp *StaticPool) newPoolAllocator(ctx context.Context, timeout time.Duration, factory transport.Factory, cmd func() *exec.Cmd) worker.Allocator { - return func() (*worker.SyncWorkerImpl, error) { - ctx, cancel := context.WithTimeout(ctx, timeout) - defer cancel() - w, err := factory.SpawnWorkerWithTimeout(ctx, cmd(), sp.listeners...) - if err != nil { - return nil, err - } - - sw := worker.From(w) - - sp.events.Push(events.PoolEvent{ - Event: events.EventWorkerConstruct, - Payload: sw, - }) - return sw, nil - } -} - -func (sp *StaticPool) execDebug(p payload.Payload) (payload.Payload, error) { - sw, err := sp.allocator() - if err != nil { - return payload.Payload{}, err - } - - r, err := sw.Exec(p) - - if stopErr := sw.Stop(); stopErr != nil { - sp.events.Push(events.WorkerEvent{Event: events.EventWorkerError, Worker: sw, Payload: err}) - } - - return r, err -} - -// allocate required number of stack -func (sp *StaticPool) allocateWorkers(numWorkers uint64) ([]worker.SyncWorker, error) { - const op = errors.Op("allocate workers") - var workers []worker.SyncWorker - - // constant number of stack simplify logic - for i := uint64(0); i < numWorkers; i++ { - w, err := sp.allocator() - if err != nil { - return nil, errors.E(op, errors.WorkerAllocate, err) - } - - workers = append(workers, w) - } - return workers, nil -} diff --git a/pkg/pool/static_pool_test.go b/pkg/pool/static_pool_test.go deleted file mode 100755 index a32790e0..00000000 --- a/pkg/pool/static_pool_test.go +++ /dev/null @@ -1,647 +0,0 @@ -package pool - -import ( - "context" - "log" - "os/exec" - "runtime" - "strconv" - "strings" - "sync" - "testing" - "time" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/internal" - "github.com/spiral/roadrunner/v2/pkg/events" - "github.com/spiral/roadrunner/v2/pkg/payload" - "github.com/spiral/roadrunner/v2/pkg/transport/pipe" - "github.com/stretchr/testify/assert" -) - -var cfg = Config{ - NumWorkers: uint64(runtime.NumCPU()), - AllocateTimeout: time.Second * 5, - DestroyTimeout: time.Second * 5, -} - -func Test_NewPool(t *testing.T) { - ctx := context.Background() - p, err := Initialize( - ctx, - func() *exec.Cmd { return exec.Command("php", "../../tests/client.php", "echo", "pipes") }, - pipe.NewPipeFactory(), - cfg, - ) - assert.NoError(t, err) - - defer p.Destroy(ctx) - - assert.NotNil(t, p) -} - -func Test_StaticPool_Invalid(t *testing.T) { - p, err := Initialize( - context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../tests/invalid.php") }, - pipe.NewPipeFactory(), - cfg, - ) - - assert.Nil(t, p) - assert.Error(t, err) -} - -func Test_ConfigNoErrorInitDefaults(t *testing.T) { - p, err := Initialize( - context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../tests/client.php", "echo", "pipes") }, - pipe.NewPipeFactory(), - Config{ - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, - ) - - assert.NotNil(t, p) - assert.NoError(t, err) -} - -func Test_StaticPool_Echo(t *testing.T) { - ctx := context.Background() - p, err := Initialize( - ctx, - func() *exec.Cmd { return exec.Command("php", "../../tests/client.php", "echo", "pipes") }, - pipe.NewPipeFactory(), - cfg, - ) - assert.NoError(t, err) - - defer p.Destroy(ctx) - - assert.NotNil(t, p) - - res, err := p.Exec(payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Test_StaticPool_Echo_NilContext(t *testing.T) { - ctx := context.Background() - p, err := Initialize( - ctx, - func() *exec.Cmd { return exec.Command("php", "../../tests/client.php", "echo", "pipes") }, - pipe.NewPipeFactory(), - cfg, - ) - assert.NoError(t, err) - - defer p.Destroy(ctx) - - assert.NotNil(t, p) - - res, err := p.Exec(payload.Payload{Body: []byte("hello"), Context: nil}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Test_StaticPool_Echo_Context(t *testing.T) { - ctx := context.Background() - p, err := Initialize( - ctx, - func() *exec.Cmd { return exec.Command("php", "../../tests/client.php", "head", "pipes") }, - pipe.NewPipeFactory(), - cfg, - ) - assert.NoError(t, err) - - defer p.Destroy(ctx) - - assert.NotNil(t, p) - - res, err := p.Exec(payload.Payload{Body: []byte("hello"), Context: []byte("world")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.Empty(t, res.Body) - assert.NotNil(t, res.Context) - - assert.Equal(t, "world", string(res.Context)) -} - -func Test_StaticPool_JobError(t *testing.T) { - ctx := context.Background() - p, err := Initialize( - ctx, - func() *exec.Cmd { return exec.Command("php", "../../tests/client.php", "error", "pipes") }, - pipe.NewPipeFactory(), - cfg, - ) - assert.NoError(t, err) - defer p.Destroy(ctx) - - assert.NotNil(t, p) - - res, err := p.Exec(payload.Payload{Body: []byte("hello")}) - - assert.Error(t, err) - assert.Nil(t, res.Body) - assert.Nil(t, res.Context) - - if errors.Is(errors.SoftJob, err) == false { - t.Fatal("error should be of type errors.Exec") - } - - assert.Contains(t, err.Error(), "hello") -} - -func Test_StaticPool_Broken_Replace(t *testing.T) { - ctx := context.Background() - block := make(chan struct{}, 1) - - listener := func(event interface{}) { - if wev, ok := event.(events.WorkerEvent); ok { - if wev.Event == events.EventWorkerLog { - e := string(wev.Payload.([]byte)) - if strings.ContainsAny(e, "undefined_function()") { - block <- struct{}{} - return - } - } - } - } - - p, err := Initialize( - ctx, - func() *exec.Cmd { return exec.Command("php", "../../tests/client.php", "broken", "pipes") }, - pipe.NewPipeFactory(), - cfg, - AddListeners(listener), - ) - assert.NoError(t, err) - assert.NotNil(t, p) - - time.Sleep(time.Second) - res, err := p.ExecWithContext(ctx, payload.Payload{Body: []byte("hello")}) - assert.Error(t, err) - assert.Nil(t, res.Context) - assert.Nil(t, res.Body) - - <-block - - p.Destroy(ctx) -} - -func Test_StaticPool_Broken_FromOutside(t *testing.T) { - ctx := context.Background() - // Consume pool events - ev := make(chan struct{}, 1) - listener := func(event interface{}) { - if pe, ok := event.(events.PoolEvent); ok { - if pe.Event == events.EventWorkerConstruct { - ev <- struct{}{} - } - } - } - - var cfg = Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 5, - DestroyTimeout: time.Second * 5, - } - - p, err := Initialize( - ctx, - func() *exec.Cmd { return exec.Command("php", "../../tests/client.php", "echo", "pipes") }, - pipe.NewPipeFactory(), - cfg, - AddListeners(listener), - ) - assert.NoError(t, err) - defer p.Destroy(ctx) - - assert.NotNil(t, p) - - res, err := p.Exec(payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) - assert.Equal(t, 1, len(p.Workers())) - - // first creation - <-ev - // killing random worker and expecting pool to replace it - err = p.Workers()[0].Kill() - if err != nil { - t.Errorf("error killing the process: error %v", err) - } - - // re-creation - <-ev - - list := p.Workers() - for _, w := range list { - assert.Equal(t, internal.StateReady, w.State().Value()) - } -} - -func Test_StaticPool_AllocateTimeout(t *testing.T) { - p, err := Initialize( - context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../tests/client.php", "delay", "pipes") }, - pipe.NewPipeFactory(), - Config{ - NumWorkers: 1, - AllocateTimeout: time.Nanosecond * 1, - DestroyTimeout: time.Second * 2, - }, - ) - assert.Error(t, err) - if !errors.Is(errors.WorkerAllocate, err) { - t.Fatal("error should be of type WorkerAllocate") - } - assert.Nil(t, p) -} - -func Test_StaticPool_Replace_Worker(t *testing.T) { - ctx := context.Background() - p, err := Initialize( - ctx, - func() *exec.Cmd { return exec.Command("php", "../../tests/client.php", "pid", "pipes") }, - pipe.NewPipeFactory(), - Config{ - NumWorkers: 1, - MaxJobs: 1, - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, - ) - assert.NoError(t, err) - defer p.Destroy(ctx) - - assert.NotNil(t, p) - - var lastPID string - lastPID = strconv.Itoa(int(p.Workers()[0].Pid())) - - res, _ := p.Exec(payload.Payload{Body: []byte("hello")}) - assert.Equal(t, lastPID, string(res.Body)) - - for i := 0; i < 10; i++ { - res, err := p.Exec(payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.NotEqual(t, lastPID, string(res.Body)) - lastPID = string(res.Body) - } -} - -func Test_StaticPool_Debug_Worker(t *testing.T) { - ctx := context.Background() - p, err := Initialize( - ctx, - func() *exec.Cmd { return exec.Command("php", "../../tests/client.php", "pid", "pipes") }, - pipe.NewPipeFactory(), - Config{ - Debug: true, - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, - ) - assert.NoError(t, err) - defer p.Destroy(ctx) - - assert.NotNil(t, p) - - assert.Len(t, p.Workers(), 0) - - var lastPID string - res, _ := p.Exec(payload.Payload{Body: []byte("hello")}) - assert.NotEqual(t, lastPID, string(res.Body)) - - assert.Len(t, p.Workers(), 0) - - for i := 0; i < 10; i++ { - assert.Len(t, p.Workers(), 0) - res, err := p.Exec(payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.NotEqual(t, lastPID, string(res.Body)) - lastPID = string(res.Body) - } -} - -// identical to replace but controlled on worker side -func Test_StaticPool_Stop_Worker(t *testing.T) { - ctx := context.Background() - p, err := Initialize( - ctx, - func() *exec.Cmd { return exec.Command("php", "../../tests/client.php", "stop", "pipes") }, - pipe.NewPipeFactory(), - Config{ - NumWorkers: 1, - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, - ) - assert.NoError(t, err) - defer p.Destroy(ctx) - - assert.NotNil(t, p) - - var lastPID string - lastPID = strconv.Itoa(int(p.Workers()[0].Pid())) - - res, err := p.Exec(payload.Payload{Body: []byte("hello")}) - if err != nil { - t.Fatal(err) - } - assert.Equal(t, lastPID, string(res.Body)) - - for i := 0; i < 10; i++ { - res, err := p.Exec(payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.NotEqual(t, lastPID, string(res.Body)) - lastPID = string(res.Body) - } -} - -// identical to replace but controlled on worker side -func Test_Static_Pool_Destroy_And_Close(t *testing.T) { - ctx := context.Background() - p, err := Initialize( - ctx, - func() *exec.Cmd { return exec.Command("php", "../../tests/client.php", "delay", "pipes") }, - pipe.NewPipeFactory(), - Config{ - NumWorkers: 1, - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, - ) - - assert.NotNil(t, p) - assert.NoError(t, err) - - p.Destroy(ctx) - _, err = p.Exec(payload.Payload{Body: []byte("100")}) - assert.Error(t, err) -} - -// identical to replace but controlled on worker side -func Test_Static_Pool_Destroy_And_Close_While_Wait(t *testing.T) { - ctx := context.Background() - p, err := Initialize( - ctx, - func() *exec.Cmd { return exec.Command("php", "../../tests/client.php", "delay", "pipes") }, - pipe.NewPipeFactory(), - Config{ - NumWorkers: 1, - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, - ) - - assert.NotNil(t, p) - assert.NoError(t, err) - - go func() { - _, err := p.Exec(payload.Payload{Body: []byte("100")}) - if err != nil { - t.Errorf("error executing payload: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - p.Destroy(ctx) - _, err = p.Exec(payload.Payload{Body: []byte("100")}) - assert.Error(t, err) -} - -// identical to replace but controlled on worker side -func Test_Static_Pool_Handle_Dead(t *testing.T) { - ctx := context.Background() - p, err := Initialize( - context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../tests/slow-destroy.php", "echo", "pipes") }, - pipe.NewPipeFactory(), - Config{ - NumWorkers: 5, - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, - ) - assert.NoError(t, err) - assert.NotNil(t, p) - - for i := range p.Workers() { - p.Workers()[i].State().Set(internal.StateErrored) - } - - _, err = p.Exec(payload.Payload{Body: []byte("hello")}) - assert.Error(t, err) - p.Destroy(ctx) -} - -// identical to replace but controlled on worker side -func Test_Static_Pool_Slow_Destroy(t *testing.T) { - p, err := Initialize( - context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../tests/slow-destroy.php", "echo", "pipes") }, - pipe.NewPipeFactory(), - Config{ - NumWorkers: 5, - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, - ) - - assert.NoError(t, err) - assert.NotNil(t, p) - - p.Destroy(context.Background()) -} - -func Test_StaticPool_NoFreeWorkers(t *testing.T) { - ctx := context.Background() - block := make(chan struct{}, 1) - - listener := func(event interface{}) { - if ev, ok := event.(events.PoolEvent); ok { - if ev.Event == events.EventNoFreeWorkers { - block <- struct{}{} - } - } - } - - p, err := Initialize( - ctx, - // sleep for the 3 seconds - func() *exec.Cmd { return exec.Command("php", "../../tests/sleep.php", "pipes") }, - pipe.NewPipeFactory(), - Config{ - Debug: false, - NumWorkers: 1, - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - Supervisor: nil, - }, - AddListeners(listener), - ) - assert.NoError(t, err) - assert.NotNil(t, p) - - go func() { - _, _ = p.ExecWithContext(ctx, payload.Payload{Body: []byte("hello")}) - }() - - time.Sleep(time.Second) - res, err := p.ExecWithContext(ctx, payload.Payload{Body: []byte("hello")}) - assert.Error(t, err) - assert.Nil(t, res.Context) - assert.Nil(t, res.Body) - - <-block - - p.Destroy(ctx) -} - -// identical to replace but controlled on worker side -func Test_Static_Pool_WrongCommand1(t *testing.T) { - p, err := Initialize( - context.Background(), - func() *exec.Cmd { return exec.Command("phg", "../../tests/slow-destroy.php", "echo", "pipes") }, - pipe.NewPipeFactory(), - Config{ - NumWorkers: 5, - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, - ) - - assert.Error(t, err) - assert.Nil(t, p) -} - -// identical to replace but controlled on worker side -func Test_Static_Pool_WrongCommand2(t *testing.T) { - p, err := Initialize( - context.Background(), - func() *exec.Cmd { return exec.Command("php", "", "echo", "pipes") }, - pipe.NewPipeFactory(), - Config{ - NumWorkers: 5, - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, - ) - - assert.Error(t, err) - assert.Nil(t, p) -} - -func Benchmark_Pool_Echo(b *testing.B) { - ctx := context.Background() - p, err := Initialize( - ctx, - func() *exec.Cmd { return exec.Command("php", "../../tests/client.php", "echo", "pipes") }, - pipe.NewPipeFactory(), - cfg, - ) - if err != nil { - b.Fatal(err) - } - - b.ResetTimer() - b.ReportAllocs() - for n := 0; n < b.N; n++ { - if _, err := p.Exec(payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - } - } -} - -// -func Benchmark_Pool_Echo_Batched(b *testing.B) { - ctx := context.Background() - p, err := Initialize( - ctx, - func() *exec.Cmd { return exec.Command("php", "../../tests/client.php", "echo", "pipes") }, - pipe.NewPipeFactory(), - Config{ - NumWorkers: uint64(runtime.NumCPU()), - AllocateTimeout: time.Second * 100, - DestroyTimeout: time.Second, - }, - ) - assert.NoError(b, err) - defer p.Destroy(ctx) - - var wg sync.WaitGroup - for i := 0; i < b.N; i++ { - wg.Add(1) - go func() { - defer wg.Done() - if _, err := p.Exec(payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - log.Println(err) - } - }() - } - - wg.Wait() -} - -// -func Benchmark_Pool_Echo_Replaced(b *testing.B) { - ctx := context.Background() - p, err := Initialize( - ctx, - func() *exec.Cmd { return exec.Command("php", "../../tests/client.php", "echo", "pipes") }, - pipe.NewPipeFactory(), - Config{ - NumWorkers: 1, - MaxJobs: 1, - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, - ) - assert.NoError(b, err) - defer p.Destroy(ctx) - b.ResetTimer() - b.ReportAllocs() - - for n := 0; n < b.N; n++ { - if _, err := p.Exec(payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - log.Println(err) - } - } -} diff --git a/pkg/pool/supervisor_pool.go b/pkg/pool/supervisor_pool.go deleted file mode 100755 index 2597b352..00000000 --- a/pkg/pool/supervisor_pool.go +++ /dev/null @@ -1,222 +0,0 @@ -package pool - -import ( - "context" - "sync" - "time" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/internal" - "github.com/spiral/roadrunner/v2/pkg/events" - "github.com/spiral/roadrunner/v2/pkg/payload" - "github.com/spiral/roadrunner/v2/pkg/worker" - "github.com/spiral/roadrunner/v2/tools" -) - -const MB = 1024 * 1024 - -// NSEC_IN_SEC nanoseconds in second -const NSEC_IN_SEC int64 = 1000000000 //nolint:golint,stylecheck - -type Supervised interface { - Pool - // Start used to start watching process for all pool workers - Start() -} - -type supervised struct { - cfg *SupervisorConfig - events events.Handler - pool Pool - stopCh chan struct{} - mu *sync.RWMutex -} - -func supervisorWrapper(pool Pool, events events.Handler, cfg *SupervisorConfig) Supervised { - sp := &supervised{ - cfg: cfg, - events: events, - pool: pool, - mu: &sync.RWMutex{}, - stopCh: make(chan struct{}), - } - - return sp -} - -type ttlExec struct { - err error - p payload.Payload -} - -func (sp *supervised) ExecWithContext(ctx context.Context, rqs payload.Payload) (payload.Payload, error) { - const op = errors.Op("supervised_exec_with_context") - if sp.cfg.ExecTTL == 0 { - return sp.pool.Exec(rqs) - } - - c := make(chan ttlExec, 1) - ctx, cancel := context.WithTimeout(ctx, sp.cfg.ExecTTL) - defer cancel() - go func() { - res, err := sp.pool.ExecWithContext(ctx, rqs) - if err != nil { - c <- ttlExec{ - err: errors.E(op, err), - p: payload.Payload{}, - } - } - - c <- ttlExec{ - err: nil, - p: res, - } - }() - - for { - select { - case <-ctx.Done(): - return payload.Payload{}, errors.E(op, errors.TimeOut, ctx.Err()) - case res := <-c: - if res.err != nil { - return payload.Payload{}, res.err - } - - return res.p, nil - } - } -} - -func (sp *supervised) Exec(p payload.Payload) (payload.Payload, error) { - const op = errors.Op("supervised_exec") - rsp, err := sp.pool.Exec(p) - if err != nil { - return payload.Payload{}, errors.E(op, err) - } - return rsp, nil -} - -func (sp *supervised) GetConfig() interface{} { - return sp.pool.GetConfig() -} - -func (sp *supervised) Workers() (workers []worker.SyncWorker) { - sp.mu.Lock() - defer sp.mu.Unlock() - return sp.pool.Workers() -} - -func (sp *supervised) RemoveWorker(worker worker.SyncWorker) error { - return sp.pool.RemoveWorker(worker) -} - -func (sp *supervised) Destroy(ctx context.Context) { - sp.pool.Destroy(ctx) -} - -func (sp *supervised) Start() { - go func() { - watchTout := time.NewTicker(sp.cfg.WatchTick) - for { - select { - case <-sp.stopCh: - watchTout.Stop() - return - // stop here - case <-watchTout.C: - sp.mu.Lock() - sp.control() - sp.mu.Unlock() - } - } - }() -} - -func (sp *supervised) Stop() { - sp.stopCh <- struct{}{} -} - -func (sp *supervised) control() { - now := time.Now() - const op = errors.Op("supervised_pool_control_tick") - - // THIS IS A COPY OF WORKERS - workers := sp.pool.Workers() - - for i := 0; i < len(workers); i++ { - if workers[i].State().Value() == internal.StateInvalid { - continue - } - - s, err := tools.WorkerProcessState(workers[i]) - if err != nil { - // worker not longer valid for supervision - continue - } - - if sp.cfg.TTL != 0 && now.Sub(workers[i].Created()).Seconds() >= sp.cfg.TTL.Seconds() { - err = sp.pool.RemoveWorker(workers[i]) - if err != nil { - sp.events.Push(events.PoolEvent{Event: events.EventSupervisorError, Payload: errors.E(op, err)}) - return - } - sp.events.Push(events.PoolEvent{Event: events.EventTTL, Payload: workers[i]}) - continue - } - - if sp.cfg.MaxWorkerMemory != 0 && s.MemoryUsage >= sp.cfg.MaxWorkerMemory*MB { - err = sp.pool.RemoveWorker(workers[i]) - if err != nil { - sp.events.Push(events.PoolEvent{Event: events.EventSupervisorError, Payload: errors.E(op, err)}) - return - } - sp.events.Push(events.PoolEvent{Event: events.EventMaxMemory, Payload: workers[i]}) - continue - } - - // firs we check maxWorker idle - if sp.cfg.IdleTTL != 0 { - // then check for the worker state - if workers[i].State().Value() != internal.StateReady { - continue - } - - /* - Calculate idle time - If worker in the StateReady, we read it LastUsed timestamp as UnixNano uint64 - 2. For example maxWorkerIdle is equal to 5sec, then, if (time.Now - LastUsed) > maxWorkerIdle - we are guessing that worker overlap idle time and has to be killed - */ - - // 1610530005534416045 lu - // lu - now = -7811150814 - nanoseconds - // 7.8 seconds - // get last used unix nano - lu := workers[i].State().LastUsed() - // worker not used, skip - if lu == 0 { - continue - } - - // convert last used to unixNano and sub time.now to seconds - // negative number, because lu always in the past, except for the `back to the future` :) - res := ((int64(lu) - now.UnixNano()) / NSEC_IN_SEC) * -1 - - // maxWorkerIdle more than diff between now and last used - // for example: - // After exec worker goes to the rest - // And resting for the 5 seconds - // IdleTTL is 1 second. - // After the control check, res will be 5, idle is 1 - // 5 - 1 = 4, more than 0, YOU ARE FIRED (removed). Done. - if int64(sp.cfg.IdleTTL.Seconds())-res <= 0 { - err = sp.pool.RemoveWorker(workers[i]) - if err != nil { - sp.events.Push(events.PoolEvent{Event: events.EventSupervisorError, Payload: errors.E(op, err)}) - return - } - sp.events.Push(events.PoolEvent{Event: events.EventIdleTTL, Payload: workers[i]}) - } - } - } -} diff --git a/pkg/pool/supervisor_test.go b/pkg/pool/supervisor_test.go deleted file mode 100644 index c67d5d91..00000000 --- a/pkg/pool/supervisor_test.go +++ /dev/null @@ -1,248 +0,0 @@ -package pool - -import ( - "context" - "os/exec" - "testing" - "time" - - "github.com/spiral/roadrunner/v2/pkg/events" - "github.com/spiral/roadrunner/v2/pkg/payload" - "github.com/spiral/roadrunner/v2/pkg/transport/pipe" - "github.com/spiral/roadrunner/v2/tools" - "github.com/stretchr/testify/assert" -) - -var cfgSupervised = Config{ - NumWorkers: uint64(1), - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - Supervisor: &SupervisorConfig{ - WatchTick: 1 * time.Second, - TTL: 100 * time.Second, - IdleTTL: 100 * time.Second, - ExecTTL: 100 * time.Second, - MaxWorkerMemory: 100, - }, -} - -func TestSupervisedPool_Exec(t *testing.T) { - ctx := context.Background() - p, err := Initialize( - ctx, - func() *exec.Cmd { return exec.Command("php", "../../tests/memleak.php", "pipes") }, - pipe.NewPipeFactory(), - cfgSupervised, - ) - - assert.NoError(t, err) - assert.NotNil(t, p) - stopCh := make(chan struct{}) - defer p.Destroy(context.Background()) - - go func() { - for { - select { - case <-stopCh: - return - default: - workers := p.Workers() - if len(workers) > 0 { - s, err := tools.WorkerProcessState(workers[0]) - assert.NoError(t, err) - assert.NotNil(t, s) - // since this is soft limit, double max memory limit watch - if (s.MemoryUsage / MB) > cfgSupervised.Supervisor.MaxWorkerMemory*2 { - assert.Fail(t, "max memory reached") - } - } - } - } - }() - - for i := 0; i < 100; i++ { - time.Sleep(time.Millisecond * 50) - _, err = p.Exec(payload.Payload{ - Context: []byte(""), - Body: []byte("foo"), - }) - assert.NoError(t, err) - } - - stopCh <- struct{}{} -} - -func TestSupervisedPool_ExecTTL_TimedOut(t *testing.T) { - var cfgExecTTL = Config{ - NumWorkers: uint64(1), - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - Supervisor: &SupervisorConfig{ - WatchTick: 1 * time.Second, - TTL: 100 * time.Second, - IdleTTL: 100 * time.Second, - ExecTTL: 1 * time.Second, - MaxWorkerMemory: 100, - }, - } - ctx := context.Background() - p, err := Initialize( - ctx, - func() *exec.Cmd { return exec.Command("php", "../../tests/sleep.php", "pipes") }, - pipe.NewPipeFactory(), - cfgExecTTL, - ) - - assert.NoError(t, err) - assert.NotNil(t, p) - defer p.Destroy(context.Background()) - - pid := p.Workers()[0].Pid() - - resp, err := p.ExecWithContext(context.Background(), payload.Payload{ - Context: []byte(""), - Body: []byte("foo"), - }) - - assert.Error(t, err) - assert.Empty(t, resp.Body) - assert.Empty(t, resp.Context) - - time.Sleep(time.Second * 1) - // should be new worker with new pid - assert.NotEqual(t, pid, p.Workers()[0].Pid()) -} - -func TestSupervisedPool_Idle(t *testing.T) { - var cfgExecTTL = Config{ - NumWorkers: uint64(1), - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - Supervisor: &SupervisorConfig{ - WatchTick: 1 * time.Second, - TTL: 100 * time.Second, - IdleTTL: 1 * time.Second, - ExecTTL: 100 * time.Second, - MaxWorkerMemory: 100, - }, - } - ctx := context.Background() - p, err := Initialize( - ctx, - func() *exec.Cmd { return exec.Command("php", "../../tests/sleep.php", "pipes") }, - pipe.NewPipeFactory(), - cfgExecTTL, - ) - - assert.NoError(t, err) - assert.NotNil(t, p) - - pid := p.Workers()[0].Pid() - - resp, err := p.ExecWithContext(context.Background(), payload.Payload{ - Context: []byte(""), - Body: []byte("foo"), - }) - - assert.Nil(t, err) - assert.Empty(t, resp.Body) - assert.Empty(t, resp.Context) - - time.Sleep(time.Second * 5) - // should be new worker with new pid - assert.NotEqual(t, pid, p.Workers()[0].Pid()) - p.Destroy(context.Background()) -} - -func TestSupervisedPool_ExecTTL_OK(t *testing.T) { - var cfgExecTTL = Config{ - NumWorkers: uint64(1), - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - Supervisor: &SupervisorConfig{ - WatchTick: 1 * time.Second, - TTL: 100 * time.Second, - IdleTTL: 100 * time.Second, - ExecTTL: 4 * time.Second, - MaxWorkerMemory: 100, - }, - } - ctx := context.Background() - p, err := Initialize( - ctx, - func() *exec.Cmd { return exec.Command("php", "../../tests/sleep.php", "pipes") }, - pipe.NewPipeFactory(), - cfgExecTTL, - ) - - assert.NoError(t, err) - assert.NotNil(t, p) - defer p.Destroy(context.Background()) - - pid := p.Workers()[0].Pid() - - time.Sleep(time.Millisecond * 100) - resp, err := p.Exec(payload.Payload{ - Context: []byte(""), - Body: []byte("foo"), - }) - - assert.NoError(t, err) - assert.Empty(t, resp.Body) - assert.Empty(t, resp.Context) - - time.Sleep(time.Second * 1) - // should be the same pid - assert.Equal(t, pid, p.Workers()[0].Pid()) -} - -func TestSupervisedPool_MaxMemoryReached(t *testing.T) { - var cfgExecTTL = Config{ - NumWorkers: uint64(1), - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - Supervisor: &SupervisorConfig{ - WatchTick: 1 * time.Second, - TTL: 100 * time.Second, - IdleTTL: 100 * time.Second, - ExecTTL: 4 * time.Second, - MaxWorkerMemory: 1, - }, - } - - block := make(chan struct{}, 1) - listener := func(event interface{}) { - if ev, ok := event.(events.PoolEvent); ok { - if ev.Event == events.EventMaxMemory { - block <- struct{}{} - } - } - } - - // constructed - // max memory - // constructed - ctx := context.Background() - p, err := Initialize( - ctx, - func() *exec.Cmd { return exec.Command("php", "../../tests/memleak.php", "pipes") }, - pipe.NewPipeFactory(), - cfgExecTTL, - AddListeners(listener), - ) - - assert.NoError(t, err) - assert.NotNil(t, p) - - resp, err := p.Exec(payload.Payload{ - Context: []byte(""), - Body: []byte("foo"), - }) - - assert.NoError(t, err) - assert.Empty(t, resp.Body) - assert.Empty(t, resp.Context) - - <-block - p.Destroy(context.Background()) -} diff --git a/pkg/transport/interface.go b/pkg/transport/interface.go deleted file mode 100644 index 299ac95f..00000000 --- a/pkg/transport/interface.go +++ /dev/null @@ -1,21 +0,0 @@ -package transport - -import ( - "context" - "os/exec" - - "github.com/spiral/roadrunner/v2/pkg/events" - "github.com/spiral/roadrunner/v2/pkg/worker" -) - -// Factory is responsible of wrapping given command into tasks WorkerProcess. -type Factory interface { - // SpawnWorkerWithContext creates new WorkerProcess process based on given command with context. - // Process must not be started. - SpawnWorkerWithTimeout(context.Context, *exec.Cmd, ...events.Listener) (*worker.Process, error) - // SpawnWorker creates new WorkerProcess process based on given command. - // Process must not be started. - SpawnWorker(*exec.Cmd, ...events.Listener) (*worker.Process, error) - // Close the factory and underlying connections. - Close() error -} diff --git a/pkg/transport/pipe/pipe_factory.go b/pkg/transport/pipe/pipe_factory.go deleted file mode 100755 index dd7c5841..00000000 --- a/pkg/transport/pipe/pipe_factory.go +++ /dev/null @@ -1,162 +0,0 @@ -package pipe - -import ( - "context" - "os/exec" - - "github.com/spiral/errors" - "github.com/spiral/goridge/v3/pkg/pipe" - "github.com/spiral/roadrunner/v2/internal" - "github.com/spiral/roadrunner/v2/pkg/events" - "github.com/spiral/roadrunner/v2/pkg/worker" - "go.uber.org/multierr" -) - -// Factory connects to stack using standard -// streams (STDIN, STDOUT pipes). -type Factory struct{} - -// NewPipeFactory returns new factory instance and starts -// listening -func NewPipeFactory() *Factory { - return &Factory{} -} - -type SpawnResult struct { - w *worker.Process - err error -} - -// SpawnWorker creates new Process and connects it to goridge relay, -// method Wait() must be handled on level above. -func (f *Factory) SpawnWorkerWithTimeout(ctx context.Context, cmd *exec.Cmd, listeners ...events.Listener) (*worker.Process, error) { - c := make(chan SpawnResult) - const op = errors.Op("factory_spawn_worker_with_timeout") - go func() { - w, err := worker.InitBaseWorker(cmd, worker.AddListeners(listeners...)) - if err != nil { - c <- SpawnResult{ - w: nil, - err: errors.E(op, err), - } - return - } - - // TODO why out is in? - in, err := cmd.StdoutPipe() - if err != nil { - c <- SpawnResult{ - w: nil, - err: errors.E(op, err), - } - return - } - - // TODO why in is out? - out, err := cmd.StdinPipe() - if err != nil { - c <- SpawnResult{ - w: nil, - err: errors.E(op, err), - } - return - } - - // Init new PIPE relay - relay := pipe.NewPipeRelay(in, out) - w.AttachRelay(relay) - - // Start the worker - err = w.Start() - if err != nil { - c <- SpawnResult{ - w: nil, - err: errors.E(op, err), - } - return - } - - // errors bundle - pid, err := internal.FetchPID(relay) - if pid != w.Pid() || err != nil { - err = multierr.Combine( - err, - w.Kill(), - w.Wait(), - ) - c <- SpawnResult{ - w: nil, - err: errors.E(op, err), - } - return - } - - // everything ok, set ready state - w.State().Set(internal.StateReady) - - // return worker - c <- SpawnResult{ - w: w, - err: nil, - } - }() - - select { - case <-ctx.Done(): - return nil, ctx.Err() - case res := <-c: - if res.err != nil { - return nil, res.err - } - return res.w, nil - } -} - -func (f *Factory) SpawnWorker(cmd *exec.Cmd, listeners ...events.Listener) (*worker.Process, error) { - const op = errors.Op("factory_spawn_worker") - w, err := worker.InitBaseWorker(cmd, worker.AddListeners(listeners...)) - if err != nil { - return nil, errors.E(op, err) - } - - // TODO why out is in? - in, err := cmd.StdoutPipe() - if err != nil { - return nil, errors.E(op, err) - } - - // TODO why in is out? - out, err := cmd.StdinPipe() - if err != nil { - return nil, errors.E(op, err) - } - - // Init new PIPE relay - relay := pipe.NewPipeRelay(in, out) - w.AttachRelay(relay) - - // Start the worker - err = w.Start() - if err != nil { - return nil, errors.E(op, err) - } - - // errors bundle - if pid, err := internal.FetchPID(relay); pid != w.Pid() { - err = multierr.Combine( - err, - w.Kill(), - w.Wait(), - ) - return nil, errors.E(op, err) - } - - // everything ok, set ready state - w.State().Set(internal.StateReady) - return w, nil -} - -// Close the factory. -func (f *Factory) Close() error { - return nil -} diff --git a/pkg/transport/pipe/pipe_factory_spawn_test.go b/pkg/transport/pipe/pipe_factory_spawn_test.go deleted file mode 100644 index d4949c82..00000000 --- a/pkg/transport/pipe/pipe_factory_spawn_test.go +++ /dev/null @@ -1,456 +0,0 @@ -package pipe - -import ( - "os/exec" - "strings" - "sync" - "testing" - "time" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/internal" - "github.com/spiral/roadrunner/v2/pkg/events" - "github.com/spiral/roadrunner/v2/pkg/payload" - "github.com/spiral/roadrunner/v2/pkg/worker" - "github.com/stretchr/testify/assert" -) - -func Test_GetState2(t *testing.T) { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - - w, err := NewPipeFactory().SpawnWorker(cmd) - go func() { - assert.NoError(t, w.Wait()) - assert.Equal(t, internal.StateStopped, w.State().Value()) - }() - - assert.NoError(t, err) - assert.NotNil(t, w) - - assert.Equal(t, internal.StateReady, w.State().Value()) - assert.NoError(t, w.Stop()) -} - -func Test_Kill2(t *testing.T) { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - - w, err := NewPipeFactory().SpawnWorker(cmd) - wg := &sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - assert.Error(t, w.Wait()) - assert.Equal(t, internal.StateErrored, w.State().Value()) - }() - - assert.NoError(t, err) - assert.NotNil(t, w) - - assert.Equal(t, internal.StateReady, w.State().Value()) - err = w.Kill() - if err != nil { - t.Errorf("error killing the Process: error %v", err) - } - wg.Wait() -} - -func Test_Pipe_Start2(t *testing.T) { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - - w, err := NewPipeFactory().SpawnWorker(cmd) - assert.NoError(t, err) - assert.NotNil(t, w) - - go func() { - assert.NoError(t, w.Wait()) - }() - - assert.NoError(t, w.Stop()) -} - -func Test_Pipe_StartError2(t *testing.T) { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - err := cmd.Start() - if err != nil { - t.Errorf("error running the command: error %v", err) - } - - w, err := NewPipeFactory().SpawnWorker(cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Pipe_PipeError3(t *testing.T) { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - _, err := cmd.StdinPipe() - if err != nil { - t.Errorf("error creating the STDIN pipe: error %v", err) - } - - w, err := NewPipeFactory().SpawnWorker(cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Pipe_PipeError4(t *testing.T) { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - _, err := cmd.StdinPipe() - if err != nil { - t.Errorf("error creating the STDIN pipe: error %v", err) - } - - w, err := NewPipeFactory().SpawnWorker(cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Pipe_Failboot2(t *testing.T) { - cmd := exec.Command("php", "../../../tests/failboot.php") - w, err := NewPipeFactory().SpawnWorker(cmd) - - assert.Nil(t, w) - assert.Error(t, err) - assert.Contains(t, err.Error(), "failboot") -} - -func Test_Pipe_Invalid2(t *testing.T) { - cmd := exec.Command("php", "../../../tests/invalid.php") - w, err := NewPipeFactory().SpawnWorker(cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Pipe_Echo2(t *testing.T) { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - w, err := NewPipeFactory().SpawnWorker(cmd) - if err != nil { - t.Fatal(err) - } - defer func() { - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - res, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Test_Pipe_Broken2(t *testing.T) { - cmd := exec.Command("php", "../../../tests/client.php", "broken", "pipes") - w, err := NewPipeFactory().SpawnWorker(cmd) - if err != nil { - t.Fatal(err) - } - defer func() { - time.Sleep(time.Second) - err = w.Stop() - assert.Error(t, err) - }() - - sw := worker.From(w) - - res, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - - assert.Error(t, err) - assert.Nil(t, res.Body) - assert.Nil(t, res.Context) -} - -func Benchmark_Pipe_SpawnWorker_Stop2(b *testing.B) { - f := NewPipeFactory() - for n := 0; n < b.N; n++ { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - w, _ := f.SpawnWorker(cmd) - go func() { - if w.Wait() != nil { - b.Fail() - } - }() - - err := w.Stop() - if err != nil { - b.Errorf("error stopping the worker: error %v", err) - } - } -} - -func Benchmark_Pipe_Worker_ExecEcho2(b *testing.B) { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - - w, _ := NewPipeFactory().SpawnWorker(cmd) - sw := worker.From(w) - - b.ReportAllocs() - b.ResetTimer() - go func() { - err := w.Wait() - if err != nil { - b.Errorf("error waiting the worker: error %v", err) - } - }() - defer func() { - err := w.Stop() - if err != nil { - b.Errorf("error stopping the worker: error %v", err) - } - }() - - for n := 0; n < b.N; n++ { - if _, err := sw.Exec(payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - } - } -} - -func Benchmark_Pipe_Worker_ExecEcho4(b *testing.B) { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - w, err := NewPipeFactory().SpawnWorker(cmd) - if err != nil { - b.Fatal(err) - } - - defer func() { - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - for n := 0; n < b.N; n++ { - if _, err := sw.Exec(payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - } - } -} - -func Benchmark_Pipe_Worker_ExecEchoWithoutContext2(b *testing.B) { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - w, err := NewPipeFactory().SpawnWorker(cmd) - if err != nil { - b.Fatal(err) - } - - defer func() { - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - for n := 0; n < b.N; n++ { - if _, err := sw.Exec(payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - } - } -} - -func Test_Echo2(t *testing.T) { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - - w, err := NewPipeFactory().SpawnWorker(cmd) - if err != nil { - t.Fatal(err) - } - - sw := worker.From(w) - - go func() { - assert.NoError(t, sw.Wait()) - }() - defer func() { - err := sw.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - res, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - - assert.Nil(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Test_BadPayload2(t *testing.T) { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - - w, _ := NewPipeFactory().SpawnWorker(cmd) - - sw := worker.From(w) - - go func() { - assert.NoError(t, sw.Wait()) - }() - defer func() { - err := sw.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - res, err := sw.Exec(payload.Payload{}) - - assert.Error(t, err) - assert.Nil(t, res.Body) - assert.Nil(t, res.Context) - - assert.Contains(t, err.Error(), "payload can not be empty") -} - -func Test_String2(t *testing.T) { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - - w, _ := NewPipeFactory().SpawnWorker(cmd) - go func() { - assert.NoError(t, w.Wait()) - }() - defer func() { - err := w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - assert.Contains(t, w.String(), "php ../../../tests/client.php echo pipes") - assert.Contains(t, w.String(), "ready") - assert.Contains(t, w.String(), "numExecs: 0") -} - -func Test_Echo_Slow2(t *testing.T) { - cmd := exec.Command("php", "../../../tests/slow-client.php", "echo", "pipes", "10", "10") - - w, _ := NewPipeFactory().SpawnWorker(cmd) - go func() { - assert.NoError(t, w.Wait()) - }() - defer func() { - err := w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - res, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - - assert.Nil(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Test_Broken2(t *testing.T) { - cmd := exec.Command("php", "../../../tests/client.php", "broken", "pipes") - data := "" - mu := &sync.Mutex{} - listener := func(event interface{}) { - if wev, ok := event.(events.WorkerEvent); ok { - mu.Lock() - data = string(wev.Payload.([]byte)) - mu.Unlock() - } - } - - w, err := NewPipeFactory().SpawnWorker(cmd, listener) - if err != nil { - t.Fatal(err) - } - - sw := worker.From(w) - - res, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - assert.NotNil(t, err) - assert.Nil(t, res.Body) - assert.Nil(t, res.Context) - - time.Sleep(time.Second * 3) - mu.Lock() - if strings.ContainsAny(data, "undefined_function()") == false { - t.Fail() - } - mu.Unlock() - assert.Error(t, w.Stop()) -} - -func Test_Error2(t *testing.T) { - cmd := exec.Command("php", "../../../tests/client.php", "error", "pipes") - - w, _ := NewPipeFactory().SpawnWorker(cmd) - go func() { - assert.NoError(t, w.Wait()) - }() - - defer func() { - err := w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - res, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - assert.NotNil(t, err) - assert.Nil(t, res.Body) - assert.Nil(t, res.Context) - - if errors.Is(errors.SoftJob, err) == false { - t.Fatal("error should be of type errors.ErrSoftJob") - } - assert.Contains(t, err.Error(), "hello") -} - -func Test_NumExecs2(t *testing.T) { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - - w, _ := NewPipeFactory().SpawnWorker(cmd) - go func() { - assert.NoError(t, w.Wait()) - }() - defer func() { - err := w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - _, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - if err != nil { - t.Errorf("fail to execute payload: error %v", err) - } - assert.Equal(t, uint64(1), w.State().NumExecs()) - - _, err = sw.Exec(payload.Payload{Body: []byte("hello")}) - if err != nil { - t.Errorf("fail to execute payload: error %v", err) - } - assert.Equal(t, uint64(2), w.State().NumExecs()) - - _, err = sw.Exec(payload.Payload{Body: []byte("hello")}) - if err != nil { - t.Errorf("fail to execute payload: error %v", err) - } - assert.Equal(t, uint64(3), w.State().NumExecs()) -} diff --git a/pkg/transport/pipe/pipe_factory_test.go b/pkg/transport/pipe/pipe_factory_test.go deleted file mode 100755 index 38166b85..00000000 --- a/pkg/transport/pipe/pipe_factory_test.go +++ /dev/null @@ -1,478 +0,0 @@ -package pipe - -import ( - "context" - "os/exec" - "strings" - "sync" - "testing" - "time" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/internal" - "github.com/spiral/roadrunner/v2/pkg/events" - "github.com/spiral/roadrunner/v2/pkg/payload" - "github.com/spiral/roadrunner/v2/pkg/worker" - "github.com/stretchr/testify/assert" -) - -func Test_GetState(t *testing.T) { - ctx := context.Background() - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - - w, err := NewPipeFactory().SpawnWorkerWithTimeout(ctx, cmd) - go func() { - assert.NoError(t, w.Wait()) - assert.Equal(t, internal.StateStopped, w.State().Value()) - }() - - assert.NoError(t, err) - assert.NotNil(t, w) - - assert.Equal(t, internal.StateReady, w.State().Value()) - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } -} - -func Test_Kill(t *testing.T) { - ctx := context.Background() - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - - w, err := NewPipeFactory().SpawnWorkerWithTimeout(ctx, cmd) - wg := &sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - assert.Error(t, w.Wait()) - assert.Equal(t, internal.StateErrored, w.State().Value()) - }() - - assert.NoError(t, err) - assert.NotNil(t, w) - - assert.Equal(t, internal.StateReady, w.State().Value()) - err = w.Kill() - if err != nil { - t.Errorf("error killing the Process: error %v", err) - } - wg.Wait() -} - -func Test_Pipe_Start(t *testing.T) { - ctx := context.Background() - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - - w, err := NewPipeFactory().SpawnWorkerWithTimeout(ctx, cmd) - assert.NoError(t, err) - assert.NotNil(t, w) - - go func() { - assert.NoError(t, w.Wait()) - }() - - assert.NoError(t, w.Stop()) -} - -func Test_Pipe_StartError(t *testing.T) { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - err := cmd.Start() - if err != nil { - t.Errorf("error running the command: error %v", err) - } - - ctx := context.Background() - w, err := NewPipeFactory().SpawnWorkerWithTimeout(ctx, cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Pipe_PipeError(t *testing.T) { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - _, err := cmd.StdinPipe() - if err != nil { - t.Errorf("error creating the STDIN pipe: error %v", err) - } - - ctx := context.Background() - w, err := NewPipeFactory().SpawnWorkerWithTimeout(ctx, cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Pipe_PipeError2(t *testing.T) { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - _, err := cmd.StdinPipe() - if err != nil { - t.Errorf("error creating the STDIN pipe: error %v", err) - } - - ctx := context.Background() - w, err := NewPipeFactory().SpawnWorkerWithTimeout(ctx, cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Pipe_Failboot(t *testing.T) { - cmd := exec.Command("php", "../../../tests/failboot.php") - ctx := context.Background() - w, err := NewPipeFactory().SpawnWorkerWithTimeout(ctx, cmd) - - assert.Nil(t, w) - assert.Error(t, err) - assert.Contains(t, err.Error(), "failboot") -} - -func Test_Pipe_Invalid(t *testing.T) { - cmd := exec.Command("php", "../../../tests/invalid.php") - ctx := context.Background() - w, err := NewPipeFactory().SpawnWorkerWithTimeout(ctx, cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Pipe_Echo(t *testing.T) { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - ctx := context.Background() - w, err := NewPipeFactory().SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - t.Fatal(err) - } - defer func() { - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - res, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Test_Pipe_Broken(t *testing.T) { - cmd := exec.Command("php", "../../../tests/client.php", "broken", "pipes") - ctx := context.Background() - w, err := NewPipeFactory().SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - t.Fatal(err) - } - defer func() { - time.Sleep(time.Second) - err = w.Stop() - assert.Error(t, err) - }() - - sw := worker.From(w) - - res, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - - assert.Error(t, err) - assert.Nil(t, res.Body) - assert.Nil(t, res.Context) -} - -func Benchmark_Pipe_SpawnWorker_Stop(b *testing.B) { - f := NewPipeFactory() - for n := 0; n < b.N; n++ { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - w, _ := f.SpawnWorkerWithTimeout(context.Background(), cmd) - go func() { - if w.Wait() != nil { - b.Fail() - } - }() - - err := w.Stop() - if err != nil { - b.Errorf("error stopping the worker: error %v", err) - } - } -} - -func Benchmark_Pipe_Worker_ExecEcho(b *testing.B) { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - - w, _ := NewPipeFactory().SpawnWorkerWithTimeout(context.Background(), cmd) - sw := worker.From(w) - - b.ReportAllocs() - b.ResetTimer() - go func() { - err := w.Wait() - if err != nil { - b.Errorf("error waiting the worker: error %v", err) - } - }() - defer func() { - err := w.Stop() - if err != nil { - b.Errorf("error stopping the worker: error %v", err) - } - }() - - for n := 0; n < b.N; n++ { - if _, err := sw.Exec(payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - } - } -} - -func Benchmark_Pipe_Worker_ExecEcho3(b *testing.B) { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - ctx := context.Background() - w, err := NewPipeFactory().SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - b.Fatal(err) - } - - defer func() { - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - for n := 0; n < b.N; n++ { - if _, err := sw.Exec(payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - } - } -} - -func Benchmark_Pipe_Worker_ExecEchoWithoutContext(b *testing.B) { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - ctx := context.Background() - w, err := NewPipeFactory().SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - b.Fatal(err) - } - - defer func() { - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - for n := 0; n < b.N; n++ { - if _, err := sw.Exec(payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - } - } -} - -func Test_Echo(t *testing.T) { - ctx := context.Background() - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - - w, err := NewPipeFactory().SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - t.Fatal(err) - } - - sw := worker.From(w) - go func() { - assert.NoError(t, sw.Wait()) - }() - defer func() { - err := sw.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - res, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - - assert.Nil(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Test_BadPayload(t *testing.T) { - ctx := context.Background() - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - - w, _ := NewPipeFactory().SpawnWorkerWithTimeout(ctx, cmd) - - sw := worker.From(w) - - go func() { - assert.NoError(t, sw.Wait()) - }() - defer func() { - err := sw.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - res, err := sw.Exec(payload.Payload{}) - - assert.Error(t, err) - assert.Nil(t, res.Body) - assert.Nil(t, res.Context) - - assert.Contains(t, err.Error(), "payload can not be empty") -} - -func Test_String(t *testing.T) { - ctx := context.Background() - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - - w, _ := NewPipeFactory().SpawnWorkerWithTimeout(ctx, cmd) - go func() { - assert.NoError(t, w.Wait()) - }() - defer func() { - err := w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - assert.Contains(t, w.String(), "php ../../../tests/client.php echo pipes") - assert.Contains(t, w.String(), "ready") - assert.Contains(t, w.String(), "numExecs: 0") -} - -func Test_Echo_Slow(t *testing.T) { - ctx := context.Background() - cmd := exec.Command("php", "../../../tests/slow-client.php", "echo", "pipes", "10", "10") - - w, _ := NewPipeFactory().SpawnWorkerWithTimeout(ctx, cmd) - go func() { - assert.NoError(t, w.Wait()) - }() - defer func() { - err := w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - res, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - - assert.Nil(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Test_Broken(t *testing.T) { - ctx := context.Background() - cmd := exec.Command("php", "../../../tests/client.php", "broken", "pipes") - data := "" - mu := &sync.Mutex{} - listener := func(event interface{}) { - if wev, ok := event.(events.WorkerEvent); ok { - mu.Lock() - data = string(wev.Payload.([]byte)) - mu.Unlock() - } - } - - w, err := NewPipeFactory().SpawnWorkerWithTimeout(ctx, cmd, listener) - if err != nil { - t.Fatal(err) - } - - sw := worker.From(w) - - res, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - assert.NotNil(t, err) - assert.Nil(t, res.Body) - assert.Nil(t, res.Context) - - time.Sleep(time.Second * 3) - mu.Lock() - if strings.ContainsAny(data, "undefined_function()") == false { - t.Fail() - } - mu.Unlock() - assert.Error(t, w.Stop()) -} - -func Test_Error(t *testing.T) { - ctx := context.Background() - cmd := exec.Command("php", "../../../tests/client.php", "error", "pipes") - - w, _ := NewPipeFactory().SpawnWorkerWithTimeout(ctx, cmd) - go func() { - assert.NoError(t, w.Wait()) - }() - - defer func() { - err := w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - res, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - assert.NotNil(t, err) - assert.Nil(t, res.Body) - assert.Nil(t, res.Context) - - if errors.Is(errors.SoftJob, err) == false { - t.Fatal("error should be of type errors.ErrSoftJob") - } - assert.Contains(t, err.Error(), "hello") -} - -func Test_NumExecs(t *testing.T) { - ctx := context.Background() - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - - w, _ := NewPipeFactory().SpawnWorkerWithTimeout(ctx, cmd) - go func() { - assert.NoError(t, w.Wait()) - }() - defer func() { - err := w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - _, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - if err != nil { - t.Errorf("fail to execute payload: error %v", err) - } - assert.Equal(t, uint64(1), w.State().NumExecs()) - - _, err = sw.Exec(payload.Payload{Body: []byte("hello")}) - if err != nil { - t.Errorf("fail to execute payload: error %v", err) - } - assert.Equal(t, uint64(2), w.State().NumExecs()) - - _, err = sw.Exec(payload.Payload{Body: []byte("hello")}) - if err != nil { - t.Errorf("fail to execute payload: error %v", err) - } - assert.Equal(t, uint64(3), w.State().NumExecs()) -} diff --git a/pkg/transport/socket/socket_factory.go b/pkg/transport/socket/socket_factory.go deleted file mode 100755 index ccd2b0bf..00000000 --- a/pkg/transport/socket/socket_factory.go +++ /dev/null @@ -1,228 +0,0 @@ -package socket - -import ( - "context" - "net" - "os/exec" - "sync" - "time" - - "github.com/shirou/gopsutil/process" - "github.com/spiral/errors" - "github.com/spiral/goridge/v3/interfaces/relay" - "github.com/spiral/goridge/v3/pkg/socket" - "github.com/spiral/roadrunner/v2/internal" - "github.com/spiral/roadrunner/v2/pkg/events" - "github.com/spiral/roadrunner/v2/pkg/worker" - - "go.uber.org/multierr" - "golang.org/x/sync/errgroup" -) - -// Factory connects to external stack using socket server. -type Factory struct { - // listens for incoming connections from underlying processes - ls net.Listener - - // relay connection timeout - tout time.Duration - - // sockets which are waiting for process association - relays sync.Map - - ErrCh chan error -} - -// todo: review - -// NewSocketServer returns Factory attached to a given socket listener. -// tout specifies for how long factory should serve for incoming relay connection -func NewSocketServer(ls net.Listener, tout time.Duration) *Factory { - f := &Factory{ - ls: ls, - tout: tout, - relays: sync.Map{}, - ErrCh: make(chan error, 10), - } - - // Be careful - // https://github.com/go101/go101/wiki/About-memory-ordering-guarantees-made-by-atomic-operations-in-Go - // https://github.com/golang/go/issues/5045 - go func() { - f.ErrCh <- f.listen() - }() - - return f -} - -// blocking operation, returns an error -func (f *Factory) listen() error { - errGr := &errgroup.Group{} - errGr.Go(func() error { - for { - conn, err := f.ls.Accept() - if err != nil { - return err - } - - rl := socket.NewSocketRelay(conn) - pid, err := internal.FetchPID(rl) - if err != nil { - return err - } - - f.attachRelayToPid(pid, rl) - } - }) - - return errGr.Wait() -} - -type socketSpawn struct { - w *worker.Process - err error -} - -// SpawnWorker creates Process and connects it to appropriate relay or returns error -func (f *Factory) SpawnWorkerWithTimeout(ctx context.Context, cmd *exec.Cmd, listeners ...events.Listener) (*worker.Process, error) { - const op = errors.Op("factory_spawn_worker_with_timeout") - c := make(chan socketSpawn) - go func() { - ctx, cancel := context.WithTimeout(ctx, f.tout) - defer cancel() - w, err := worker.InitBaseWorker(cmd, worker.AddListeners(listeners...)) - if err != nil { - c <- socketSpawn{ - w: nil, - err: err, - } - return - } - - err = w.Start() - if err != nil { - c <- socketSpawn{ - w: nil, - err: errors.E(op, err), - } - return - } - - rl, err := f.findRelayWithContext(ctx, w) - if err != nil { - err = multierr.Combine( - err, - w.Kill(), - w.Wait(), - ) - - c <- socketSpawn{ - w: nil, - err: errors.E(op, err), - } - return - } - - w.AttachRelay(rl) - w.State().Set(internal.StateReady) - - c <- socketSpawn{ - w: w, - err: nil, - } - }() - - select { - case <-ctx.Done(): - return nil, ctx.Err() - case res := <-c: - if res.err != nil { - return nil, res.err - } - - return res.w, nil - } -} - -func (f *Factory) SpawnWorker(cmd *exec.Cmd, listeners ...events.Listener) (*worker.Process, error) { - const op = errors.Op("factory_spawn_worker") - w, err := worker.InitBaseWorker(cmd, worker.AddListeners(listeners...)) - if err != nil { - return nil, err - } - - err = w.Start() - if err != nil { - return nil, errors.E(op, err) - } - - rl, err := f.findRelay(w) - if err != nil { - err = multierr.Combine( - err, - w.Kill(), - w.Wait(), - ) - return nil, err - } - - w.AttachRelay(rl) - w.State().Set(internal.StateReady) - - return w, nil -} - -// Close socket factory and underlying socket connection. -func (f *Factory) Close() error { - return f.ls.Close() -} - -// waits for Process to connect over socket and returns associated relay of timeout -func (f *Factory) findRelayWithContext(ctx context.Context, w worker.BaseProcess) (*socket.Relay, error) { - ticker := time.NewTicker(time.Millisecond * 100) - for { - select { - case <-ctx.Done(): - return nil, ctx.Err() - case <-ticker.C: - _, err := process.NewProcess(int32(w.Pid())) - if err != nil { - return nil, err - } - default: - tmp, ok := f.relays.Load(w.Pid()) - if !ok { - continue - } - return tmp.(*socket.Relay), nil - } - } -} - -func (f *Factory) findRelay(w worker.BaseProcess) (*socket.Relay, error) { - const op = errors.Op("factory_find_relay") - // poll every 1ms for the relay - pollDone := time.NewTimer(f.tout) - for { - select { - case <-pollDone.C: - return nil, errors.E(op, errors.Str("relay timeout")) - default: - tmp, ok := f.relays.Load(w.Pid()) - if !ok { - continue - } - return tmp.(*socket.Relay), nil - } - } -} - -// chan to store relay associated with specific pid -func (f *Factory) attachRelayToPid(pid int64, relay relay.Relay) { - f.relays.Store(pid, relay) -} - -// deletes relay chan associated with specific pid -func (f *Factory) removeRelayFromPid(pid int64) { - f.relays.Delete(pid) -} diff --git a/pkg/transport/socket/socket_factory_spawn_test.go b/pkg/transport/socket/socket_factory_spawn_test.go deleted file mode 100644 index 0e29e7d2..00000000 --- a/pkg/transport/socket/socket_factory_spawn_test.go +++ /dev/null @@ -1,489 +0,0 @@ -package socket - -import ( - "net" - "os/exec" - "sync" - "syscall" - "testing" - "time" - - "github.com/spiral/roadrunner/v2/pkg/payload" - "github.com/spiral/roadrunner/v2/pkg/worker" - "github.com/stretchr/testify/assert" -) - -func Test_Tcp_Start2(t *testing.T) { - ls, err := net.Listen("tcp", "localhost:9007") - if assert.NoError(t, err) { - defer func() { - err := ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/client.php", "echo", "tcp") - - w, err := NewSocketServer(ls, time.Minute).SpawnWorker(cmd) - assert.NoError(t, err) - assert.NotNil(t, w) - - go func() { - assert.NoError(t, w.Wait()) - }() - - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } -} - -func Test_Tcp_StartCloseFactory2(t *testing.T) { - ls, err := net.Listen("tcp", "localhost:9007") - if assert.NoError(t, err) { - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/client.php", "echo", "tcp") - - f := NewSocketServer(ls, time.Minute) - defer func() { - err := ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - - w, err := f.SpawnWorker(cmd) - assert.NoError(t, err) - assert.NotNil(t, w) - - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } -} - -func Test_Tcp_StartError2(t *testing.T) { - ls, err := net.Listen("tcp", "localhost:9007") - if assert.NoError(t, err) { - defer func() { - err := ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - err = cmd.Start() - if err != nil { - t.Errorf("error executing the command: error %v", err) - } - - w, err := NewSocketServer(ls, time.Minute).SpawnWorker(cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Tcp_Failboot2(t *testing.T) { - ls, err := net.Listen("tcp", "localhost:9007") - if assert.NoError(t, err) { - defer func() { - err3 := ls.Close() - if err3 != nil { - t.Errorf("error closing the listener: error %v", err3) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/failboot.php") - - w, err2 := NewSocketServer(ls, time.Second*5).SpawnWorker(cmd) - assert.Nil(t, w) - assert.Error(t, err2) - assert.Contains(t, err2.Error(), "failboot") -} - -func Test_Tcp_Invalid2(t *testing.T) { - ls, err := net.Listen("tcp", "localhost:9007") - if assert.NoError(t, err) { - defer func() { - err := ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/invalid.php") - - w, err := NewSocketServer(ls, time.Second*1).SpawnWorker(cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Tcp_Broken2(t *testing.T) { - ls, err := net.Listen("tcp", "localhost:9007") - if assert.NoError(t, err) { - defer func() { - err := ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/client.php", "broken", "tcp") - - w, err := NewSocketServer(ls, time.Minute).SpawnWorker(cmd) - if err != nil { - t.Fatal(err) - } - wg := sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - err := w.Wait() - assert.Error(t, err) - assert.Contains(t, err.Error(), "undefined_function()") - }() - - defer func() { - time.Sleep(time.Second) - err2 := w.Stop() - // write tcp 127.0.0.1:9007->127.0.0.1:34204: use of closed network connection - assert.Error(t, err2) - }() - - sw := worker.From(w) - - res, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - assert.Error(t, err) - assert.Nil(t, res.Body) - assert.Nil(t, res.Context) - wg.Wait() -} - -func Test_Tcp_Echo2(t *testing.T) { - ls, err := net.Listen("tcp", "localhost:9007") - if assert.NoError(t, err) { - defer func() { - err := ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/client.php", "echo", "tcp") - - w, _ := NewSocketServer(ls, time.Minute).SpawnWorker(cmd) - go func() { - assert.NoError(t, w.Wait()) - }() - defer func() { - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - res, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Test_Unix_Start2(t *testing.T) { - ls, err := net.Listen("unix", "sock.unix") - assert.NoError(t, err) - defer func() { - err := ls.Close() - assert.NoError(t, err) - }() - - cmd := exec.Command("php", "../../../tests/client.php", "echo", "unix") - - w, err := NewSocketServer(ls, time.Minute).SpawnWorker(cmd) - assert.NoError(t, err) - assert.NotNil(t, w) - - go func() { - assert.NoError(t, w.Wait()) - }() - - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } -} - -func Test_Unix_Failboot2(t *testing.T) { - ls, err := net.Listen("unix", "sock.unix") - assert.NoError(t, err) - defer func() { - err := ls.Close() - assert.NoError(t, err) - }() - - cmd := exec.Command("php", "../../../tests/failboot.php") - - w, err := NewSocketServer(ls, time.Second*5).SpawnWorker(cmd) - assert.Nil(t, w) - assert.Error(t, err) - assert.Contains(t, err.Error(), "failboot") -} - -func Test_Unix_Timeout2(t *testing.T) { - ls, err := net.Listen("unix", "sock.unix") - assert.NoError(t, err) - defer func() { - err := ls.Close() - assert.NoError(t, err) - }() - - cmd := exec.Command("php", "../../../tests/slow-client.php", "echo", "unix", "200", "0") - - w, err := NewSocketServer(ls, time.Millisecond*100).SpawnWorker(cmd) - assert.Nil(t, w) - assert.Error(t, err) - assert.Contains(t, err.Error(), "relay timeout") -} - -func Test_Unix_Invalid2(t *testing.T) { - ls, err := net.Listen("unix", "sock.unix") - assert.NoError(t, err) - defer func() { - err := ls.Close() - assert.NoError(t, err) - }() - - cmd := exec.Command("php", "../../../tests/invalid.php") - - w, err := NewSocketServer(ls, time.Second*10).SpawnWorker(cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Unix_Broken2(t *testing.T) { - ls, err := net.Listen("unix", "sock.unix") - assert.NoError(t, err) - defer func() { - err := ls.Close() - assert.NoError(t, err) - }() - - cmd := exec.Command("php", "../../../tests/client.php", "broken", "unix") - - w, err := NewSocketServer(ls, time.Minute).SpawnWorker(cmd) - if err != nil { - t.Fatal(err) - } - wg := &sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - err := w.Wait() - assert.Error(t, err) - assert.Contains(t, err.Error(), "undefined_function()") - }() - - defer func() { - time.Sleep(time.Second) - err = w.Stop() - assert.Error(t, err) - }() - - sw := worker.From(w) - - res, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - - assert.Error(t, err) - assert.Nil(t, res.Context) - assert.Nil(t, res.Body) - wg.Wait() -} - -func Test_Unix_Echo2(t *testing.T) { - ls, err := net.Listen("unix", "sock.unix") - assert.NoError(t, err) - defer func() { - err := ls.Close() - assert.NoError(t, err) - }() - - cmd := exec.Command("php", "../../../tests/client.php", "echo", "unix") - - w, err := NewSocketServer(ls, time.Minute).SpawnWorker(cmd) - if err != nil { - t.Fatal(err) - } - go func() { - assert.NoError(t, w.Wait()) - }() - defer func() { - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - res, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Benchmark_Tcp_SpawnWorker_Stop2(b *testing.B) { - ls, err := net.Listen("unix", "sock.unix") - assert.NoError(b, err) - defer func() { - err := ls.Close() - assert.NoError(b, err) - }() - - f := NewSocketServer(ls, time.Minute) - for n := 0; n < b.N; n++ { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "tcp") - - w, err := f.SpawnWorker(cmd) - if err != nil { - b.Fatal(err) - } - go func() { - assert.NoError(b, w.Wait()) - }() - - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - } -} - -func Benchmark_Tcp_Worker_ExecEcho2(b *testing.B) { - ls, err := net.Listen("unix", "sock.unix") - assert.NoError(b, err) - defer func() { - err := ls.Close() - assert.NoError(b, err) - }() - - cmd := exec.Command("php", "../../../tests/client.php", "echo", "tcp") - - w, err := NewSocketServer(ls, time.Minute).SpawnWorker(cmd) - if err != nil { - b.Fatal(err) - } - defer func() { - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - for n := 0; n < b.N; n++ { - if _, err := sw.Exec(payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - } - } -} - -func Benchmark_Unix_SpawnWorker_Stop2(b *testing.B) { - defer func() { - _ = syscall.Unlink("sock.unix") - }() - ls, err := net.Listen("unix", "sock.unix") - if err == nil { - defer func() { - err := ls.Close() - if err != nil { - b.Errorf("error closing the listener: error %v", err) - } - }() - } else { - b.Skip("socket is busy") - } - - f := NewSocketServer(ls, time.Minute) - for n := 0; n < b.N; n++ { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "unix") - - w, err := f.SpawnWorker(cmd) - if err != nil { - b.Fatal(err) - } - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - } -} - -func Benchmark_Unix_Worker_ExecEcho2(b *testing.B) { - defer func() { - _ = syscall.Unlink("sock.unix") - }() - ls, err := net.Listen("unix", "sock.unix") - if err == nil { - defer func() { - err := ls.Close() - if err != nil { - b.Errorf("error closing the listener: error %v", err) - } - }() - } else { - b.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/client.php", "echo", "unix") - - w, err := NewSocketServer(ls, time.Minute).SpawnWorker(cmd) - if err != nil { - b.Fatal(err) - } - defer func() { - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - for n := 0; n < b.N; n++ { - if _, err := sw.Exec(payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - } - } -} diff --git a/pkg/transport/socket/socket_factory_test.go b/pkg/transport/socket/socket_factory_test.go deleted file mode 100755 index f55fc3dd..00000000 --- a/pkg/transport/socket/socket_factory_test.go +++ /dev/null @@ -1,572 +0,0 @@ -package socket - -import ( - "context" - "net" - "os/exec" - "sync" - "testing" - "time" - - "github.com/spiral/roadrunner/v2/pkg/payload" - "github.com/spiral/roadrunner/v2/pkg/worker" - "github.com/stretchr/testify/assert" -) - -func Test_Tcp_Start(t *testing.T) { - ctx := context.Background() - time.Sleep(time.Millisecond * 10) // to ensure free socket - - ls, err := net.Listen("tcp", "localhost:9007") - if assert.NoError(t, err) { - defer func() { - err := ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/client.php", "echo", "tcp") - - w, err := NewSocketServer(ls, time.Minute).SpawnWorkerWithTimeout(ctx, cmd) - assert.NoError(t, err) - assert.NotNil(t, w) - - go func() { - assert.NoError(t, w.Wait()) - }() - - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } -} - -func Test_Tcp_StartCloseFactory(t *testing.T) { - time.Sleep(time.Millisecond * 10) // to ensure free socket - ctx := context.Background() - ls, err := net.Listen("tcp", "localhost:9007") - if assert.NoError(t, err) { - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/client.php", "echo", "tcp") - - f := NewSocketServer(ls, time.Minute) - defer func() { - err := ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - - w, err := f.SpawnWorkerWithTimeout(ctx, cmd) - assert.NoError(t, err) - assert.NotNil(t, w) - - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } -} - -func Test_Tcp_StartError(t *testing.T) { - time.Sleep(time.Millisecond * 10) // to ensure free socket - ctx := context.Background() - ls, err := net.Listen("tcp", "localhost:9007") - if assert.NoError(t, err) { - defer func() { - err := ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/client.php", "echo", "pipes") - err = cmd.Start() - if err != nil { - t.Errorf("error executing the command: error %v", err) - } - - w, err := NewSocketServer(ls, time.Minute).SpawnWorkerWithTimeout(ctx, cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Tcp_Failboot(t *testing.T) { - time.Sleep(time.Millisecond * 10) // to ensure free socket - ctx := context.Background() - - ls, err := net.Listen("tcp", "localhost:9007") - if assert.NoError(t, err) { - defer func() { - err3 := ls.Close() - if err3 != nil { - t.Errorf("error closing the listener: error %v", err3) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/failboot.php") - - w, err2 := NewSocketServer(ls, time.Second*5).SpawnWorkerWithTimeout(ctx, cmd) - assert.Nil(t, w) - assert.Error(t, err2) - assert.Contains(t, err2.Error(), "failboot") -} - -func Test_Tcp_Timeout(t *testing.T) { - time.Sleep(time.Millisecond * 10) // to ensure free socket - ctx := context.Background() - ls, err := net.Listen("tcp", "localhost:9007") - if assert.NoError(t, err) { - defer func() { - err := ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/slow-client.php", "echo", "tcp", "200", "0") - - w, err := NewSocketServer(ls, time.Millisecond*1).SpawnWorkerWithTimeout(ctx, cmd) - assert.Nil(t, w) - assert.Error(t, err) - assert.Contains(t, err.Error(), "context deadline exceeded") -} - -func Test_Tcp_Invalid(t *testing.T) { - time.Sleep(time.Millisecond * 10) // to ensure free socket - ctx := context.Background() - ls, err := net.Listen("tcp", "localhost:9007") - if assert.NoError(t, err) { - defer func() { - err := ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/invalid.php") - - w, err := NewSocketServer(ls, time.Second*1).SpawnWorkerWithTimeout(ctx, cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Tcp_Broken(t *testing.T) { - time.Sleep(time.Millisecond * 10) // to ensure free socket - ctx := context.Background() - ls, err := net.Listen("tcp", "localhost:9007") - if assert.NoError(t, err) { - defer func() { - err := ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/client.php", "broken", "tcp") - - w, err := NewSocketServer(ls, time.Minute).SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - t.Fatal(err) - } - wg := sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - err := w.Wait() - assert.Error(t, err) - assert.Contains(t, err.Error(), "undefined_function()") - }() - - defer func() { - time.Sleep(time.Second) - err2 := w.Stop() - // write tcp 127.0.0.1:9007->127.0.0.1:34204: use of closed network connection - assert.Error(t, err2) - }() - - sw := worker.From(w) - - res, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - assert.Error(t, err) - assert.Nil(t, res.Body) - assert.Nil(t, res.Context) - wg.Wait() -} - -func Test_Tcp_Echo(t *testing.T) { - time.Sleep(time.Millisecond * 10) // to ensure free socket - ctx := context.Background() - ls, err := net.Listen("tcp", "localhost:9007") - if assert.NoError(t, err) { - defer func() { - err := ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/client.php", "echo", "tcp") - - w, _ := NewSocketServer(ls, time.Minute).SpawnWorkerWithTimeout(ctx, cmd) - go func() { - assert.NoError(t, w.Wait()) - }() - defer func() { - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - res, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Test_Unix_Start(t *testing.T) { - ctx := context.Background() - ls, err := net.Listen("unix", "sock.unix") - if err == nil { - defer func() { - err := ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/client.php", "echo", "unix") - - w, err := NewSocketServer(ls, time.Minute).SpawnWorkerWithTimeout(ctx, cmd) - assert.NoError(t, err) - assert.NotNil(t, w) - - go func() { - assert.NoError(t, w.Wait()) - }() - - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } -} - -func Test_Unix_Failboot(t *testing.T) { - ls, err := net.Listen("unix", "sock.unix") - ctx := context.Background() - if err == nil { - defer func() { - err := ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/failboot.php") - - w, err := NewSocketServer(ls, time.Second*5).SpawnWorkerWithTimeout(ctx, cmd) - assert.Nil(t, w) - assert.Error(t, err) - assert.Contains(t, err.Error(), "failboot") -} - -func Test_Unix_Timeout(t *testing.T) { - ls, err := net.Listen("unix", "sock.unix") - ctx := context.Background() - if err == nil { - defer func() { - err := ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/slow-client.php", "echo", "unix", "200", "0") - - w, err := NewSocketServer(ls, time.Millisecond*100).SpawnWorkerWithTimeout(ctx, cmd) - assert.Nil(t, w) - assert.Error(t, err) - assert.Contains(t, err.Error(), "context deadline exceeded") -} - -func Test_Unix_Invalid(t *testing.T) { - ctx := context.Background() - ls, err := net.Listen("unix", "sock.unix") - if err == nil { - defer func() { - err := ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/invalid.php") - - w, err := NewSocketServer(ls, time.Second*10).SpawnWorkerWithTimeout(ctx, cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Unix_Broken(t *testing.T) { - ctx := context.Background() - ls, err := net.Listen("unix", "sock.unix") - if err == nil { - defer func() { - err := ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/client.php", "broken", "unix") - - w, err := NewSocketServer(ls, time.Minute).SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - t.Fatal(err) - } - wg := &sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - err := w.Wait() - assert.Error(t, err) - assert.Contains(t, err.Error(), "undefined_function()") - }() - - defer func() { - time.Sleep(time.Second) - err = w.Stop() - assert.Error(t, err) - }() - - sw := worker.From(w) - - res, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - - assert.Error(t, err) - assert.Nil(t, res.Context) - assert.Nil(t, res.Body) - wg.Wait() -} - -func Test_Unix_Echo(t *testing.T) { - ctx := context.Background() - ls, err := net.Listen("unix", "sock.unix") - if err == nil { - defer func() { - err := ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/client.php", "echo", "unix") - - w, err := NewSocketServer(ls, time.Minute).SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - t.Fatal(err) - } - go func() { - assert.NoError(t, w.Wait()) - }() - defer func() { - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - res, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Benchmark_Tcp_SpawnWorker_Stop(b *testing.B) { - ctx := context.Background() - ls, err := net.Listen("tcp", "localhost:9007") - if err == nil { - defer func() { - err = ls.Close() - if err != nil { - b.Errorf("error closing the listener: error %v", err) - } - }() - } else { - b.Skip("socket is busy") - } - - f := NewSocketServer(ls, time.Minute) - for n := 0; n < b.N; n++ { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "tcp") - - w, err := f.SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - b.Fatal(err) - } - go func() { - assert.NoError(b, w.Wait()) - }() - - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - } -} - -func Benchmark_Tcp_Worker_ExecEcho(b *testing.B) { - ctx := context.Background() - ls, err := net.Listen("tcp", "localhost:9007") - if err == nil { - defer func() { - err = ls.Close() - if err != nil { - b.Errorf("error closing the listener: error %v", err) - } - }() - } else { - b.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/client.php", "echo", "tcp") - - w, err := NewSocketServer(ls, time.Minute).SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - b.Fatal(err) - } - defer func() { - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - for n := 0; n < b.N; n++ { - if _, err := sw.Exec(payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - } - } -} - -func Benchmark_Unix_SpawnWorker_Stop(b *testing.B) { - ctx := context.Background() - ls, err := net.Listen("unix", "sock.unix") - if err == nil { - defer func() { - err := ls.Close() - if err != nil { - b.Errorf("error closing the listener: error %v", err) - } - }() - } else { - b.Skip("socket is busy") - } - - f := NewSocketServer(ls, time.Minute) - for n := 0; n < b.N; n++ { - cmd := exec.Command("php", "../../../tests/client.php", "echo", "unix") - - w, err := f.SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - b.Fatal(err) - } - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - } -} - -func Benchmark_Unix_Worker_ExecEcho(b *testing.B) { - ctx := context.Background() - ls, err := net.Listen("unix", "sock.unix") - if err == nil { - defer func() { - err := ls.Close() - if err != nil { - b.Errorf("error closing the listener: error %v", err) - } - }() - } else { - b.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../../tests/client.php", "echo", "unix") - - w, err := NewSocketServer(ls, time.Minute).SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - b.Fatal(err) - } - defer func() { - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - for n := 0; n < b.N; n++ { - if _, err := sw.Exec(payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - } - } -} diff --git a/pkg/worker/interface.go b/pkg/worker/interface.go deleted file mode 100644 index 9d74ae10..00000000 --- a/pkg/worker/interface.go +++ /dev/null @@ -1,56 +0,0 @@ -package worker - -import ( - "context" - "fmt" - "time" - - "github.com/spiral/goridge/v3/interfaces/relay" - "github.com/spiral/roadrunner/v2/internal" - "github.com/spiral/roadrunner/v2/pkg/payload" -) - -type BaseProcess interface { - fmt.Stringer - - // Pid returns worker pid. - Pid() int64 - - // Created returns time worker was created at. - Created() time.Time - - // State return receive-only WorkerProcess state object, state can be used to safely access - // WorkerProcess status, time when status changed and number of WorkerProcess executions. - State() internal.State - - // Start used to run Cmd and immediately return - Start() error - - // Wait must be called once for each WorkerProcess, call will be released once WorkerProcess is - // complete and will return process error (if any), if stderr is presented it's value - // will be wrapped as WorkerError. Method will return error code if php process fails - // to find or Start the script. - Wait() error - - // Stop sends soft termination command to the WorkerProcess and waits for process completion. - Stop() error - - // Kill kills underlying process, make sure to call Wait() func to gather - // error log from the stderr. Does not waits for process completion! - Kill() error - - // Relay returns attached to worker goridge relay - Relay() relay.Relay - - // AttachRelay used to attach goridge relay to the worker process - AttachRelay(rl relay.Relay) -} - -type SyncWorker interface { - // BaseProcess provides basic functionality for the SyncWorker - BaseProcess - // Exec used to execute payload on the SyncWorker, there is no TIMEOUTS - Exec(rqs payload.Payload) (payload.Payload, error) - // ExecWithContext used to handle Exec with TTL - ExecWithTimeout(ctx context.Context, p payload.Payload) (payload.Payload, error) -} diff --git a/pkg/worker/sync_worker.go b/pkg/worker/sync_worker.go deleted file mode 100755 index 1a0393fb..00000000 --- a/pkg/worker/sync_worker.go +++ /dev/null @@ -1,244 +0,0 @@ -package worker - -import ( - "bytes" - "context" - "time" - - "github.com/spiral/errors" - "github.com/spiral/goridge/v3/interfaces/relay" - "github.com/spiral/goridge/v3/pkg/frame" - "github.com/spiral/roadrunner/v2/internal" - "github.com/spiral/roadrunner/v2/pkg/payload" - "go.uber.org/multierr" -) - -// Allocator is responsible for worker allocation in the pool -type Allocator func() (*SyncWorkerImpl, error) - -type SyncWorkerImpl struct { - process *Process -} - -// From creates SyncWorker from BaseProcess -func From(process *Process) *SyncWorkerImpl { - return &SyncWorkerImpl{ - process: process, - } -} - -// FromSync creates BaseProcess from SyncWorkerImpl -func FromSync(w *SyncWorkerImpl) BaseProcess { - return &Process{ - created: w.process.created, - events: w.process.events, - state: w.process.state, - cmd: w.process.cmd, - pid: w.process.pid, - stderr: w.process.stderr, - endState: w.process.endState, - relay: w.process.relay, - rd: w.process.rd, - } -} - -// Exec payload without TTL timeout. -func (tw *SyncWorkerImpl) Exec(p payload.Payload) (payload.Payload, error) { - const op = errors.Op("sync_worker_exec") - if len(p.Body) == 0 && len(p.Context) == 0 { - return payload.Payload{}, errors.E(op, errors.Str("payload can not be empty")) - } - - if tw.process.State().Value() != internal.StateReady { - return payload.Payload{}, errors.E(op, errors.Errorf("Process is not ready (%s)", tw.process.State().String())) - } - - // set last used time - tw.process.State().SetLastUsed(uint64(time.Now().UnixNano())) - tw.process.State().Set(internal.StateWorking) - - rsp, err := tw.execPayload(p) - if err != nil { - // just to be more verbose - if errors.Is(errors.SoftJob, err) == false { - tw.process.State().Set(internal.StateErrored) - tw.process.State().RegisterExec() - } - return payload.Payload{}, err - } - - tw.process.State().Set(internal.StateReady) - tw.process.State().RegisterExec() - - return rsp, nil -} - -type wexec struct { - payload payload.Payload - err error -} - -// Exec payload without TTL timeout. -func (tw *SyncWorkerImpl) ExecWithTimeout(ctx context.Context, p payload.Payload) (payload.Payload, error) { - const op = errors.Op("sync_worker_exec_worker_with_timeout") - c := make(chan wexec, 1) - - go func() { - if len(p.Body) == 0 && len(p.Context) == 0 { - c <- wexec{ - payload: payload.Payload{}, - err: errors.E(op, errors.Str("payload can not be empty")), - } - return - } - - if tw.process.State().Value() != internal.StateReady { - c <- wexec{ - payload: payload.Payload{}, - err: errors.E(op, errors.Errorf("Process is not ready (%s)", tw.process.State().String())), - } - return - } - - // set last used time - tw.process.State().SetLastUsed(uint64(time.Now().UnixNano())) - tw.process.State().Set(internal.StateWorking) - - rsp, err := tw.execPayload(p) - if err != nil { - // just to be more verbose - if errors.Is(errors.SoftJob, err) == false { - tw.process.State().Set(internal.StateErrored) - tw.process.State().RegisterExec() - } - c <- wexec{ - payload: payload.Payload{}, - err: errors.E(op, err), - } - return - } - - tw.process.State().Set(internal.StateReady) - tw.process.State().RegisterExec() - - c <- wexec{ - payload: rsp, - err: nil, - } - }() - - select { - // exec TTL reached - case <-ctx.Done(): - err := multierr.Combine(tw.Kill()) - if err != nil { - // append timeout error - err = multierr.Append(err, errors.E(op, errors.ExecTTL)) - return payload.Payload{}, multierr.Append(err, ctx.Err()) - } - return payload.Payload{}, errors.E(op, errors.ExecTTL, ctx.Err()) - case res := <-c: - if res.err != nil { - return payload.Payload{}, res.err - } - return res.payload, nil - } -} - -func (tw *SyncWorkerImpl) execPayload(p payload.Payload) (payload.Payload, error) { - const op = errors.Op("sync_worker_exec_payload") - - fr := frame.NewFrame() - fr.WriteVersion(frame.VERSION_1) - // can be 0 here - - buf := new(bytes.Buffer) - buf.Write(p.Context) - buf.Write(p.Body) - - // Context offset - fr.WriteOptions(uint32(len(p.Context))) - fr.WritePayloadLen(uint32(buf.Len())) - fr.WritePayload(buf.Bytes()) - - fr.WriteCRC() - - // empty and free the buffer - buf.Truncate(0) - - err := tw.Relay().Send(fr) - if err != nil { - return payload.Payload{}, err - } - - frameR := frame.NewFrame() - - err = tw.process.Relay().Receive(frameR) - if err != nil { - return payload.Payload{}, errors.E(op, err) - } - if frameR == nil { - return payload.Payload{}, errors.E(op, errors.Str("nil fr received")) - } - - if !frameR.VerifyCRC() { - return payload.Payload{}, errors.E(op, errors.Str("failed to verify CRC")) - } - - flags := frameR.ReadFlags() - - if flags&byte(frame.ERROR) != byte(0) { - return payload.Payload{}, errors.E(op, errors.SoftJob, errors.Str(string(frameR.Payload()))) - } - - options := frameR.ReadOptions() - if len(options) != 1 { - return payload.Payload{}, errors.E(op, errors.Str("options length should be equal 1 (body offset)")) - } - - pl := payload.Payload{} - pl.Context = frameR.Payload()[:options[0]] - pl.Body = frameR.Payload()[options[0]:] - - return pl, nil -} - -func (tw *SyncWorkerImpl) String() string { - return tw.process.String() -} - -func (tw *SyncWorkerImpl) Pid() int64 { - return tw.process.Pid() -} - -func (tw *SyncWorkerImpl) Created() time.Time { - return tw.process.Created() -} - -func (tw *SyncWorkerImpl) State() internal.State { - return tw.process.State() -} - -func (tw *SyncWorkerImpl) Start() error { - return tw.process.Start() -} - -func (tw *SyncWorkerImpl) Wait() error { - return tw.process.Wait() -} - -func (tw *SyncWorkerImpl) Stop() error { - return tw.process.Stop() -} - -func (tw *SyncWorkerImpl) Kill() error { - return tw.process.Kill() -} - -func (tw *SyncWorkerImpl) Relay() relay.Relay { - return tw.process.Relay() -} - -func (tw *SyncWorkerImpl) AttachRelay(rl relay.Relay) { - tw.process.AttachRelay(rl) -} diff --git a/pkg/worker/sync_worker_test.go b/pkg/worker/sync_worker_test.go deleted file mode 100755 index df556e93..00000000 --- a/pkg/worker/sync_worker_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package worker - -import ( - "os/exec" - "testing" - - "github.com/spiral/roadrunner/v2/pkg/payload" - "github.com/stretchr/testify/assert" -) - -func Test_NotStarted_String(t *testing.T) { - cmd := exec.Command("php", "tests/client.php", "echo", "pipes") - - w, _ := InitBaseWorker(cmd) - assert.Contains(t, w.String(), "php tests/client.php echo pipes") - assert.Contains(t, w.String(), "inactive") - assert.Contains(t, w.String(), "numExecs: 0") -} - -func Test_NotStarted_Exec(t *testing.T) { - cmd := exec.Command("php", "tests/client.php", "echo", "pipes") - - w, _ := InitBaseWorker(cmd) - - sw := From(w) - - res, err := sw.Exec(payload.Payload{Body: []byte("hello")}) - - assert.Error(t, err) - assert.Nil(t, res.Body) - assert.Nil(t, res.Context) - - assert.Contains(t, err.Error(), "Process is not ready (inactive)") -} diff --git a/pkg/worker/worker.go b/pkg/worker/worker.go deleted file mode 100755 index 8fd71cca..00000000 --- a/pkg/worker/worker.go +++ /dev/null @@ -1,318 +0,0 @@ -package worker - -import ( - "bytes" - "fmt" - "io" - "os" - "os/exec" - "strconv" - "strings" - "sync" - "time" - - "github.com/spiral/errors" - "github.com/spiral/goridge/v3/interfaces/relay" - "github.com/spiral/roadrunner/v2/internal" - "github.com/spiral/roadrunner/v2/pkg/events" - "go.uber.org/multierr" -) - -const ( - // WaitDuration - for how long error buffer should attempt to aggregate error messages - // before merging output together since lastError update (required to keep error update together). - WaitDuration = 25 * time.Millisecond - - // ReadBufSize used to make a slice with specified length to read from stderr - ReadBufSize = 10240 // Kb -) - -type Options func(p *Process) - -// Process - supervised process with api over goridge.Relay. -type Process struct { - // created indicates at what time Process has been created. - created time.Time - - // updates parent supervisor or pool about Process events - events events.Handler - - // state holds information about current Process state, - // number of Process executions, buf status change time. - // publicly this object is receive-only and protected using Mutex - // and atomic counter. - state *internal.WorkerState - - // underlying command with associated process, command must be - // provided to Process from outside in non-started form. CmdSource - // stdErr direction will be handled by Process to aggregate error message. - cmd *exec.Cmd - - // pid of the process, points to pid of underlying process and - // can be nil while process is not started. - pid int - - // stderr aggregates stderr output from underlying process. Value can be - // receive only once command is completed and all pipes are closed. - stderr *bytes.Buffer - - // channel is being closed once command is complete. - // waitDone chan interface{} - - // contains information about resulted process state. - endState *os.ProcessState - - // ensures than only one execution can be run at once. - mu sync.RWMutex - - // communication bus with underlying process. - relay relay.Relay - // rd in a second part of pipe to read from stderr - rd io.Reader - // stop signal terminates io.Pipe from reading from stderr - stop chan struct{} - - syncPool sync.Pool -} - -// InitBaseWorker creates new Process over given exec.cmd. -func InitBaseWorker(cmd *exec.Cmd, options ...Options) (*Process, error) { - const op = errors.Op("init_base_worker") - if cmd.Process != nil { - return nil, fmt.Errorf("can't attach to running process") - } - w := &Process{ - created: time.Now(), - events: events.NewEventsHandler(), - cmd: cmd, - state: internal.NewWorkerState(internal.StateInactive), - stderr: new(bytes.Buffer), - stop: make(chan struct{}, 1), - // sync pool for STDERR - // All receivers are pointers - syncPool: sync.Pool{ - New: func() interface{} { - buf := make([]byte, ReadBufSize) - return &buf - }, - }, - } - - w.rd, w.cmd.Stderr = io.Pipe() - - // small buffer optimization - // at this point we know, that stderr will contain huge messages - w.stderr.Grow(ReadBufSize) - - // add options - for i := 0; i < len(options); i++ { - options[i](w) - } - - go func() { - w.watch() - }() - - return w, nil -} - -func AddListeners(listeners ...events.Listener) Options { - return func(p *Process) { - for i := 0; i < len(listeners); i++ { - p.addListener(listeners[i]) - } - } -} - -// Pid returns worker pid. -func (w *Process) Pid() int64 { - return int64(w.pid) -} - -// Created returns time worker was created at. -func (w *Process) Created() time.Time { - return w.created -} - -// AddListener registers new worker event listener. -func (w *Process) addListener(listener events.Listener) { - w.events.AddListener(listener) -} - -// State return receive-only Process state object, state can be used to safely access -// Process status, time when status changed and number of Process executions. -func (w *Process) State() internal.State { - return w.state -} - -// State return receive-only Process state object, state can be used to safely access -// Process status, time when status changed and number of Process executions. -func (w *Process) AttachRelay(rl relay.Relay) { - w.relay = rl -} - -// State return receive-only Process state object, state can be used to safely access -// Process status, time when status changed and number of Process executions. -func (w *Process) Relay() relay.Relay { - return w.relay -} - -// String returns Process description. fmt.Stringer interface -func (w *Process) String() string { - st := w.state.String() - // we can safely compare pid to 0 - if w.pid != 0 { - st = st + ", pid:" + strconv.Itoa(w.pid) - } - - return fmt.Sprintf( - "(`%s` [%s], numExecs: %v)", - strings.Join(w.cmd.Args, " "), - st, - w.state.NumExecs(), - ) -} - -func (w *Process) Start() error { - err := w.cmd.Start() - if err != nil { - return err - } - w.pid = w.cmd.Process.Pid - return nil -} - -// Wait must be called once for each Process, call will be released once Process is -// complete and will return process error (if any), if stderr is presented it's value -// will be wrapped as WorkerError. Method will return error code if php process fails -// to find or Start the script. -func (w *Process) Wait() error { - const op = errors.Op("process_wait") - err := multierr.Combine(w.cmd.Wait()) - - if w.State().Value() == internal.StateDestroyed { - return errors.E(op, err) - } - - // at this point according to the documentation (see cmd.Wait comment) - // if worker finishes with an error, message will be written to the stderr first - // and then process.cmd.Wait return an error - w.endState = w.cmd.ProcessState - if err != nil { - w.state.Set(internal.StateErrored) - - w.mu.RLock() - // if process return code > 0, here will be an error from stderr (if presents) - if w.stderr.Len() > 0 { - err = multierr.Append(err, errors.E(op, errors.Str(w.stderr.String()))) - // stop the stderr buffer - w.stop <- struct{}{} - } - w.mu.RUnlock() - - return multierr.Append(err, w.closeRelay()) - } - - err = multierr.Append(err, w.closeRelay()) - if err != nil { - w.state.Set(internal.StateErrored) - return err - } - - if w.endState.Success() { - w.state.Set(internal.StateStopped) - } - - w.stderr.Reset() - - return nil -} - -func (w *Process) closeRelay() error { - if w.relay != nil { - err := w.relay.Close() - if err != nil { - return err - } - } - return nil -} - -// Stop sends soft termination command to the Process and waits for process completion. -func (w *Process) Stop() error { - var err error - w.state.Set(internal.StateStopping) - err = multierr.Append(err, internal.SendControl(w.relay, &internal.StopCommand{Stop: true})) - if err != nil { - w.state.Set(internal.StateKilling) - return multierr.Append(err, w.cmd.Process.Kill()) - } - w.state.Set(internal.StateStopped) - return nil -} - -// Kill kills underlying process, make sure to call Wait() func to gather -// error log from the stderr. Does not waits for process completion! -func (w *Process) Kill() error { - if w.State().Value() == internal.StateDestroyed { - err := w.cmd.Process.Signal(os.Kill) - if err != nil { - return err - } - return nil - } - - w.state.Set(internal.StateKilling) - err := w.cmd.Process.Signal(os.Kill) - if err != nil { - return err - } - w.state.Set(internal.StateStopped) - return nil -} - -// put the pointer, to not allocate new slice -// but erase it len and then return back -func (w *Process) put(data *[]byte) { - w.syncPool.Put(data) -} - -// get pointer to the byte slice -func (w *Process) get() *[]byte { - return w.syncPool.Get().(*[]byte) -} - -// Write appends the contents of pool to the errBuffer, growing the errBuffer as -// needed. The return value n is the length of pool; errBuffer is always nil. -func (w *Process) watch() { - go func() { - for { - select { - case <-w.stop: - buf := w.get() - // read the last data - n, _ := w.rd.Read(*buf) - w.events.Push(events.WorkerEvent{Event: events.EventWorkerLog, Worker: w, Payload: (*buf)[:n]}) - w.mu.Lock() - // write new message - // we are sending only n read bytes, without sending previously written message as bytes slice from syncPool - w.stderr.Write((*buf)[:n]) - w.mu.Unlock() - w.put(buf) - return - default: - // read the max 10kb of stderr per one read - buf := w.get() - n, _ := w.rd.Read(*buf) - w.events.Push(events.WorkerEvent{Event: events.EventWorkerLog, Worker: w, Payload: (*buf)[:n]}) - w.mu.Lock() - // delete all prev messages - w.stderr.Reset() - // write new message - w.stderr.Write((*buf)[:n]) - w.mu.Unlock() - w.put(buf) - } - } - }() -} diff --git a/pkg/worker/worker_test.go b/pkg/worker/worker_test.go deleted file mode 100755 index 805f66b5..00000000 --- a/pkg/worker/worker_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package worker - -import ( - "os/exec" - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_OnStarted(t *testing.T) { - cmd := exec.Command("php", "tests/client.php", "broken", "pipes") - assert.Nil(t, cmd.Start()) - - w, err := InitBaseWorker(cmd) - assert.Nil(t, w) - assert.NotNil(t, err) - - assert.Equal(t, "can't attach to running process", err.Error()) -} diff --git a/pkg/worker_watcher/interface.go b/pkg/worker_watcher/interface.go deleted file mode 100644 index 927aa270..00000000 --- a/pkg/worker_watcher/interface.go +++ /dev/null @@ -1,30 +0,0 @@ -package worker_watcher //nolint:golint,stylecheck - -import ( - "context" - - "github.com/spiral/roadrunner/v2/pkg/worker" -) - -type Watcher interface { - // AddToWatch used to add stack to wait its state - AddToWatch(workers []worker.SyncWorker) error - - // GetFreeWorker provide first free worker - GetFreeWorker(ctx context.Context) (worker.SyncWorker, error) - - // PutWorker enqueues worker back - PushWorker(w worker.SyncWorker) - - // AllocateNew used to allocate new worker and put in into the WorkerWatcher - AllocateNew() error - - // Destroy destroys the underlying stack - Destroy(ctx context.Context) - - // WorkersList return all stack w/o removing it from internal storage - WorkersList() []worker.SyncWorker - - // RemoveWorker remove worker from the stack - RemoveWorker(wb worker.SyncWorker) error -} diff --git a/pkg/worker_watcher/stack.go b/pkg/worker_watcher/stack.go deleted file mode 100644 index d76f4d8f..00000000 --- a/pkg/worker_watcher/stack.go +++ /dev/null @@ -1,142 +0,0 @@ -package worker_watcher //nolint:golint,stylecheck -import ( - "context" - "runtime" - "sync" - "time" - - "github.com/spiral/roadrunner/v2/internal" - "github.com/spiral/roadrunner/v2/pkg/worker" -) - -type Stack struct { - workers []*worker.SyncWorkerImpl - mutex sync.RWMutex - destroy bool - actualNumOfWorkers uint64 - initialNumOfWorkers uint64 -} - -func NewWorkersStack(initialNumOfWorkers uint64) *Stack { - w := runtime.NumCPU() - return &Stack{ - workers: make([]*worker.SyncWorkerImpl, 0, w), - actualNumOfWorkers: 0, - initialNumOfWorkers: initialNumOfWorkers, - } -} - -func (stack *Stack) Reset() { - stack.mutex.Lock() - defer stack.mutex.Unlock() - stack.actualNumOfWorkers = 0 - stack.workers = nil -} - -// Push worker back to the stack -// If stack in destroy state, Push will provide 100ms window to unlock the mutex -func (stack *Stack) Push(w worker.BaseProcess) { - stack.mutex.Lock() - defer stack.mutex.Unlock() - stack.actualNumOfWorkers++ - stack.workers = append(stack.workers, w.(*worker.SyncWorkerImpl)) -} - -func (stack *Stack) IsEmpty() bool { - stack.mutex.Lock() - defer stack.mutex.Unlock() - return len(stack.workers) == 0 -} - -func (stack *Stack) Pop() (*worker.SyncWorkerImpl, bool) { - stack.mutex.Lock() - defer stack.mutex.Unlock() - - // do not release new stack - if stack.destroy { - return nil, true - } - - if len(stack.workers) == 0 { - return nil, false - } - - // move worker - w := stack.workers[len(stack.workers)-1] - stack.workers = stack.workers[:len(stack.workers)-1] - stack.actualNumOfWorkers-- - return w, false -} - -func (stack *Stack) FindAndRemoveByPid(pid int64) bool { - stack.mutex.Lock() - defer stack.mutex.Unlock() - for i := 0; i < len(stack.workers); i++ { - // worker in the stack, reallocating - if stack.workers[i].Pid() == pid { - stack.workers = append(stack.workers[:i], stack.workers[i+1:]...) - stack.actualNumOfWorkers-- - // worker found and removed - return true - } - } - // no worker with such ID - return false -} - -// Workers return copy of the workers in the stack -func (stack *Stack) Workers() []worker.SyncWorker { - stack.mutex.Lock() - defer stack.mutex.Unlock() - workersCopy := make([]worker.SyncWorker, 0, 1) - // copy - for _, v := range stack.workers { - if v != nil { - workersCopy = append(workersCopy, v) - } - } - - return workersCopy -} - -func (stack *Stack) isDestroying() bool { - stack.mutex.Lock() - defer stack.mutex.Unlock() - return stack.destroy -} - -// we also have to give a chance to pool to Push worker (return it) -func (stack *Stack) Destroy(ctx context.Context) { - stack.mutex.Lock() - stack.destroy = true - stack.mutex.Unlock() - - tt := time.NewTicker(time.Millisecond * 500) - defer tt.Stop() - for { - select { - case <-tt.C: - stack.mutex.Lock() - // that might be one of the workers is working - if stack.initialNumOfWorkers != stack.actualNumOfWorkers { - stack.mutex.Unlock() - continue - } - stack.mutex.Unlock() - // unnecessary mutex, but - // just to make sure. All stack at this moment are in the stack - // Pop operation is blocked, push can't be done, since it's not possible to pop - stack.mutex.Lock() - for i := 0; i < len(stack.workers); i++ { - // set state for the stack in the stack (unused at the moment) - stack.workers[i].State().Set(internal.StateDestroyed) - // kill the worker - _ = stack.workers[i].Kill() - } - stack.mutex.Unlock() - // clear - stack.Reset() - return - } - } -} diff --git a/pkg/worker_watcher/stack_test.go b/pkg/worker_watcher/stack_test.go deleted file mode 100644 index 5287a6dc..00000000 --- a/pkg/worker_watcher/stack_test.go +++ /dev/null @@ -1,142 +0,0 @@ -package worker_watcher //nolint:golint,stylecheck -import ( - "context" - "os/exec" - "testing" - "time" - - "github.com/spiral/roadrunner/v2/pkg/worker" - "github.com/stretchr/testify/assert" -) - -func TestNewWorkersStack(t *testing.T) { - stack := NewWorkersStack(0) - assert.Equal(t, uint64(0), stack.actualNumOfWorkers) - assert.Equal(t, []*worker.SyncWorkerImpl{}, stack.workers) -} - -func TestStack_Push(t *testing.T) { - stack := NewWorkersStack(1) - - w, err := worker.InitBaseWorker(&exec.Cmd{}) - assert.NoError(t, err) - - sw := worker.From(w) - - stack.Push(sw) - assert.Equal(t, uint64(1), stack.actualNumOfWorkers) -} - -func TestStack_Pop(t *testing.T) { - stack := NewWorkersStack(1) - cmd := exec.Command("php", "../tests/client.php", "echo", "pipes") - - w, err := worker.InitBaseWorker(cmd) - assert.NoError(t, err) - - sw := worker.From(w) - - stack.Push(sw) - assert.Equal(t, uint64(1), stack.actualNumOfWorkers) - - _, _ = stack.Pop() - assert.Equal(t, uint64(0), stack.actualNumOfWorkers) -} - -func TestStack_FindAndRemoveByPid(t *testing.T) { - stack := NewWorkersStack(1) - cmd := exec.Command("php", "../tests/client.php", "echo", "pipes") - w, err := worker.InitBaseWorker(cmd) - assert.NoError(t, err) - - assert.NoError(t, w.Start()) - - sw := worker.From(w) - - stack.Push(sw) - assert.Equal(t, uint64(1), stack.actualNumOfWorkers) - - stack.FindAndRemoveByPid(w.Pid()) - assert.Equal(t, uint64(0), stack.actualNumOfWorkers) -} - -func TestStack_IsEmpty(t *testing.T) { - stack := NewWorkersStack(1) - cmd := exec.Command("php", "../tests/client.php", "echo", "pipes") - - w, err := worker.InitBaseWorker(cmd) - assert.NoError(t, err) - - sw := worker.From(w) - stack.Push(sw) - - assert.Equal(t, uint64(1), stack.actualNumOfWorkers) - - assert.Equal(t, false, stack.IsEmpty()) -} - -func TestStack_Workers(t *testing.T) { - stack := NewWorkersStack(1) - cmd := exec.Command("php", "../tests/client.php", "echo", "pipes") - w, err := worker.InitBaseWorker(cmd) - assert.NoError(t, err) - assert.NoError(t, w.Start()) - - sw := worker.From(w) - stack.Push(sw) - - wrks := stack.Workers() - assert.Equal(t, 1, len(wrks)) - assert.Equal(t, w.Pid(), wrks[0].Pid()) -} - -func TestStack_Reset(t *testing.T) { - stack := NewWorkersStack(1) - cmd := exec.Command("php", "../tests/client.php", "echo", "pipes") - w, err := worker.InitBaseWorker(cmd) - assert.NoError(t, err) - assert.NoError(t, w.Start()) - - sw := worker.From(w) - stack.Push(sw) - - assert.Equal(t, uint64(1), stack.actualNumOfWorkers) - stack.Reset() - assert.Equal(t, uint64(0), stack.actualNumOfWorkers) -} - -func TestStack_Destroy(t *testing.T) { - stack := NewWorkersStack(1) - cmd := exec.Command("php", "../tests/client.php", "echo", "pipes") - w, err := worker.InitBaseWorker(cmd) - assert.NoError(t, err) - assert.NoError(t, w.Start()) - - sw := worker.From(w) - stack.Push(sw) - - stack.Destroy(context.Background()) - assert.Equal(t, uint64(0), stack.actualNumOfWorkers) -} - -func TestStack_DestroyWithWait(t *testing.T) { - stack := NewWorkersStack(2) - cmd := exec.Command("php", "../tests/client.php", "echo", "pipes") - w, err := worker.InitBaseWorker(cmd) - assert.NoError(t, err) - assert.NoError(t, w.Start()) - - sw := worker.From(w) - stack.Push(sw) - stack.Push(sw) - assert.Equal(t, uint64(2), stack.actualNumOfWorkers) - - go func() { - wrk, _ := stack.Pop() - time.Sleep(time.Second * 3) - stack.Push(wrk) - }() - time.Sleep(time.Second) - stack.Destroy(context.Background()) - assert.Equal(t, uint64(0), stack.actualNumOfWorkers) -} diff --git a/pkg/worker_watcher/worker_watcher.go b/pkg/worker_watcher/worker_watcher.go deleted file mode 100755 index 753b61ee..00000000 --- a/pkg/worker_watcher/worker_watcher.go +++ /dev/null @@ -1,165 +0,0 @@ -package worker_watcher //nolint:golint,stylecheck - -import ( - "context" - "sync" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/internal" - "github.com/spiral/roadrunner/v2/pkg/events" - "github.com/spiral/roadrunner/v2/pkg/worker" -) - -// workerCreateFunc can be nil, but in that case, dead stack will not be replaced -func NewSyncWorkerWatcher(allocator worker.Allocator, numWorkers uint64, events events.Handler) Watcher { - ww := &workerWatcher{ - stack: NewWorkersStack(numWorkers), - allocator: allocator, - events: events, - } - - return ww -} - -type workerWatcher struct { - mutex sync.RWMutex - stack *Stack - allocator worker.Allocator - events events.Handler -} - -func (ww *workerWatcher) AddToWatch(workers []worker.SyncWorker) error { - for i := 0; i < len(workers); i++ { - ww.stack.Push(workers[i]) - - go func(swc worker.SyncWorker) { - ww.wait(swc) - }(workers[i]) - } - return nil -} - -func (ww *workerWatcher) GetFreeWorker(ctx context.Context) (worker.SyncWorker, error) { - const op = errors.Op("worker_watcher_get_free_worker") - // thread safe operation - w, stop := ww.stack.Pop() - if stop { - return nil, errors.E(op, errors.WatcherStopped) - } - - // handle worker remove state - // in this state worker is destroyed by supervisor - if w != nil && w.State().Value() == internal.StateRemove { - err := ww.RemoveWorker(w) - if err != nil { - return nil, err - } - // try to get next - return ww.GetFreeWorker(ctx) - } - // no free stack - if w == nil { - for { - select { - default: - w, stop = ww.stack.Pop() - if stop { - return nil, errors.E(op, errors.WatcherStopped) - } - if w == nil { - continue - } - return w, nil - case <-ctx.Done(): - return nil, errors.E(op, errors.NoFreeWorkers, errors.Str("no free workers in the stack, timeout exceed")) - } - } - } - - return w, nil -} - -func (ww *workerWatcher) AllocateNew() error { - ww.stack.mutex.Lock() - const op = errors.Op("worker_watcher_allocate_new") - sw, err := ww.allocator() - if err != nil { - return errors.E(op, errors.WorkerAllocate, err) - } - - ww.addToWatch(sw) - ww.stack.mutex.Unlock() - ww.PushWorker(sw) - - return nil -} - -func (ww *workerWatcher) RemoveWorker(wb worker.SyncWorker) error { - ww.mutex.Lock() - defer ww.mutex.Unlock() - - const op = errors.Op("worker_watcher_remove_worker") - pid := wb.Pid() - - if ww.stack.FindAndRemoveByPid(pid) { - wb.State().Set(internal.StateRemove) - err := wb.Kill() - if err != nil { - return errors.E(op, err) - } - return nil - } - - return nil -} - -// O(1) operation -func (ww *workerWatcher) PushWorker(w worker.SyncWorker) { - ww.mutex.Lock() - defer ww.mutex.Unlock() - ww.stack.Push(w) -} - -// Destroy all underlying stack (but let them to complete the task) -func (ww *workerWatcher) Destroy(ctx context.Context) { - // destroy stack, we don't use ww mutex here, since we should be able to push worker - ww.stack.Destroy(ctx) -} - -// Warning, this is O(n) operation, and it will return copy of the actual workers -func (ww *workerWatcher) WorkersList() []worker.SyncWorker { - return ww.stack.Workers() -} - -func (ww *workerWatcher) wait(w worker.BaseProcess) { - const op = errors.Op("worker_watcher_wait") - err := w.Wait() - if err != nil { - ww.events.Push(events.WorkerEvent{ - Event: events.EventWorkerError, - Worker: w, - Payload: errors.E(op, err), - }) - } - - if w.State().Value() == internal.StateDestroyed { - // worker was manually destroyed, no need to replace - ww.events.Push(events.PoolEvent{Event: events.EventWorkerDestruct, Payload: w}) - return - } - - _ = ww.stack.FindAndRemoveByPid(w.Pid()) - err = ww.AllocateNew() - if err != nil { - ww.events.Push(events.PoolEvent{ - Event: events.EventPoolError, - Payload: errors.E(op, err), - }) - } -} - -func (ww *workerWatcher) addToWatch(wb worker.SyncWorker) { - go func() { - ww.wait(wb) - }() -} diff --git a/plugins/checker/config.go b/plugins/checker/config.go deleted file mode 100644 index 5f952592..00000000 --- a/plugins/checker/config.go +++ /dev/null @@ -1,5 +0,0 @@ -package checker - -type Config struct { - Address string -} diff --git a/plugins/checker/interface.go b/plugins/checker/interface.go deleted file mode 100644 index dd9dcada..00000000 --- a/plugins/checker/interface.go +++ /dev/null @@ -1,11 +0,0 @@ -package checker - -// Status consists of status code from the service -type Status struct { - Code int -} - -// Checker interface used to get latest status from plugin -type Checker interface { - Status() Status -} diff --git a/plugins/checker/plugin.go b/plugins/checker/plugin.go deleted file mode 100644 index 59a37613..00000000 --- a/plugins/checker/plugin.go +++ /dev/null @@ -1,150 +0,0 @@ -package checker - -import ( - "fmt" - "net/http" - "time" - - "github.com/gofiber/fiber/v2" - fiberLogger "github.com/gofiber/fiber/v2/middleware/logger" - endure "github.com/spiral/endure/pkg/container" - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/logger" -) - -const ( - // PluginName declares public plugin name. - PluginName = "status" -) - -type Plugin struct { - registry map[string]Checker - server *fiber.App - log logger.Logger - cfg *Config -} - -func (c *Plugin) Init(log logger.Logger, cfg config.Configurer) error { - const op = errors.Op("checker_plugin_init") - if !cfg.Has(PluginName) { - return errors.E(op, errors.Disabled) - } - err := cfg.UnmarshalKey(PluginName, &c.cfg) - if err != nil { - return errors.E(op, errors.Disabled, err) - } - - c.registry = make(map[string]Checker) - c.log = log - return nil -} - -func (c *Plugin) Serve() chan error { - errCh := make(chan error, 1) - c.server = fiber.New(fiber.Config{ - ReadTimeout: time.Second * 5, - WriteTimeout: time.Second * 5, - IdleTimeout: time.Second * 5, - }) - c.server.Group("/v1", c.healthHandler) - c.server.Use(fiberLogger.New()) - c.server.Use("/health", c.healthHandler) - - go func() { - err := c.server.Listen(c.cfg.Address) - if err != nil { - errCh <- err - } - }() - - return errCh -} - -func (c *Plugin) Stop() error { - const op = errors.Op("checker_plugin_stop") - err := c.server.Shutdown() - if err != nil { - return errors.E(op, err) - } - return nil -} - -// Reset named service. -func (c *Plugin) Status(name string) (Status, error) { - const op = errors.Op("checker_plugin_status") - svc, ok := c.registry[name] - if !ok { - return Status{}, errors.E(op, errors.Errorf("no such service: %s", name)) - } - - return svc.Status(), nil -} - -// CollectTarget collecting services which can provide Status. -func (c *Plugin) CollectTarget(name endure.Named, r Checker) error { - c.registry[name.Name()] = r - return nil -} - -// Collects declares services to be collected. -func (c *Plugin) Collects() []interface{} { - return []interface{}{ - c.CollectTarget, - } -} - -// Name of the service. -func (c *Plugin) Name() string { - return PluginName -} - -// RPCService returns associated rpc service. -func (c *Plugin) RPC() interface{} { - return &rpc{srv: c, log: c.log} -} - -type Plugins struct { - Plugins []string `query:"plugin"` -} - -const template string = "Service: %s: Status: %d\n" - -func (c *Plugin) healthHandler(ctx *fiber.Ctx) error { - const op = errors.Op("checker_plugin_health_handler") - plugins := &Plugins{} - err := ctx.QueryParser(plugins) - if err != nil { - return errors.E(op, err) - } - - if len(plugins.Plugins) == 0 { - ctx.Status(http.StatusOK) - _, _ = ctx.WriteString("No plugins provided in query. Query should be in form of: /v1/health?plugin=plugin1&plugin=plugin2 \n") - return nil - } - - failed := false - // iterate over all provided plugins - for i := 0; i < len(plugins.Plugins); i++ { - // check if the plugin exists - if plugin, ok := c.registry[plugins.Plugins[i]]; ok { - st := plugin.Status() - if st.Code >= 500 { - failed = true - continue - } else if st.Code >= 100 && st.Code <= 400 { - _, _ = ctx.WriteString(fmt.Sprintf(template, plugins.Plugins[i], st.Code)) - } - } else { - _, _ = ctx.WriteString(fmt.Sprintf("Service: %s not found", plugins.Plugins[i])) - } - } - if failed { - ctx.Status(http.StatusInternalServerError) - return nil - } - - ctx.Status(http.StatusOK) - return nil -} diff --git a/plugins/checker/rpc.go b/plugins/checker/rpc.go deleted file mode 100644 index a965dcd4..00000000 --- a/plugins/checker/rpc.go +++ /dev/null @@ -1,27 +0,0 @@ -package checker - -import ( - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/plugins/logger" -) - -type rpc struct { - srv *Plugin - log logger.Logger -} - -// Status return current status of the provided plugin -func (rpc *rpc) Status(service string, status *Status) error { - const op = errors.Op("checker_rpc_status") - rpc.log.Debug("started Status method", "service", service) - st, err := rpc.srv.Status(service) - if err != nil { - return errors.E(op, err) - } - - *status = st - - rpc.log.Debug("status code", "code", st.Code) - rpc.log.Debug("successfully finished Status method") - return nil -} diff --git a/plugins/config/interface.go b/plugins/config/interface.go deleted file mode 100644 index 23279f53..00000000 --- a/plugins/config/interface.go +++ /dev/null @@ -1,26 +0,0 @@ -package config - -type Configurer interface { - // // UnmarshalKey takes a single key and unmarshals it into a Struct. - // - // func (h *HttpService) Init(cp config.Configurer) error { - // h.config := &HttpConfig{} - // if err := configProvider.UnmarshalKey("http", h.config); err != nil { - // return err - // } - // } - UnmarshalKey(name string, out interface{}) error - - // Unmarshal unmarshals the config into a Struct. Make sure that the tags - // on the fields of the structure are properly set. - Unmarshal(out interface{}) error - - // Get used to get config section - Get(name string) interface{} - - // Overwrite used to overwrite particular values in the unmarshalled config - Overwrite(values map[string]interface{}) error - - // Has checks if config section exists. - Has(name string) bool -} diff --git a/plugins/config/plugin.go b/plugins/config/plugin.go deleted file mode 100755 index ce2baa85..00000000 --- a/plugins/config/plugin.go +++ /dev/null @@ -1,84 +0,0 @@ -package config - -import ( - "bytes" - "strings" - - "github.com/spf13/viper" - "github.com/spiral/errors" -) - -type Viper struct { - viper *viper.Viper - Path string - Prefix string - Type string - ReadInCfg []byte -} - -// Inits config provider. -func (v *Viper) Init() error { - const op = errors.Op("config_plugin_init") - v.viper = viper.New() - // If user provided []byte data with config, read it and ignore Path and Prefix - if v.ReadInCfg != nil && v.Type != "" { - v.viper.SetConfigType("yaml") - return v.viper.ReadConfig(bytes.NewBuffer(v.ReadInCfg)) - } - - // read in environment variables that match - v.viper.AutomaticEnv() - if v.Prefix == "" { - return errors.E(op, errors.Str("prefix should be set")) - } - - v.viper.SetEnvPrefix(v.Prefix) - if v.Path == "" { - return errors.E(op, errors.Str("path should be set")) - } - - v.viper.SetConfigFile(v.Path) - v.viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - - return v.viper.ReadInConfig() -} - -// Overwrite overwrites existing config with provided values -func (v *Viper) Overwrite(values map[string]interface{}) error { - if len(values) != 0 { - for key, value := range values { - v.viper.Set(key, value) - } - } - - return nil -} - -// UnmarshalKey reads configuration section into configuration object. -func (v *Viper) UnmarshalKey(name string, out interface{}) error { - const op = errors.Op("config_plugin_unmarshal_key") - err := v.viper.UnmarshalKey(name, &out) - if err != nil { - return errors.E(op, err) - } - return nil -} - -func (v *Viper) Unmarshal(out interface{}) error { - const op = errors.Op("config_plugin_unmarshal") - err := v.viper.Unmarshal(&out) - if err != nil { - return errors.E(op, err) - } - return nil -} - -// Get raw config in a form of config section. -func (v *Viper) Get(name string) interface{} { - return v.viper.Get(name) -} - -// Has checks if config section exists. -func (v *Viper) Has(name string) bool { - return v.viper.IsSet(name) -} diff --git a/plugins/doc/graphviz.svg b/plugins/doc/graphviz.svg deleted file mode 100644 index 86f6ab5c..00000000 --- a/plugins/doc/graphviz.svg +++ /dev/null @@ -1,169 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><!-- Generated by graphviz version 2.40.1 (20161225.0304) - --><!-- Title: endure Pages: 1 --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="625pt" height="479pt" viewBox="0.00 0.00 624.94 478.79"> -<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 474.786)"> -<title>endure</title> -<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-474.786 620.9357,-474.786 620.9357,4 -4,4"/> -<!-- checker --> -<g id="node1" class="node"> -<title>checker</title> -<ellipse fill="none" stroke="#000000" cx="412.2429" cy="-377.2862" rx="41.1103" ry="18"/> -<text text-anchor="middle" x="412.2429" y="-373.0862" font-family="Times,serif" font-size="14.00" fill="#000000">checker</text> -</g> -<!-- config --> -<g id="node2" class="node"> -<title>config</title> -<ellipse fill="none" stroke="#000000" cx="463.8878" cy="-235.393" rx="35.9154" ry="18"/> -<text text-anchor="middle" x="463.8878" y="-231.193" font-family="Times,serif" font-size="14.00" fill="#000000">config</text> -</g> -<!-- checker->config --> -<g id="edge1" class="edge"> -<title>checker->config</title> -<path fill="none" stroke="#000000" d="M418.7837,-359.3154C427.6313,-335.0068 443.4953,-291.4209 453.8554,-262.9568"/> -<polygon fill="#000000" stroke="#000000" points="457.2687,-263.812 457.4,-253.218 450.6908,-261.4178 457.2687,-263.812"/> -</g> -<!-- logger --> -<g id="node3" class="node"> -<title>logger</title> -<ellipse fill="none" stroke="#000000" cx="35.7071" cy="-310.8928" rx="35.9154" ry="18"/> -<text text-anchor="middle" x="35.7071" y="-306.6928" font-family="Times,serif" font-size="14.00" fill="#000000">logger</text> -</g> -<!-- checker->logger --> -<g id="edge2" class="edge"> -<title>checker->logger</title> -<path fill="none" stroke="#000000" d="M374.0665,-370.5547C303.7112,-358.1492 154.0014,-331.7513 79.586,-318.6299"/> -<polygon fill="#000000" stroke="#000000" points="80.0574,-315.1591 69.6015,-316.8693 78.8418,-322.0527 80.0574,-315.1591"/> -</g> -<!-- logger->config --> -<g id="edge4" class="edge"> -<title>logger->config</title> -<path fill="none" stroke="#000000" d="M69.6636,-304.9054C146.6435,-291.3317 334.3698,-258.2305 420.0048,-243.1308"/> -<polygon fill="#000000" stroke="#000000" points="420.6875,-246.5645 429.9277,-241.3811 419.4719,-239.6708 420.6875,-246.5645"/> -</g> -<!-- gzip --> -<g id="node4" class="node"> -<title>gzip</title> -<ellipse fill="none" stroke="#000000" cx="531.6651" cy="-102.393" rx="27.8286" ry="18"/> -<text text-anchor="middle" x="531.6651" y="-98.193" font-family="Times,serif" font-size="14.00" fill="#000000">gzip</text> -</g> -<!-- headers --> -<g id="node5" class="node"> -<title>headers</title> -<ellipse fill="none" stroke="#000000" cx="576.4118" cy="-235.393" rx="40.548" ry="18"/> -<text text-anchor="middle" x="576.4118" y="-231.193" font-family="Times,serif" font-size="14.00" fill="#000000">headers</text> -</g> -<!-- headers->config --> -<g id="edge3" class="edge"> -<title>headers->config</title> -<path fill="none" stroke="#000000" d="M535.788,-235.393C527.3742,-235.393 518.4534,-235.393 509.8639,-235.393"/> -<polygon fill="#000000" stroke="#000000" points="509.607,-231.8931 499.607,-235.393 509.607,-238.8931 509.607,-231.8931"/> -</g> -<!-- metrics --> -<g id="node6" class="node"> -<title>metrics</title> -<ellipse fill="none" stroke="#000000" cx="412.2429" cy="-93.4998" rx="39.4196" ry="18"/> -<text text-anchor="middle" x="412.2429" y="-89.2998" font-family="Times,serif" font-size="14.00" fill="#000000">metrics</text> -</g> -<!-- metrics->config --> -<g id="edge6" class="edge"> -<title>metrics->config</title> -<path fill="none" stroke="#000000" d="M418.7837,-111.4707C427.6313,-135.7792 443.4953,-179.3651 453.8554,-207.8292"/> -<polygon fill="#000000" stroke="#000000" points="450.6908,-209.3682 457.4,-217.5681 457.2687,-206.974 450.6908,-209.3682"/> -</g> -<!-- metrics->logger --> -<g id="edge5" class="edge"> -<title>metrics->logger</title> -<path fill="none" stroke="#000000" d="M387.5373,-107.7636C321.7958,-145.7194 142.5487,-249.2078 68.4432,-291.9926"/> -<polygon fill="#000000" stroke="#000000" points="66.4391,-289.1082 59.5289,-297.1393 69.9391,-295.1704 66.4391,-289.1082"/> -</g> -<!-- redis --> -<g id="node7" class="node"> -<title>redis</title> -<ellipse fill="none" stroke="#000000" cx="281.4734" cy="-18" rx="29.6127" ry="18"/> -<text text-anchor="middle" x="281.4734" y="-13.8" font-family="Times,serif" font-size="14.00" fill="#000000">redis</text> -</g> -<!-- redis->config --> -<g id="edge8" class="edge"> -<title>redis->config</title> -<path fill="none" stroke="#000000" d="M295.1841,-34.3398C326.9308,-72.174 405.6399,-165.9759 443.2445,-210.7914"/> -<polygon fill="#000000" stroke="#000000" points="440.6581,-213.1541 449.7672,-218.5648 446.0204,-208.6545 440.6581,-213.1541"/> -</g> -<!-- redis->logger --> -<g id="edge7" class="edge"> -<title>redis->logger</title> -<path fill="none" stroke="#000000" d="M267.9098,-34.1644C227.1471,-82.7435 105.5381,-227.6715 56.5241,-286.0841"/> -<polygon fill="#000000" stroke="#000000" points="53.5843,-284.1426 49.8376,-294.0528 58.9466,-288.6421 53.5843,-284.1426"/> -</g> -<!-- reload --> -<g id="node8" class="node"> -<title>reload</title> -<ellipse fill="none" stroke="#000000" cx="281.4734" cy="-452.786" rx="35.3315" ry="18"/> -<text text-anchor="middle" x="281.4734" y="-448.586" font-family="Times,serif" font-size="14.00" fill="#000000">reload</text> -</g> -<!-- reload->config --> -<g id="edge10" class="edge"> -<title>reload->config</title> -<path fill="none" stroke="#000000" d="M295.4842,-436.0885C327.4495,-397.9939 405.8819,-304.5217 443.3335,-259.8887"/> -<polygon fill="#000000" stroke="#000000" points="446.0824,-262.0576 449.8292,-252.1474 440.7201,-257.5581 446.0824,-262.0576"/> -</g> -<!-- reload->logger --> -<g id="edge9" class="edge"> -<title>reload->logger</title> -<path fill="none" stroke="#000000" d="M257.9083,-439.1807C213.6848,-413.6483 118.2025,-358.5216 68.0211,-329.5493"/> -<polygon fill="#000000" stroke="#000000" points="69.6111,-326.4259 59.2009,-324.457 66.1111,-332.4881 69.6111,-326.4259"/> -</g> -<!-- resetter --> -<g id="node9" class="node"> -<title>resetter</title> -<ellipse fill="none" stroke="#000000" cx="132.7678" cy="-426.5652" rx="39.3984" ry="18"/> -<text text-anchor="middle" x="132.7678" y="-422.3652" font-family="Times,serif" font-size="14.00" fill="#000000">resetter</text> -</g> -<!-- reload->resetter --> -<g id="edge11" class="edge"> -<title>reload->resetter</title> -<path fill="none" stroke="#000000" d="M248.1009,-446.9016C227.9026,-443.3401 201.8366,-438.7439 179.5962,-434.8224"/> -<polygon fill="#000000" stroke="#000000" points="180.1376,-431.3639 169.6817,-433.0742 178.922,-438.2575 180.1376,-431.3639"/> -</g> -<!-- resetter->logger --> -<g id="edge12" class="edge"> -<title>resetter->logger</title> -<path fill="none" stroke="#000000" d="M118.4461,-409.4974C102.0084,-389.9077 74.9173,-357.6218 56.2379,-335.3605"/> -<polygon fill="#000000" stroke="#000000" points="58.881,-333.0653 49.7719,-327.6546 53.5187,-337.5649 58.881,-333.0653"/> -</g> -<!-- rpc --> -<g id="node10" class="node"> -<title>rpc</title> -<ellipse fill="none" stroke="#000000" cx="132.7678" cy="-44.2208" rx="27" ry="18"/> -<text text-anchor="middle" x="132.7678" y="-40.0208" font-family="Times,serif" font-size="14.00" fill="#000000">rpc</text> -</g> -<!-- rpc->config --> -<g id="edge13" class="edge"> -<title>rpc->config</title> -<path fill="none" stroke="#000000" d="M153.4808,-56.1795C209.3277,-88.4227 363.359,-177.3527 431.1448,-216.4889"/> -<polygon fill="#000000" stroke="#000000" points="429.7078,-219.7006 440.1181,-221.6696 433.2078,-213.6384 429.7078,-219.7006"/> -</g> -<!-- rpc->logger --> -<g id="edge14" class="edge"> -<title>rpc->logger</title> -<path fill="none" stroke="#000000" d="M126.3994,-61.7179C109.8827,-107.097 65.5725,-228.8383 45.6502,-283.5745"/> -<polygon fill="#000000" stroke="#000000" points="42.3576,-282.3876 42.2262,-292.9816 48.9354,-284.7818 42.3576,-282.3876"/> -</g> -<!-- static --> -<g id="node11" class="node"> -<title>static</title> -<ellipse fill="none" stroke="#000000" cx="35.7071" cy="-159.8932" rx="31.3333" ry="18"/> -<text text-anchor="middle" x="35.7071" y="-155.6932" font-family="Times,serif" font-size="14.00" fill="#000000">static</text> -</g> -<!-- static->config --> -<g id="edge15" class="edge"> -<title>static->config</title> -<path fill="none" stroke="#000000" d="M65.8159,-165.2022C140.1736,-178.3135 332.7753,-212.2743 419.9157,-227.6396"/> -<polygon fill="#000000" stroke="#000000" points="419.5489,-231.1288 430.0048,-229.4185 420.7645,-224.2351 419.5489,-231.1288"/> -</g> -<!-- static->logger --> -<g id="edge16" class="edge"> -<title>static->logger</title> -<path fill="none" stroke="#000000" d="M35.7071,-178.1073C35.7071,-204.0691 35.7071,-251.9543 35.7071,-282.5696"/> -<polygon fill="#000000" stroke="#000000" points="32.2072,-282.6141 35.7071,-292.6141 39.2072,-282.6142 32.2072,-282.6141"/> -</g> -</g> -</svg>
\ No newline at end of file diff --git a/plugins/gzip/plugin.go b/plugins/gzip/plugin.go deleted file mode 100644 index eee6c1d3..00000000 --- a/plugins/gzip/plugin.go +++ /dev/null @@ -1,26 +0,0 @@ -package gzip - -import ( - "net/http" - - "github.com/NYTimes/gziphandler" -) - -const PluginName = "gzip" - -type Gzip struct{} - -// needed for the Endure -func (g *Gzip) Init() error { - return nil -} - -func (g *Gzip) Middleware(next http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - gziphandler.GzipHandler(next).ServeHTTP(w, r) - } -} - -func (g *Gzip) Name() string { - return PluginName -} diff --git a/plugins/headers/config.go b/plugins/headers/config.go deleted file mode 100644 index 688b4764..00000000 --- a/plugins/headers/config.go +++ /dev/null @@ -1,36 +0,0 @@ -package headers - -// Config declares headers service configuration. -type Config struct { - Headers *struct { - // CORS settings. - CORS *CORSConfig - - // Request headers to add to every payload send to PHP. - Request map[string]string - - // Response headers to add to every payload generated by PHP. - Response map[string]string - } -} - -// CORSConfig headers configuration. -type CORSConfig struct { - // AllowedOrigin: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin - AllowedOrigin string `mapstructure:"allowed_origin"` - - // AllowedHeaders: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers - AllowedHeaders string `mapstructure:"allowed_headers"` - - // AllowedMethods: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods - AllowedMethods string `mapstructure:"allowed_methods"` - - // AllowCredentials https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials - AllowCredentials *bool `mapstructure:"allow_credentials"` - - // ExposeHeaders: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers - ExposedHeaders string `mapstructure:"exposed_headers"` - - // MaxAge of CORS headers in seconds/ - MaxAge int `mapstructure:"max_age"` -} diff --git a/plugins/headers/plugin.go b/plugins/headers/plugin.go deleted file mode 100644 index a5ee702f..00000000 --- a/plugins/headers/plugin.go +++ /dev/null @@ -1,124 +0,0 @@ -package headers - -import ( - "net/http" - "strconv" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/plugins/config" -) - -// ID contains default service name. -const PluginName = "headers" -const RootPluginName = "http" - -// Service serves headers files. Potentially convert into middleware? -type Plugin struct { - // server configuration (location, forbidden files and etc) - cfg *Config -} - -// Init must return configure service and return true if service hasStatus enabled. Must return error in case of -// misconfiguration. Services must not be used without proper configuration pushed first. -func (s *Plugin) Init(cfg config.Configurer) error { - const op = errors.Op("headers_plugin_init") - if !cfg.Has(RootPluginName) { - return errors.E(op, errors.Disabled) - } - err := cfg.UnmarshalKey(RootPluginName, &s.cfg) - if err != nil { - return errors.E(op, errors.Disabled, err) - } - - if s.cfg.Headers == nil { - return errors.E(op, errors.Disabled) - } - - return nil -} - -// middleware must return true if request/response pair is handled within the middleware. -func (s *Plugin) Middleware(next http.Handler) http.HandlerFunc { - // Define the http.HandlerFunc - return func(w http.ResponseWriter, r *http.Request) { - if s.cfg.Headers.Request != nil { - for k, v := range s.cfg.Headers.Request { - r.Header.Add(k, v) - } - } - - if s.cfg.Headers.Response != nil { - for k, v := range s.cfg.Headers.Response { - w.Header().Set(k, v) - } - } - - if s.cfg.Headers.CORS != nil { - if r.Method == http.MethodOptions { - s.preflightRequest(w) - return - } - s.corsHeaders(w) - } - - next.ServeHTTP(w, r) - } -} - -func (s *Plugin) Name() string { - return PluginName -} - -// configure OPTIONS response -func (s *Plugin) preflightRequest(w http.ResponseWriter) { - headers := w.Header() - - headers.Add("Vary", "Origin") - headers.Add("Vary", "Access-Control-Request-Method") - headers.Add("Vary", "Access-Control-Request-Headers") - - if s.cfg.Headers.CORS.AllowedOrigin != "" { - headers.Set("Access-Control-Allow-Origin", s.cfg.Headers.CORS.AllowedOrigin) - } - - if s.cfg.Headers.CORS.AllowedHeaders != "" { - headers.Set("Access-Control-Allow-Headers", s.cfg.Headers.CORS.AllowedHeaders) - } - - if s.cfg.Headers.CORS.AllowedMethods != "" { - headers.Set("Access-Control-Allow-Methods", s.cfg.Headers.CORS.AllowedMethods) - } - - if s.cfg.Headers.CORS.AllowCredentials != nil { - headers.Set("Access-Control-Allow-Credentials", strconv.FormatBool(*s.cfg.Headers.CORS.AllowCredentials)) - } - - if s.cfg.Headers.CORS.MaxAge > 0 { - headers.Set("Access-Control-Max-Age", strconv.Itoa(s.cfg.Headers.CORS.MaxAge)) - } - - w.WriteHeader(http.StatusOK) -} - -// configure CORS headers -func (s *Plugin) corsHeaders(w http.ResponseWriter) { - headers := w.Header() - - headers.Add("Vary", "Origin") - - if s.cfg.Headers.CORS.AllowedOrigin != "" { - headers.Set("Access-Control-Allow-Origin", s.cfg.Headers.CORS.AllowedOrigin) - } - - if s.cfg.Headers.CORS.AllowedHeaders != "" { - headers.Set("Access-Control-Allow-Headers", s.cfg.Headers.CORS.AllowedHeaders) - } - - if s.cfg.Headers.CORS.ExposedHeaders != "" { - headers.Set("Access-Control-Expose-Headers", s.cfg.Headers.CORS.ExposedHeaders) - } - - if s.cfg.Headers.CORS.AllowCredentials != nil { - headers.Set("Access-Control-Allow-Credentials", strconv.FormatBool(*s.cfg.Headers.CORS.AllowCredentials)) - } -} diff --git a/plugins/http/attributes/attributes.go b/plugins/http/attributes/attributes.go deleted file mode 100644 index 4c453766..00000000 --- a/plugins/http/attributes/attributes.go +++ /dev/null @@ -1,85 +0,0 @@ -package attributes - -import ( - "context" - "errors" - "net/http" -) - -// contextKey is a value for use with context.WithValue. It's used as -// a pointer so it fits in an interface{} without allocation. -type contextKey struct { - name string -} - -func (k *contextKey) String() string { return k.name } - -var ( - // PsrContextKey is a context key. It can be used in the http attributes - PsrContextKey = &contextKey{"psr_attributes"} -) - -type attrs map[string]interface{} - -func (v attrs) get(key string) interface{} { - if v == nil { - return "" - } - - return v[key] -} - -func (v attrs) set(key string, value interface{}) { - v[key] = value -} - -func (v attrs) del(key string) { - delete(v, key) -} - -// Init returns request with new context and attribute bag. -func Init(r *http.Request) *http.Request { - return r.WithContext(context.WithValue(r.Context(), PsrContextKey, attrs{})) -} - -// All returns all context attributes. -func All(r *http.Request) map[string]interface{} { - v := r.Context().Value(PsrContextKey) - if v == nil { - return attrs{} - } - - return v.(attrs) -} - -// Get gets the value from request context. It replaces any existing -// values. -func Get(r *http.Request, key string) interface{} { - v := r.Context().Value(PsrContextKey) - if v == nil { - return nil - } - - return v.(attrs).get(key) -} - -// Set sets the key to value. It replaces any existing -// values. Context specific. -func Set(r *http.Request, key string, value interface{}) error { - v := r.Context().Value(PsrContextKey) - if v == nil { - return errors.New("unable to find `psr:attributes` context key") - } - - v.(attrs).set(key, value) - return nil -} - -// Delete deletes values associated with attribute key. -func (v attrs) Delete(key string) { - if v == nil { - return - } - - v.del(key) -} diff --git a/plugins/http/config/fcgi.go b/plugins/http/config/fcgi.go deleted file mode 100644 index 3d4acbe1..00000000 --- a/plugins/http/config/fcgi.go +++ /dev/null @@ -1,7 +0,0 @@ -package config - -// FCGI for FastCGI server. -type FCGI struct { - // Address and port to handle as http server. - Address string -} diff --git a/plugins/http/config/http.go b/plugins/http/config/http.go deleted file mode 100644 index bfbc1af6..00000000 --- a/plugins/http/config/http.go +++ /dev/null @@ -1,180 +0,0 @@ -package config - -import ( - "net" - "runtime" - "strings" - "time" - - "github.com/spiral/errors" - poolImpl "github.com/spiral/roadrunner/v2/pkg/pool" -) - -// HTTP configures RoadRunner HTTP server. -type HTTP struct { - // Host and port to handle as http server. - Address string - - // SSLConfig defines https server options. - SSLConfig *SSL `mapstructure:"ssl"` - - // FCGIConfig configuration. You can use FastCGI without HTTP server. - FCGIConfig *FCGI `mapstructure:"fcgi"` - - // HTTP2Config configuration - HTTP2Config *HTTP2 `mapstructure:"http2"` - - // MaxRequestSize specified max size for payload body in megabytes, set 0 to unlimited. - MaxRequestSize uint64 `mapstructure:"max_request_size"` - - // TrustedSubnets declare IP subnets which are allowed to set ip using X-Real-Ip and X-Forwarded-For - TrustedSubnets []string `mapstructure:"trusted_subnets"` - - // Uploads configures uploads configuration. - Uploads *Uploads `mapstructure:"uploads"` - - // Pool configures worker pool. - Pool *poolImpl.Config `mapstructure:"pool"` - - // Env is environment variables passed to the http pool - Env map[string]string - - // List of the middleware names (order will be preserved) - Middleware []string - - // slice of net.IPNet - Cidrs Cidrs -} - -// EnableHTTP is true when http server must run. -func (c *HTTP) EnableHTTP() bool { - return c.Address != "" -} - -// EnableTLS returns true if pool must listen TLS connections. -func (c *HTTP) EnableTLS() bool { - return c.SSLConfig.Key != "" || c.SSLConfig.Cert != "" || c.SSLConfig.RootCA != "" -} - -// EnableH2C when HTTP/2 extension must be enabled on TCP. -func (c *HTTP) EnableH2C() bool { - return c.HTTP2Config.H2C -} - -// EnableFCGI is true when FastCGI server must be enabled. -func (c *HTTP) EnableFCGI() bool { - return c.FCGIConfig.Address != "" -} - -// InitDefaults must populate HTTP values using given HTTP source. Must return error if HTTP is not valid. -func (c *HTTP) InitDefaults() error { - if c.Pool == nil { - // default pool - c.Pool = &poolImpl.Config{ - Debug: false, - NumWorkers: uint64(runtime.NumCPU()), - MaxJobs: 1000, - AllocateTimeout: time.Second * 60, - DestroyTimeout: time.Second * 60, - Supervisor: nil, - } - } - - if c.HTTP2Config == nil { - c.HTTP2Config = &HTTP2{} - } - - if c.FCGIConfig == nil { - c.FCGIConfig = &FCGI{} - } - - if c.Uploads == nil { - c.Uploads = &Uploads{} - } - - if c.SSLConfig == nil { - c.SSLConfig = &SSL{} - } - - if c.SSLConfig.Address == "" { - c.SSLConfig.Address = ":443" - } - - err := c.HTTP2Config.InitDefaults() - if err != nil { - return err - } - err = c.Uploads.InitDefaults() - if err != nil { - return err - } - - if c.TrustedSubnets == nil { - // @see https://en.wikipedia.org/wiki/Reserved_IP_addresses - c.TrustedSubnets = []string{ - "10.0.0.0/8", - "127.0.0.0/8", - "172.16.0.0/12", - "192.168.0.0/16", - "::1/128", - "fc00::/7", - "fe80::/10", - } - } - - cidrs, err := ParseCIDRs(c.TrustedSubnets) - if err != nil { - return err - } - c.Cidrs = cidrs - - return c.Valid() -} - -// ParseCIDRs parse IPNet addresses and return slice of its -func ParseCIDRs(subnets []string) (Cidrs, error) { - c := make(Cidrs, 0, len(subnets)) - for _, cidr := range subnets { - _, cr, err := net.ParseCIDR(cidr) - if err != nil { - return nil, err - } - - c = append(c, cr) - } - - return c, nil -} - -// Valid validates the configuration. -func (c *HTTP) Valid() error { - const op = errors.Op("validation") - if c.Uploads == nil { - return errors.E(op, errors.Str("malformed uploads config")) - } - - if c.HTTP2Config == nil { - return errors.E(op, errors.Str("malformed http2 config")) - } - - if c.Pool == nil { - return errors.E(op, "malformed pool config") - } - - if !c.EnableHTTP() && !c.EnableTLS() && !c.EnableFCGI() { - return errors.E(op, errors.Str("unable to run http service, no method has been specified (http, https, http/2 or FastCGI)")) - } - - if c.Address != "" && !strings.Contains(c.Address, ":") { - return errors.E(op, errors.Str("malformed http server address")) - } - - if c.EnableTLS() { - err := c.SSLConfig.Valid() - if err != nil { - return errors.E(op, err) - } - } - - return nil -} diff --git a/plugins/http/config/http2.go b/plugins/http/config/http2.go deleted file mode 100644 index b1e109e9..00000000 --- a/plugins/http/config/http2.go +++ /dev/null @@ -1,28 +0,0 @@ -package config - -// HTTP2 HTTP/2 server customizations. -type HTTP2 struct { - // h2cHandler is a Handler which implements h2c by hijacking the HTTP/1 traffic - // that should be h2c traffic. There are two ways to begin a h2c connection - // (RFC 7540 Section 3.2 and 3.4): (1) Starting with Prior Knowledge - this - // works by starting an h2c connection with a string of bytes that is valid - // HTTP/1, but unlikely to occur in practice and (2) Upgrading from HTTP/1 to - // h2c - this works by using the HTTP/1 Upgrade header to request an upgrade to - // h2c. When either of those situations occur we hijack the HTTP/1 connection, - // convert it to a HTTP/2 connection and pass the net.Conn to http2.ServeConn. - - // H2C enables HTTP/2 over TCP - H2C bool - - // MaxConcurrentStreams defaults to 128. - MaxConcurrentStreams uint32 `mapstructure:"max_concurrent_streams"` -} - -// InitDefaults sets default values for HTTP/2 configuration. -func (cfg *HTTP2) InitDefaults() error { - if cfg.MaxConcurrentStreams == 0 { - cfg.MaxConcurrentStreams = 128 - } - - return nil -} diff --git a/plugins/http/config/ip.go b/plugins/http/config/ip.go deleted file mode 100644 index c4981f74..00000000 --- a/plugins/http/config/ip.go +++ /dev/null @@ -1,26 +0,0 @@ -package config - -import "net" - -// Cidrs is a slice of IPNet addresses -type Cidrs []*net.IPNet - -// IsTrusted checks if the ip address exists in the provided in the config addresses -func (c *Cidrs) IsTrusted(ip string) bool { - if len(*c) == 0 { - return false - } - - i := net.ParseIP(ip) - if i == nil { - return false - } - - for _, cird := range *c { - if cird.Contains(i) { - return true - } - } - - return false -} diff --git a/plugins/http/config/ssl.go b/plugins/http/config/ssl.go deleted file mode 100644 index eb2b72b5..00000000 --- a/plugins/http/config/ssl.go +++ /dev/null @@ -1,84 +0,0 @@ -package config - -import ( - "os" - "strconv" - "strings" - - "github.com/spiral/errors" -) - -// SSL defines https server configuration. -type SSL struct { - // Address to listen as HTTPS server, defaults to 0.0.0.0:443. - Address string - - // Redirect when enabled forces all http connections to switch to https. - Redirect bool - - // Key defined private server key. - Key string - - // Cert is https certificate. - Cert string - - // Root CA file - RootCA string `mapstructure:"root_ca"` - - // internal - host string - Port int -} - -func (s *SSL) Valid() error { - const op = errors.Op("ssl_valid") - - parts := strings.Split(s.Address, ":") - switch len(parts) { - // :443 form - // localhost:443 form - // use 0.0.0.0 as host and 443 as port - case 2: - if parts[0] == "" { - s.host = "0.0.0.0" - } else { - s.host = parts[0] - } - - port, err := strconv.Atoi(parts[1]) - if err != nil { - return errors.E(op, err) - } - s.Port = port - default: - return errors.E(op, errors.Errorf("unknown format, accepted format is [:<port> or <host>:<port>], provided: %s", s.Address)) - } - - if _, err := os.Stat(s.Key); err != nil { - if os.IsNotExist(err) { - return errors.E(op, errors.Errorf("key file '%s' does not exists", s.Key)) - } - - return err - } - - if _, err := os.Stat(s.Cert); err != nil { - if os.IsNotExist(err) { - return errors.E(op, errors.Errorf("cert file '%s' does not exists", s.Cert)) - } - - return err - } - - // RootCA is optional, but if provided - check it - if s.RootCA != "" { - if _, err := os.Stat(s.RootCA); err != nil { - if os.IsNotExist(err) { - return errors.E(op, errors.Errorf("root ca path provided, but path '%s' does not exists", s.RootCA)) - } - return err - } - } - - return nil -} diff --git a/plugins/http/config/ssl_config_test.go b/plugins/http/config/ssl_config_test.go deleted file mode 100644 index 1f5fef0a..00000000 --- a/plugins/http/config/ssl_config_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package config - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestSSL_Valid1(t *testing.T) { - conf := &SSL{ - Address: "", - Redirect: false, - Key: "", - Cert: "", - RootCA: "", - host: "", - Port: 0, - } - - err := conf.Valid() - assert.Error(t, err) -} - -func TestSSL_Valid2(t *testing.T) { - conf := &SSL{ - Address: ":hello", - Redirect: false, - Key: "", - Cert: "", - RootCA: "", - host: "", - Port: 0, - } - - err := conf.Valid() - assert.Error(t, err) -} - -func TestSSL_Valid3(t *testing.T) { - conf := &SSL{ - Address: ":555", - Redirect: false, - Key: "", - Cert: "", - RootCA: "", - host: "", - Port: 0, - } - - err := conf.Valid() - assert.Error(t, err) -} - -func TestSSL_Valid4(t *testing.T) { - conf := &SSL{ - Address: ":555", - Redirect: false, - Key: "../../../tests/plugins/http/fixtures/server.key", - Cert: "../../../tests/plugins/http/fixtures/server.crt", - RootCA: "", - host: "", - // private - Port: 0, - } - - err := conf.Valid() - assert.NoError(t, err) -} - -func TestSSL_Valid5(t *testing.T) { - conf := &SSL{ - Address: "a:b:c", - Redirect: false, - Key: "../../../tests/plugins/http/fixtures/server.key", - Cert: "../../../tests/plugins/http/fixtures/server.crt", - RootCA: "", - host: "", - // private - Port: 0, - } - - err := conf.Valid() - assert.Error(t, err) -} - -func TestSSL_Valid6(t *testing.T) { - conf := &SSL{ - Address: ":", - Redirect: false, - Key: "../../../tests/plugins/http/fixtures/server.key", - Cert: "../../../tests/plugins/http/fixtures/server.crt", - RootCA: "", - host: "", - // private - Port: 0, - } - - err := conf.Valid() - assert.Error(t, err) -} - -func TestSSL_Valid7(t *testing.T) { - conf := &SSL{ - Address: "localhost:555:1", - Redirect: false, - Key: "../../../tests/plugins/http/fixtures/server.key", - Cert: "../../../tests/plugins/http/fixtures/server.crt", - RootCA: "", - host: "", - // private - Port: 0, - } - - err := conf.Valid() - assert.Error(t, err) -} diff --git a/plugins/http/config/uploads_config.go b/plugins/http/config/uploads_config.go deleted file mode 100644 index 5edb0ab7..00000000 --- a/plugins/http/config/uploads_config.go +++ /dev/null @@ -1,46 +0,0 @@ -package config - -import ( - "os" - "path" - "strings" -) - -// Uploads describes file location and controls access to them. -type Uploads struct { - // Dir contains name of directory to control access to. - Dir string - - // Forbid specifies list of file extensions which are forbidden for access. - // Example: .php, .exe, .bat, .htaccess and etc. - Forbid []string -} - -// InitDefaults sets missing values to their default values. -func (cfg *Uploads) InitDefaults() error { - cfg.Forbid = []string{".php", ".exe", ".bat"} - cfg.Dir = os.TempDir() - return nil -} - -// TmpDir returns temporary directory. -func (cfg *Uploads) TmpDir() string { - if cfg.Dir != "" { - return cfg.Dir - } - - return os.TempDir() -} - -// Forbids must return true if file extension is not allowed for the upload. -func (cfg *Uploads) Forbids(filename string) bool { - ext := strings.ToLower(path.Ext(filename)) - - for _, v := range cfg.Forbid { - if ext == v { - return true - } - } - - return false -} diff --git a/plugins/http/constants.go b/plugins/http/constants.go deleted file mode 100644 index c3d5c589..00000000 --- a/plugins/http/constants.go +++ /dev/null @@ -1,8 +0,0 @@ -package http - -import "net/http" - -var http2pushHeaderKey = http.CanonicalHeaderKey("http2-push") - -// TrailerHeaderKey http header key -var TrailerHeaderKey = http.CanonicalHeaderKey("trailer") diff --git a/plugins/http/errors.go b/plugins/http/errors.go deleted file mode 100644 index fb8762ef..00000000 --- a/plugins/http/errors.go +++ /dev/null @@ -1,25 +0,0 @@ -// +build !windows - -package http - -import ( - "errors" - "net" - "os" - "syscall" -) - -// Broken pipe -var errEPIPE = errors.New("EPIPE(32) -> connection reset by peer") - -// handleWriteError just check if error was caused by aborted connection on linux -func handleWriteError(err error) error { - if netErr, ok2 := err.(*net.OpError); ok2 { - if syscallErr, ok3 := netErr.Err.(*os.SyscallError); ok3 { - if syscallErr.Err == syscall.EPIPE { - return errEPIPE - } - } - } - return err -} diff --git a/plugins/http/errors_windows.go b/plugins/http/errors_windows.go deleted file mode 100644 index 3d0ba04c..00000000 --- a/plugins/http/errors_windows.go +++ /dev/null @@ -1,27 +0,0 @@ -// +build windows - -package http - -import ( - "errors" - "net" - "os" - "syscall" -) - -//Software caused connection abort. -//An established connection was aborted by the software in your host computer, -//possibly due to a data transmission time-out or protocol error. -var errEPIPE = errors.New("WSAECONNABORTED (10053) -> an established connection was aborted by peer") - -// handleWriteError just check if error was caused by aborted connection on windows -func handleWriteError(err error) error { - if netErr, ok2 := err.(*net.OpError); ok2 { - if syscallErr, ok3 := netErr.Err.(*os.SyscallError); ok3 { - if syscallErr.Err == syscall.WSAECONNABORTED { - return errEPIPE - } - } - } - return err -} diff --git a/plugins/http/handler.go b/plugins/http/handler.go deleted file mode 100644 index 0e7481b5..00000000 --- a/plugins/http/handler.go +++ /dev/null @@ -1,230 +0,0 @@ -package http - -import ( - "net" - "net/http" - "strconv" - "strings" - "sync" - "time" - - "github.com/hashicorp/go-multierror" - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/pkg/events" - "github.com/spiral/roadrunner/v2/pkg/pool" - "github.com/spiral/roadrunner/v2/plugins/http/config" - "github.com/spiral/roadrunner/v2/plugins/logger" -) - -// MB is 1024 bytes -const MB uint64 = 1024 * 1024 - -// ErrorEvent represents singular http error event. -type ErrorEvent struct { - // Request contains client request, must not be stored. - Request *http.Request - - // Error - associated error, if any. - Error error - - // event timings - start time.Time - elapsed time.Duration -} - -// Elapsed returns duration of the invocation. -func (e *ErrorEvent) Elapsed() time.Duration { - return e.elapsed -} - -// ResponseEvent represents singular http response event. -type ResponseEvent struct { - // Request contains client request, must not be stored. - Request *Request - - // Response contains service response. - Response *Response - - // event timings - start time.Time - elapsed time.Duration -} - -// Elapsed returns duration of the invocation. -func (e *ResponseEvent) Elapsed() time.Duration { - return e.elapsed -} - -// Handler serves http connections to underlying PHP application using PSR-7 protocol. Context will include request headers, -// parsed files and query, payload will include parsed form dataTree (if any). -type Handler struct { - maxRequestSize uint64 - uploads config.Uploads - trusted config.Cidrs - log logger.Logger - pool pool.Pool - mul sync.Mutex - lsn events.Listener -} - -// NewHandler return handle interface implementation -func NewHandler(maxReqSize uint64, uploads config.Uploads, trusted config.Cidrs, pool pool.Pool) (*Handler, error) { - if pool == nil { - return nil, errors.E(errors.Str("pool should be initialized")) - } - return &Handler{ - maxRequestSize: maxReqSize * MB, - uploads: uploads, - pool: pool, - trusted: trusted, - }, nil -} - -// AddListener attaches handler event controller. -func (h *Handler) AddListener(l events.Listener) { - h.mul.Lock() - defer h.mul.Unlock() - - h.lsn = l -} - -// mdwr serve using PSR-7 requests passed to underlying application. Attempts to serve static files first if enabled. -func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - const op = errors.Op("http_plugin_serve_http") - start := time.Now() - - // validating request size - if h.maxRequestSize != 0 { - err := h.maxSize(w, r, start, op) - if err != nil { - return - } - } - - req, err := NewRequest(r, h.uploads) - if err != nil { - h.handleError(w, r, err, start) - return - } - - // proxy IP resolution - h.resolveIP(req) - - req.Open(h.log) - defer req.Close(h.log) - - p, err := req.Payload() - if err != nil { - h.handleError(w, r, err, start) - return - } - - rsp, err := h.pool.Exec(p) - if err != nil { - h.handleError(w, r, err, start) - return - } - - resp, err := NewResponse(rsp) - if err != nil { - h.handleError(w, r, err, start) - return - } - - h.handleResponse(req, resp, start) - err = resp.Write(w) - if err != nil { - h.handleError(w, r, err, start) - } -} - -func (h *Handler) maxSize(w http.ResponseWriter, r *http.Request, start time.Time, op errors.Op) error { - if length := r.Header.Get("content-length"); length != "" { - if size, err := strconv.ParseInt(length, 10, 64); err != nil { - h.handleError(w, r, err, start) - return err - } else if size > int64(h.maxRequestSize) { - h.handleError(w, r, errors.E(op, errors.Str("request body max size is exceeded")), start) - return err - } - } - return nil -} - -// handleError sends error. -func (h *Handler) handleError(w http.ResponseWriter, r *http.Request, err error, start time.Time) { - h.mul.Lock() - defer h.mul.Unlock() - // if pipe is broken, there is no sense to write the header - // in this case we just report about error - if err == errEPIPE { - h.throw(ErrorEvent{Request: r, Error: err, start: start, elapsed: time.Since(start)}) - return - } - err = multierror.Append(err) - // ResponseWriter is ok, write the error code - w.WriteHeader(500) - _, err2 := w.Write([]byte(err.Error())) - // error during the writing to the ResponseWriter - if err2 != nil { - err = multierror.Append(err2, err) - // concat original error with ResponseWriter error - h.throw(ErrorEvent{Request: r, Error: errors.E(err), start: start, elapsed: time.Since(start)}) - return - } - h.throw(ErrorEvent{Request: r, Error: err, start: start, elapsed: time.Since(start)}) -} - -// handleResponse triggers response event. -func (h *Handler) handleResponse(req *Request, resp *Response, start time.Time) { - h.throw(ResponseEvent{Request: req, Response: resp, start: start, elapsed: time.Since(start)}) -} - -// throw invokes event handler if any. -func (h *Handler) throw(event interface{}) { - if h.lsn != nil { - h.lsn(event) - } -} - -// get real ip passing multiple proxy -func (h *Handler) resolveIP(r *Request) { - if h.trusted.IsTrusted(r.RemoteAddr) == false { - return - } - - if r.Header.Get("X-Forwarded-For") != "" { - ips := strings.Split(r.Header.Get("X-Forwarded-For"), ",") - ipCount := len(ips) - - for i := ipCount - 1; i >= 0; i-- { - addr := strings.TrimSpace(ips[i]) - if net.ParseIP(addr) != nil { - r.RemoteAddr = addr - return - } - } - - return - } - - // The logic here is the following: - // In general case, we only expect X-Real-Ip header. If it exist, we get the IP address from header and set request Remote address - // But, if there is no X-Real-Ip header, we also trying to check CloudFlare headers - // True-Client-IP is a general CF header in which copied information from X-Real-Ip in CF. - // CF-Connecting-IP is an Enterprise feature and we check it last in order. - // This operations are near O(1) because Headers struct are the map type -> type MIMEHeader map[string][]string - if r.Header.Get("X-Real-Ip") != "" { - r.RemoteAddr = fetchIP(r.Header.Get("X-Real-Ip")) - return - } - - if r.Header.Get("True-Client-IP") != "" { - r.RemoteAddr = fetchIP(r.Header.Get("True-Client-IP")) - return - } - - if r.Header.Get("CF-Connecting-IP") != "" { - r.RemoteAddr = fetchIP(r.Header.Get("CF-Connecting-IP")) - } -} diff --git a/plugins/http/parse.go b/plugins/http/parse.go deleted file mode 100644 index 780e1279..00000000 --- a/plugins/http/parse.go +++ /dev/null @@ -1,149 +0,0 @@ -package http - -import ( - "net/http" - - "github.com/spiral/roadrunner/v2/plugins/http/config" -) - -// MaxLevel defines maximum tree depth for incoming request data and files. -const MaxLevel = 127 - -type dataTree map[string]interface{} -type fileTree map[string]interface{} - -// parseData parses incoming request body into data tree. -func parseData(r *http.Request) dataTree { - data := make(dataTree) - if r.PostForm != nil { - for k, v := range r.PostForm { - data.push(k, v) - } - } - - if r.MultipartForm != nil { - for k, v := range r.MultipartForm.Value { - data.push(k, v) - } - } - - return data -} - -// pushes value into data tree. -func (d dataTree) push(k string, v []string) { - keys := FetchIndexes(k) - if len(keys) <= MaxLevel { - d.mount(keys, v) - } -} - -// mount mounts data tree recursively. -func (d dataTree) mount(i []string, v []string) { - if len(i) == 1 { - // single value context (last element) - d[i[0]] = v[len(v)-1] - return - } - - if len(i) == 2 && i[1] == "" { - // non associated array of elements - d[i[0]] = v - return - } - - if p, ok := d[i[0]]; ok { - p.(dataTree).mount(i[1:], v) - return - } - - d[i[0]] = make(dataTree) - d[i[0]].(dataTree).mount(i[1:], v) -} - -// parse incoming dataTree request into JSON (including contentMultipart form dataTree) -func parseUploads(r *http.Request, cfg config.Uploads) *Uploads { - u := &Uploads{ - cfg: cfg, - tree: make(fileTree), - list: make([]*FileUpload, 0), - } - - for k, v := range r.MultipartForm.File { - files := make([]*FileUpload, 0, len(v)) - for _, f := range v { - files = append(files, NewUpload(f)) - } - - u.list = append(u.list, files...) - u.tree.push(k, files) - } - - return u -} - -// pushes new file upload into it's proper place. -func (d fileTree) push(k string, v []*FileUpload) { - keys := FetchIndexes(k) - if len(keys) <= MaxLevel { - d.mount(keys, v) - } -} - -// mount mounts data tree recursively. -func (d fileTree) mount(i []string, v []*FileUpload) { - if len(i) == 1 { - // single value context - d[i[0]] = v[0] - return - } - - if len(i) == 2 && i[1] == "" { - // non associated array of elements - d[i[0]] = v - return - } - - if p, ok := d[i[0]]; ok { - p.(fileTree).mount(i[1:], v) - return - } - - d[i[0]] = make(fileTree) - d[i[0]].(fileTree).mount(i[1:], v) -} - -// FetchIndexes parses input name and splits it into separate indexes list. -func FetchIndexes(s string) []string { - var ( - pos int - ch string - keys = make([]string, 1) - ) - - for _, c := range s { - ch = string(c) - switch ch { - case " ": - // ignore all spaces - continue - case "[": - pos = 1 - continue - case "]": - if pos == 1 { - keys = append(keys, "") - } - pos = 2 - default: - if pos == 1 || pos == 2 { - keys = append(keys, "") - } - - keys[len(keys)-1] += ch - pos = 0 - } - } - - return keys -} diff --git a/plugins/http/plugin.go b/plugins/http/plugin.go deleted file mode 100644 index 3672f5ac..00000000 --- a/plugins/http/plugin.go +++ /dev/null @@ -1,564 +0,0 @@ -package http - -import ( - "context" - "crypto/tls" - "crypto/x509" - "fmt" - "io/ioutil" - "net/http" - "net/http/fcgi" - "net/url" - "strings" - "sync" - - "github.com/hashicorp/go-multierror" - endure "github.com/spiral/endure/pkg/container" - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/pkg/pool" - "github.com/spiral/roadrunner/v2/pkg/worker" - "github.com/spiral/roadrunner/v2/plugins/checker" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/http/attributes" - httpConfig "github.com/spiral/roadrunner/v2/plugins/http/config" - "github.com/spiral/roadrunner/v2/plugins/logger" - "github.com/spiral/roadrunner/v2/plugins/server" - "github.com/spiral/roadrunner/v2/utils" - "golang.org/x/net/http2" - "golang.org/x/net/http2/h2c" - "golang.org/x/sys/cpu" -) - -const ( - // PluginName declares plugin name. - PluginName = "http" - - // RR_HTTP env variable key (internal) if the HTTP presents - RR_MODE = "RR_MODE" //nolint:golint,stylecheck - - // HTTPS_SCHEME - HTTPS_SCHEME = "https" //nolint:golint,stylecheck -) - -// Middleware interface -type Middleware interface { - Middleware(f http.Handler) http.HandlerFunc -} - -type middleware map[string]Middleware - -// Plugin manages pool, http servers. The main http plugin structure -type Plugin struct { - sync.RWMutex - - // plugins - server server.Server - log logger.Logger - - cfg *httpConfig.HTTP `mapstructure:"http"` - // middlewares to chain - mdwr middleware - - // Pool which attached to all servers - pool pool.Pool - - // servers RR handler - handler *Handler - - // servers - http *http.Server - https *http.Server - fcgi *http.Server -} - -// Init must return configure svc and return true if svc hasStatus enabled. Must return error in case of -// misconfiguration. Services must not be used without proper configuration pushed first. -func (s *Plugin) Init(cfg config.Configurer, log logger.Logger, server server.Server) error { - const op = errors.Op("http_plugin_init") - if !cfg.Has(PluginName) { - return errors.E(op, errors.Disabled) - } - - err := cfg.UnmarshalKey(PluginName, &s.cfg) - if err != nil { - return errors.E(op, err) - } - - err = s.cfg.InitDefaults() - if err != nil { - return errors.E(op, err) - } - - s.log = log - s.mdwr = make(map[string]Middleware) - - if !s.cfg.EnableHTTP() && !s.cfg.EnableTLS() && !s.cfg.EnableFCGI() { - return errors.E(op, errors.Disabled) - } - - // init if nil - if s.cfg.Env == nil { - s.cfg.Env = make(map[string]string) - } - - s.cfg.Env[RR_MODE] = "http" - - s.pool, err = server.NewWorkerPool(context.Background(), pool.Config{ - Debug: s.cfg.Pool.Debug, - NumWorkers: s.cfg.Pool.NumWorkers, - MaxJobs: s.cfg.Pool.MaxJobs, - AllocateTimeout: s.cfg.Pool.AllocateTimeout, - DestroyTimeout: s.cfg.Pool.DestroyTimeout, - Supervisor: s.cfg.Pool.Supervisor, - }, s.cfg.Env, s.logCallback) - if err != nil { - return errors.E(op, err) - } - - s.server = server - - return nil -} - -func (s *Plugin) logCallback(event interface{}) { - if ev, ok := event.(ResponseEvent); ok { - s.log.Debug("", - "remote", ev.Request.RemoteAddr, - "ts", ev.Elapsed().String(), - "resp.status", ev.Response.Status, - "method", ev.Request.Method, - "uri", ev.Request.URI, - ) - } -} - -// Serve serves the svc. -func (s *Plugin) Serve() chan error { - s.Lock() - defer s.Unlock() - - const op = errors.Op("http_plugin_serve") - errCh := make(chan error, 2) - - var err error - s.handler, err = NewHandler( - s.cfg.MaxRequestSize, - *s.cfg.Uploads, - s.cfg.Cidrs, - s.pool, - ) - if err != nil { - errCh <- errors.E(op, err) - return errCh - } - - s.handler.AddListener(s.logCallback) - - if s.cfg.EnableHTTP() { - if s.cfg.EnableH2C() { - s.http = &http.Server{Handler: h2c.NewHandler(s, &http2.Server{})} - } else { - s.http = &http.Server{Handler: s} - } - } - - if s.cfg.EnableTLS() { - s.https = s.initSSL() - if s.cfg.SSLConfig.RootCA != "" { - err = s.appendRootCa() - if err != nil { - errCh <- errors.E(op, err) - return errCh - } - } - - // if HTTP2Config not nil - if s.cfg.HTTP2Config != nil { - if err := s.initHTTP2(); err != nil { - errCh <- errors.E(op, err) - return errCh - } - } - } - - if s.cfg.EnableFCGI() { - s.fcgi = &http.Server{Handler: s} - } - - // apply middlewares before starting the server - if len(s.mdwr) > 0 { - s.addMiddlewares() - } - - if s.http != nil { - go func() { - l, err := utils.CreateListener(s.cfg.Address) - if err != nil { - errCh <- errors.E(op, err) - return - } - - err = s.http.Serve(l) - if err != nil && err != http.ErrServerClosed { - errCh <- errors.E(op, err) - return - } - }() - } - - if s.https != nil { - go func() { - l, err := utils.CreateListener(s.cfg.SSLConfig.Address) - if err != nil { - errCh <- errors.E(op, err) - return - } - - err = s.https.ServeTLS( - l, - s.cfg.SSLConfig.Cert, - s.cfg.SSLConfig.Key, - ) - - if err != nil && err != http.ErrServerClosed { - errCh <- errors.E(op, err) - return - } - }() - } - - if s.fcgi != nil { - go func() { - httpErr := s.serveFCGI() - if httpErr != nil && httpErr != http.ErrServerClosed { - errCh <- errors.E(op, httpErr) - return - } - }() - } - - return errCh -} - -// Stop stops the http. -func (s *Plugin) Stop() error { - s.Lock() - defer s.Unlock() - - var err error - if s.fcgi != nil { - err = s.fcgi.Shutdown(context.Background()) - if err != nil && err != http.ErrServerClosed { - s.log.Error("error shutting down the fcgi server", "error", err) - // write error and try to stop other transport - err = multierror.Append(err) - } - } - - if s.https != nil { - err = s.https.Shutdown(context.Background()) - if err != nil && err != http.ErrServerClosed { - s.log.Error("error shutting down the https server", "error", err) - // write error and try to stop other transport - err = multierror.Append(err) - } - } - - if s.http != nil { - err = s.http.Shutdown(context.Background()) - if err != nil && err != http.ErrServerClosed { - s.log.Error("error shutting down the http server", "error", err) - // write error and try to stop other transport - err = multierror.Append(err) - } - } - - s.pool.Destroy(context.Background()) - - return err -} - -// ServeHTTP handles connection using set of middleware and pool PSR-7 server. -func (s *Plugin) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if headerContainsUpgrade(r, s) { - http.Error(w, "server does not support upgrade header", http.StatusInternalServerError) - return - } - - if s.https != nil && r.TLS == nil && s.cfg.SSLConfig.Redirect { - s.redirect(w, r) - return - } - - if s.https != nil && r.TLS != nil { - w.Header().Add("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload") - } - - r = attributes.Init(r) - // protect the case, when user send Reset and we are replacing handler with pool - s.RLock() - s.handler.ServeHTTP(w, r) - s.RUnlock() -} - -// Workers returns associated pool workers -func (s *Plugin) Workers() []worker.BaseProcess { - workers := s.pool.Workers() - baseWorkers := make([]worker.BaseProcess, 0, len(workers)) - for i := 0; i < len(workers); i++ { - baseWorkers = append(baseWorkers, worker.FromSync(workers[i].(*worker.SyncWorkerImpl))) - } - return baseWorkers -} - -// Name returns endure.Named interface implementation -func (s *Plugin) Name() string { - return PluginName -} - -// Reset destroys the old pool and replaces it with new one, waiting for old pool to die -func (s *Plugin) Reset() error { - s.Lock() - defer s.Unlock() - const op = errors.Op("http_plugin_reset") - s.log.Info("HTTP plugin got restart request. Restarting...") - s.pool.Destroy(context.Background()) - s.pool = nil - - var err error - s.pool, err = s.server.NewWorkerPool(context.Background(), pool.Config{ - Debug: s.cfg.Pool.Debug, - NumWorkers: s.cfg.Pool.NumWorkers, - MaxJobs: s.cfg.Pool.MaxJobs, - AllocateTimeout: s.cfg.Pool.AllocateTimeout, - DestroyTimeout: s.cfg.Pool.DestroyTimeout, - Supervisor: s.cfg.Pool.Supervisor, - }, s.cfg.Env, s.logCallback) - if err != nil { - return errors.E(op, err) - } - - s.log.Info("HTTP listeners successfully re-added") - - s.log.Info("HTTP workers Pool successfully restarted") - s.handler, err = NewHandler( - s.cfg.MaxRequestSize, - *s.cfg.Uploads, - s.cfg.Cidrs, - s.pool, - ) - if err != nil { - return errors.E(op, err) - } - - s.log.Info("HTTP plugin successfully restarted") - return nil -} - -// Collects collecting http middlewares -func (s *Plugin) Collects() []interface{} { - return []interface{}{ - s.AddMiddleware, - } -} - -// AddMiddleware is base requirement for the middleware (name and Middleware) -func (s *Plugin) AddMiddleware(name endure.Named, m Middleware) { - s.mdwr[name.Name()] = m -} - -// Status return status of the particular plugin -func (s *Plugin) Status() checker.Status { - workers := s.Workers() - for i := 0; i < len(workers); i++ { - if workers[i].State().IsActive() { - return checker.Status{ - Code: http.StatusOK, - } - } - } - // if there are no workers, threat this as error - return checker.Status{ - Code: http.StatusInternalServerError, - } -} - -func (s *Plugin) redirect(w http.ResponseWriter, r *http.Request) { - target := &url.URL{ - Scheme: HTTPS_SCHEME, - // host or host:port - Host: s.tlsAddr(r.Host, false), - Path: r.URL.Path, - RawQuery: r.URL.RawQuery, - } - - http.Redirect(w, r, target.String(), http.StatusTemporaryRedirect) -} - -//go:inline -func headerContainsUpgrade(r *http.Request, s *Plugin) bool { - if _, ok := r.Header["Upgrade"]; ok { - // https://golang.org/pkg/net/http/#Hijacker - s.log.Error("server does not support Upgrade header") - return true - } - return false -} - -// append RootCA to the https server TLS config -func (s *Plugin) appendRootCa() error { - const op = errors.Op("http_plugin_append_root_ca") - rootCAs, err := x509.SystemCertPool() - if err != nil { - return nil - } - if rootCAs == nil { - rootCAs = x509.NewCertPool() - } - - CA, err := ioutil.ReadFile(s.cfg.SSLConfig.RootCA) - if err != nil { - return err - } - - // should append our CA cert - ok := rootCAs.AppendCertsFromPEM(CA) - if !ok { - return errors.E(op, errors.Str("could not append Certs from PEM")) - } - // disable "G402 (CWE-295): TLS MinVersion too low. (Confidence: HIGH, Severity: HIGH)" - // #nosec G402 - cfg := &tls.Config{ - InsecureSkipVerify: false, - RootCAs: rootCAs, - } - s.http.TLSConfig = cfg - - return nil -} - -// Init https server -func (s *Plugin) initSSL() *http.Server { - var topCipherSuites []uint16 - var defaultCipherSuitesTLS13 []uint16 - - hasGCMAsmAMD64 := cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ - hasGCMAsmARM64 := cpu.ARM64.HasAES && cpu.ARM64.HasPMULL - // Keep in sync with crypto/aes/cipher_s390x.go. - hasGCMAsmS390X := cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM) - - hasGCMAsm := hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X - - if hasGCMAsm { - // If AES-GCM hardware is provided then priorities AES-GCM - // cipher suites. - topCipherSuites = []uint16{ - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, - tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, - } - defaultCipherSuitesTLS13 = []uint16{ - tls.TLS_AES_128_GCM_SHA256, - tls.TLS_CHACHA20_POLY1305_SHA256, - tls.TLS_AES_256_GCM_SHA384, - } - } else { - // Without AES-GCM hardware, we put the ChaCha20-Poly1305 - // cipher suites first. - topCipherSuites = []uint16{ - tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, - tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - } - defaultCipherSuitesTLS13 = []uint16{ - tls.TLS_CHACHA20_POLY1305_SHA256, - tls.TLS_AES_128_GCM_SHA256, - tls.TLS_AES_256_GCM_SHA384, - } - } - - DefaultCipherSuites := make([]uint16, 0, 22) - DefaultCipherSuites = append(DefaultCipherSuites, topCipherSuites...) - DefaultCipherSuites = append(DefaultCipherSuites, defaultCipherSuitesTLS13...) - - sslServer := &http.Server{ - Addr: s.tlsAddr(s.cfg.Address, true), - Handler: s, - TLSConfig: &tls.Config{ - CurvePreferences: []tls.CurveID{ - tls.CurveP256, - tls.CurveP384, - tls.CurveP521, - tls.X25519, - }, - CipherSuites: DefaultCipherSuites, - MinVersion: tls.VersionTLS12, - PreferServerCipherSuites: true, - }, - } - - return sslServer -} - -// init http/2 server -func (s *Plugin) initHTTP2() error { - return http2.ConfigureServer(s.https, &http2.Server{ - MaxConcurrentStreams: s.cfg.HTTP2Config.MaxConcurrentStreams, - }) -} - -// serveFCGI starts FastCGI server. -func (s *Plugin) serveFCGI() error { - l, err := utils.CreateListener(s.cfg.FCGIConfig.Address) - if err != nil { - return err - } - - err = fcgi.Serve(l, s.fcgi.Handler) - if err != nil { - return err - } - - return nil -} - -// tlsAddr replaces listen or host port with port configured by SSLConfig config. -func (s *Plugin) tlsAddr(host string, forcePort bool) string { - // remove current forcePort first - host = strings.Split(host, ":")[0] - - if forcePort || s.cfg.SSLConfig.Port != 443 { - host = fmt.Sprintf("%s:%v", host, s.cfg.SSLConfig.Port) - } - - return host -} - -func (s *Plugin) addMiddlewares() { - if s.http != nil { - applyMiddlewares(s.http, s.mdwr, s.cfg.Middleware, s.log) - } - if s.https != nil { - applyMiddlewares(s.https, s.mdwr, s.cfg.Middleware, s.log) - } - - if s.fcgi != nil { - applyMiddlewares(s.fcgi, s.mdwr, s.cfg.Middleware, s.log) - } -} - -func applyMiddlewares(server *http.Server, middlewares map[string]Middleware, order []string, log logger.Logger) { - for i := 0; i < len(order); i++ { - if mdwr, ok := middlewares[order[i]]; ok { - server.Handler = mdwr.Middleware(server.Handler) - } else { - log.Warn("requested middleware does not exist", "requested", order[i]) - } - } -} diff --git a/plugins/http/request.go b/plugins/http/request.go deleted file mode 100644 index a1398819..00000000 --- a/plugins/http/request.go +++ /dev/null @@ -1,187 +0,0 @@ -package http - -import ( - "fmt" - "io/ioutil" - "net" - "net/http" - "net/url" - "strings" - - j "github.com/json-iterator/go" - "github.com/spiral/roadrunner/v2/pkg/payload" - "github.com/spiral/roadrunner/v2/plugins/http/attributes" - "github.com/spiral/roadrunner/v2/plugins/http/config" - "github.com/spiral/roadrunner/v2/plugins/logger" -) - -var json = j.ConfigCompatibleWithStandardLibrary - -const ( - defaultMaxMemory = 32 << 20 // 32 MB - contentNone = iota + 900 - contentStream - contentMultipart - contentFormData -) - -// Request maps net/http requests to PSR7 compatible structure and managed state of temporary uploaded files. -type Request struct { - // RemoteAddr contains ip address of client, make sure to check X-Real-Ip and X-Forwarded-For for real client address. - RemoteAddr string `json:"remoteAddr"` - - // Protocol includes HTTP protocol version. - Protocol string `json:"protocol"` - - // Method contains name of HTTP method used for the request. - Method string `json:"method"` - - // URI contains full request URI with scheme and query. - URI string `json:"uri"` - - // Header contains list of request headers. - Header http.Header `json:"headers"` - - // Cookies contains list of request cookies. - Cookies map[string]string `json:"cookies"` - - // RawQuery contains non parsed query string (to be parsed on php end). - RawQuery string `json:"rawQuery"` - - // Parsed indicates that request body has been parsed on RR end. - Parsed bool `json:"parsed"` - - // Uploads contains list of uploaded files, their names, sized and associations with temporary files. - Uploads *Uploads `json:"uploads"` - - // Attributes can be set by chained mdwr to safely pass value from Golang to PHP. See: GetAttribute, SetAttribute functions. - Attributes map[string]interface{} `json:"attributes"` - - // request body can be parsedData or []byte - body interface{} -} - -func fetchIP(pair string) string { - if !strings.ContainsRune(pair, ':') { - return pair - } - - addr, _, _ := net.SplitHostPort(pair) - return addr -} - -// NewRequest creates new PSR7 compatible request using net/http request. -func NewRequest(r *http.Request, cfg config.Uploads) (*Request, error) { - req := &Request{ - RemoteAddr: fetchIP(r.RemoteAddr), - Protocol: r.Proto, - Method: r.Method, - URI: uri(r), - Header: r.Header, - Cookies: make(map[string]string), - RawQuery: r.URL.RawQuery, - Attributes: attributes.All(r), - } - - for _, c := range r.Cookies() { - if v, err := url.QueryUnescape(c.Value); err == nil { - req.Cookies[c.Name] = v - } - } - - switch req.contentType() { - case contentNone: - return req, nil - - case contentStream: - var err error - req.body, err = ioutil.ReadAll(r.Body) - return req, err - - case contentMultipart: - if err := r.ParseMultipartForm(defaultMaxMemory); err != nil { - return nil, err - } - - req.Uploads = parseUploads(r, cfg) - fallthrough - case contentFormData: - if err := r.ParseForm(); err != nil { - return nil, err - } - - req.body = parseData(r) - } - - req.Parsed = true - return req, nil -} - -// Open moves all uploaded files to temporary directory so it can be given to php later. -func (r *Request) Open(log logger.Logger) { - if r.Uploads == nil { - return - } - - r.Uploads.Open(log) -} - -// Close clears all temp file uploads -func (r *Request) Close(log logger.Logger) { - if r.Uploads == nil { - return - } - - r.Uploads.Clear(log) -} - -// Payload request marshaled RoadRunner payload based on PSR7 data. values encode method is JSON. Make sure to open -// files prior to calling this method. -func (r *Request) Payload() (payload.Payload, error) { - p := payload.Payload{} - - var err error - if p.Context, err = json.Marshal(r); err != nil { - return payload.Payload{}, err - } - - if r.Parsed { - if p.Body, err = json.Marshal(r.body); err != nil { - return payload.Payload{}, err - } - } else if r.body != nil { - p.Body = r.body.([]byte) - } - - return p, nil -} - -// contentType returns the payload content type. -func (r *Request) contentType() int { - if r.Method == "HEAD" || r.Method == "OPTIONS" { - return contentNone - } - - ct := r.Header.Get("content-type") - if strings.Contains(ct, "application/x-www-form-urlencoded") { - return contentFormData - } - - if strings.Contains(ct, "multipart/form-data") { - return contentMultipart - } - - return contentStream -} - -// uri fetches full uri from request in a form of string (including https scheme if TLS connection is enabled). -func uri(r *http.Request) string { - if r.URL.Host != "" { - return r.URL.String() - } - if r.TLS != nil { - return fmt.Sprintf("https://%s%s", r.Host, r.URL.String()) - } - - return fmt.Sprintf("http://%s%s", r.Host, r.URL.String()) -} diff --git a/plugins/http/response.go b/plugins/http/response.go deleted file mode 100644 index 17049ce1..00000000 --- a/plugins/http/response.go +++ /dev/null @@ -1,105 +0,0 @@ -package http - -import ( - "io" - "net/http" - "strings" - "sync" - - "github.com/spiral/roadrunner/v2/pkg/payload" -) - -// Response handles PSR7 response logic. -type Response struct { - // Status contains response status. - Status int `json:"status"` - - // Header contains list of response headers. - Headers map[string][]string `json:"headers"` - - // associated Body payload. - Body interface{} - sync.Mutex -} - -// NewResponse creates new response based on given pool payload. -func NewResponse(p payload.Payload) (*Response, error) { - r := &Response{Body: p.Body} - if err := json.Unmarshal(p.Context, r); err != nil { - return nil, err - } - - return r, nil -} - -// Write writes response headers, status and body into ResponseWriter. -func (r *Response) Write(w http.ResponseWriter) error { - // INFO map is the reference type in golang - p := handlePushHeaders(r.Headers) - if pusher, ok := w.(http.Pusher); ok { - for _, v := range p { - err := pusher.Push(v, nil) - if err != nil { - return err - } - } - } - - handleTrailers(r.Headers) - for n, h := range r.Headers { - for _, v := range h { - w.Header().Add(n, v) - } - } - - w.WriteHeader(r.Status) - - if data, ok := r.Body.([]byte); ok { - _, err := w.Write(data) - if err != nil { - return handleWriteError(err) - } - } - - if rc, ok := r.Body.(io.Reader); ok { - if _, err := io.Copy(w, rc); err != nil { - return err - } - } - - return nil -} - -func handlePushHeaders(h map[string][]string) []string { - var p []string - pushHeader, ok := h[http2pushHeaderKey] - if !ok { - return p - } - - p = append(p, pushHeader...) - - delete(h, http2pushHeaderKey) - - return p -} - -func handleTrailers(h map[string][]string) { - trailers, ok := h[TrailerHeaderKey] - if !ok { - return - } - - for _, tr := range trailers { - for _, n := range strings.Split(tr, ",") { - n = strings.Trim(n, "\t ") - if v, ok := h[n]; ok { - h["Trailer:"+n] = v - - delete(h, n) - } - } - } - - delete(h, TrailerHeaderKey) -} diff --git a/plugins/http/uploads.go b/plugins/http/uploads.go deleted file mode 100644 index f9f8e1c8..00000000 --- a/plugins/http/uploads.go +++ /dev/null @@ -1,159 +0,0 @@ -package http - -import ( - "github.com/spiral/roadrunner/v2/plugins/http/config" - "github.com/spiral/roadrunner/v2/plugins/logger" - - "io" - "io/ioutil" - "mime/multipart" - "os" - "sync" -) - -const ( - // UploadErrorOK - no error, the file uploaded with success. - UploadErrorOK = 0 - - // UploadErrorNoFile - no file was uploaded. - UploadErrorNoFile = 4 - - // UploadErrorNoTmpDir - missing a temporary folder. - UploadErrorNoTmpDir = 6 - - // UploadErrorCantWrite - failed to write file to disk. - UploadErrorCantWrite = 7 - - // UploadErrorExtension - forbidden file extension. - UploadErrorExtension = 8 -) - -// Uploads tree manages uploaded files tree and temporary files. -type Uploads struct { - // associated temp directory and forbidden extensions. - cfg config.Uploads - - // pre processed data tree for Uploads. - tree fileTree - - // flat list of all file Uploads. - list []*FileUpload -} - -// MarshalJSON marshal tree tree into JSON. -func (u *Uploads) MarshalJSON() ([]byte, error) { - return json.Marshal(u.tree) -} - -// Open moves all uploaded files to temp directory, return error in case of issue with temp directory. File errors -// will be handled individually. -func (u *Uploads) Open(log logger.Logger) { - var wg sync.WaitGroup - for _, f := range u.list { - wg.Add(1) - go func(f *FileUpload) { - defer wg.Done() - err := f.Open(u.cfg) - if err != nil && log != nil { - log.Error("error opening the file", "err", err) - } - }(f) - } - - wg.Wait() -} - -// Clear deletes all temporary files. -func (u *Uploads) Clear(log logger.Logger) { - for _, f := range u.list { - if f.TempFilename != "" && exists(f.TempFilename) { - err := os.Remove(f.TempFilename) - if err != nil && log != nil { - log.Error("error removing the file", "err", err) - } - } - } -} - -// FileUpload represents singular file NewUpload. -type FileUpload struct { - // ID contains filename specified by the client. - Name string `json:"name"` - - // Mime contains mime-type provided by the client. - Mime string `json:"mime"` - - // Size of the uploaded file. - Size int64 `json:"size"` - - // Error indicates file upload error (if any). See http://php.net/manual/en/features.file-upload.errors.php - Error int `json:"error"` - - // TempFilename points to temporary file location. - TempFilename string `json:"tmpName"` - - // associated file header - header *multipart.FileHeader -} - -// NewUpload wraps net/http upload into PRS-7 compatible structure. -func NewUpload(f *multipart.FileHeader) *FileUpload { - return &FileUpload{ - Name: f.Filename, - Mime: f.Header.Get("Content-Type"), - Error: UploadErrorOK, - header: f, - } -} - -// Open moves file content into temporary file available for PHP. -// NOTE: -// There is 2 deferred functions, and in case of getting 2 errors from both functions -// error from close of temp file would be overwritten by error from the main file -// STACK -// DEFER FILE CLOSE (2) -// DEFER TMP CLOSE (1) -func (f *FileUpload) Open(cfg config.Uploads) (err error) { - if cfg.Forbids(f.Name) { - f.Error = UploadErrorExtension - return nil - } - - file, err := f.header.Open() - if err != nil { - f.Error = UploadErrorNoFile - return err - } - - defer func() { - // close the main file - err = file.Close() - }() - - tmp, err := ioutil.TempFile(cfg.TmpDir(), "upload") - if err != nil { - // most likely cause of this issue is missing tmp dir - f.Error = UploadErrorNoTmpDir - return err - } - - f.TempFilename = tmp.Name() - defer func() { - // close the temp file - err = tmp.Close() - }() - - if f.Size, err = io.Copy(tmp, file); err != nil { - f.Error = UploadErrorCantWrite - } - - return err -} - -// exists if file exists. -func exists(path string) bool { - if _, err := os.Stat(path); os.IsNotExist(err) { - return false - } - return true -} diff --git a/plugins/informer/interface.go b/plugins/informer/interface.go deleted file mode 100644 index 8e3b922b..00000000 --- a/plugins/informer/interface.go +++ /dev/null @@ -1,8 +0,0 @@ -package informer - -import "github.com/spiral/roadrunner/v2/pkg/worker" - -// Informer used to get workers from particular plugin or set of plugins -type Informer interface { - Workers() []worker.BaseProcess -} diff --git a/plugins/informer/plugin.go b/plugins/informer/plugin.go deleted file mode 100644 index 416c0112..00000000 --- a/plugins/informer/plugin.go +++ /dev/null @@ -1,55 +0,0 @@ -package informer - -import ( - endure "github.com/spiral/endure/pkg/container" - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/pkg/worker" - "github.com/spiral/roadrunner/v2/plugins/logger" -) - -const PluginName = "informer" - -type Plugin struct { - registry map[string]Informer - log logger.Logger -} - -func (p *Plugin) Init(log logger.Logger) error { - p.registry = make(map[string]Informer) - p.log = log - return nil -} - -// Workers provides BaseProcess slice with workers for the requested plugin -func (p *Plugin) Workers(name string) ([]worker.BaseProcess, error) { - const op = errors.Op("informer_plugin_workers") - svc, ok := p.registry[name] - if !ok { - return nil, errors.E(op, errors.Errorf("no such service: %s", name)) - } - - return svc.Workers(), nil -} - -// CollectTarget resettable service. -func (p *Plugin) CollectTarget(name endure.Named, r Informer) error { - p.registry[name.Name()] = r - return nil -} - -// Collects declares services to be collected. -func (p *Plugin) Collects() []interface{} { - return []interface{}{ - p.CollectTarget, - } -} - -// Name of the service. -func (p *Plugin) Name() string { - return PluginName -} - -// RPCService returns associated rpc service. -func (p *Plugin) RPC() interface{} { - return &rpc{srv: p, log: p.log} -} diff --git a/plugins/informer/rpc.go b/plugins/informer/rpc.go deleted file mode 100644 index c036ae96..00000000 --- a/plugins/informer/rpc.go +++ /dev/null @@ -1,54 +0,0 @@ -package informer - -import ( - "github.com/spiral/roadrunner/v2/pkg/worker" - "github.com/spiral/roadrunner/v2/plugins/logger" - "github.com/spiral/roadrunner/v2/tools" -) - -type rpc struct { - srv *Plugin - log logger.Logger -} - -// WorkerList contains list of workers. -type WorkerList struct { - // Workers is list of workers. - Workers []tools.ProcessState `json:"workers"` -} - -// List all resettable services. -func (rpc *rpc) List(_ bool, list *[]string) error { - rpc.log.Debug("Started List method") - *list = make([]string, 0, len(rpc.srv.registry)) - - for name := range rpc.srv.registry { - *list = append(*list, name) - } - rpc.log.Debug("list of services", "list", *list) - - rpc.log.Debug("successfully finished List method") - return nil -} - -// Workers state of a given service. -func (rpc *rpc) Workers(service string, list *WorkerList) error { - rpc.log.Debug("started Workers method", "service", service) - workers, err := rpc.srv.Workers(service) - if err != nil { - return err - } - - list.Workers = make([]tools.ProcessState, 0) - for _, w := range workers { - ps, err := tools.WorkerProcessState(w.(worker.BaseProcess)) - if err != nil { - continue - } - - list.Workers = append(list.Workers, ps) - } - rpc.log.Debug("list of workers", "workers", list.Workers) - rpc.log.Debug("successfully finished Workers method") - return nil -} diff --git a/plugins/kv/boltdb/config.go b/plugins/kv/boltdb/config.go deleted file mode 100644 index ebe73c25..00000000 --- a/plugins/kv/boltdb/config.go +++ /dev/null @@ -1,37 +0,0 @@ -package boltdb - -type Config struct { - // Dir is a directory to store the DB files - Dir string - // File is boltDB file. No need to create it by your own, - // boltdb driver is able to create the file, or read existing - File string - // Bucket to store data in boltDB - Bucket string - // db file permissions - Permissions int - // timeout - Interval uint `mapstructure:"interval"` -} - -// InitDefaults initializes default values for the boltdb -func (s *Config) InitDefaults() { - if s.Dir == "" { - s.Dir = "." // current dir - } - if s.Bucket == "" { - s.Bucket = "rr" // default bucket name - } - - if s.File == "" { - s.File = "rr.db" // default file name - } - - if s.Permissions == 0 { - s.Permissions = 777 // free for all - } - - if s.Interval == 0 { - s.Interval = 60 // default is 60 seconds timeout - } -} diff --git a/plugins/kv/boltdb/plugin.go b/plugins/kv/boltdb/plugin.go deleted file mode 100644 index 1e3d2c34..00000000 --- a/plugins/kv/boltdb/plugin.go +++ /dev/null @@ -1,456 +0,0 @@ -package boltdb - -import ( - "bytes" - "encoding/gob" - "os" - "path" - "strings" - "sync" - "time" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/kv" - "github.com/spiral/roadrunner/v2/plugins/logger" - bolt "go.etcd.io/bbolt" -) - -const PluginName = "boltdb" - -// BoltDB K/V storage. -type Plugin struct { - // db instance - DB *bolt.DB - // name should be UTF-8 - bucket []byte - - // config for RR integration - cfg *Config - - // logger - log logger.Logger - - // gc contains key which are contain timeouts - gc *sync.Map - // default timeout for cache cleanup is 1 minute - timeout time.Duration - - // stop is used to stop keys GC and close boltdb connection - stop chan struct{} -} - -func (s *Plugin) Init(log logger.Logger, cfg config.Configurer) error { - const op = errors.Op("boltdb_plugin_init") - - if !cfg.Has(PluginName) { - return errors.E(op, errors.Disabled) - } - - err := cfg.UnmarshalKey(PluginName, &s.cfg) - if err != nil { - return errors.E(op, errors.Disabled, err) - } - - // add default values - s.cfg.InitDefaults() - - // set the logger - s.log = log - - db, err := bolt.Open(path.Join(s.cfg.Dir, s.cfg.File), os.FileMode(s.cfg.Permissions), nil) - if err != nil { - return errors.E(op, err) - } - - // create bucket if it does not exist - // tx.Commit invokes via the db.Update - err = db.Update(func(tx *bolt.Tx) error { - const upOp = errors.Op("boltdb_plugin_update") - _, err = tx.CreateBucketIfNotExists([]byte(s.cfg.Bucket)) - if err != nil { - return errors.E(op, upOp) - } - return nil - }) - - if err != nil { - return errors.E(op, err) - } - - s.DB = db - s.bucket = []byte(s.cfg.Bucket) - s.stop = make(chan struct{}) - s.timeout = time.Duration(s.cfg.Interval) * time.Second - s.gc = &sync.Map{} - - return nil -} - -func (s *Plugin) Serve() chan error { - errCh := make(chan error, 1) - // start the TTL gc - go s.gcPhase() - - return errCh -} - -func (s *Plugin) Stop() error { - const op = errors.Op("boltdb_plugin_stop") - err := s.Close() - if err != nil { - return errors.E(op, err) - } - return nil -} - -func (s *Plugin) Has(keys ...string) (map[string]bool, error) { - const op = errors.Op("boltdb_plugin_has") - s.log.Debug("boltdb HAS method called", "args", keys) - if keys == nil { - return nil, errors.E(op, errors.NoKeys) - } - - m := make(map[string]bool, len(keys)) - - // this is readable transaction - err := s.DB.View(func(tx *bolt.Tx) error { - // Get retrieves the value for a key in the bucket. - // Returns a nil value if the key does not exist or if the key is a nested bucket. - // The returned value is only valid for the life of the transaction. - for i := range keys { - keyTrimmed := strings.TrimSpace(keys[i]) - if keyTrimmed == "" { - return errors.E(op, errors.EmptyKey) - } - b := tx.Bucket(s.bucket) - if b == nil { - return errors.E(op, errors.NoSuchBucket) - } - exist := b.Get([]byte(keys[i])) - if exist != nil { - m[keys[i]] = true - } - } - return nil - }) - if err != nil { - return nil, errors.E(op, err) - } - - s.log.Debug("boltdb HAS method finished") - return m, nil -} - -// Get retrieves the value for a key in the bucket. -// Returns a nil value if the key does not exist or if the key is a nested bucket. -// The returned value is only valid for the life of the transaction. -func (s *Plugin) Get(key string) ([]byte, error) { - const op = errors.Op("boltdb_plugin_get") - // to get cases like " " - keyTrimmed := strings.TrimSpace(key) - if keyTrimmed == "" { - return nil, errors.E(op, errors.EmptyKey) - } - - var val []byte - err := s.DB.View(func(tx *bolt.Tx) error { - b := tx.Bucket(s.bucket) - if b == nil { - return errors.E(op, errors.NoSuchBucket) - } - val = b.Get([]byte(key)) - - // try to decode values - if val != nil { - buf := bytes.NewReader(val) - decoder := gob.NewDecoder(buf) - - var i string - err := decoder.Decode(&i) - if err != nil { - // unsafe (w/o runes) convert - return errors.E(op, err) - } - - // set the value - val = []byte(i) - } - return nil - }) - if err != nil { - return nil, errors.E(op, err) - } - - return val, nil -} - -func (s *Plugin) MGet(keys ...string) (map[string]interface{}, error) { - const op = errors.Op("boltdb_plugin_mget") - // defence - if keys == nil { - return nil, errors.E(op, errors.NoKeys) - } - - // should not be empty keys - for i := range keys { - keyTrimmed := strings.TrimSpace(keys[i]) - if keyTrimmed == "" { - return nil, errors.E(op, errors.EmptyKey) - } - } - - m := make(map[string]interface{}, len(keys)) - - err := s.DB.View(func(tx *bolt.Tx) error { - b := tx.Bucket(s.bucket) - if b == nil { - return errors.E(op, errors.NoSuchBucket) - } - - buf := new(bytes.Buffer) - var out string - buf.Grow(100) - for i := range keys { - value := b.Get([]byte(keys[i])) - buf.Write(value) - // allocate enough space - dec := gob.NewDecoder(buf) - if value != nil { - err := dec.Decode(&out) - if err != nil { - return errors.E(op, err) - } - m[keys[i]] = out - buf.Reset() - out = "" - } - } - - return nil - }) - if err != nil { - return nil, errors.E(op, err) - } - - return m, nil -} - -// Set puts the K/V to the bolt -func (s *Plugin) Set(items ...kv.Item) error { - const op = errors.Op("boltdb_plugin_set") - if items == nil { - return errors.E(op, errors.NoKeys) - } - - // start writable transaction - tx, err := s.DB.Begin(true) - if err != nil { - return errors.E(op, err) - } - defer func() { - err = tx.Commit() - if err != nil { - errRb := tx.Rollback() - if errRb != nil { - s.log.Error("during the commit, Rollback error occurred", "commit error", err, "rollback error", errRb) - } - } - }() - - b := tx.Bucket(s.bucket) - // use access by index to avoid copying - for i := range items { - // performance note: pass a prepared bytes slice with initial cap - // we can't move buf and gob out of loop, because we need to clear both from data - // but gob will contain (w/o re-init) the past data - buf := bytes.Buffer{} - encoder := gob.NewEncoder(&buf) - if errors.Is(errors.EmptyItem, err) { - return errors.E(op, errors.EmptyItem) - } - - // Encode value - err = encoder.Encode(&items[i].Value) - if err != nil { - return errors.E(op, err) - } - // buf.Bytes will copy the underlying slice. Take a look in case of performance problems - err = b.Put([]byte(items[i].Key), buf.Bytes()) - if err != nil { - return errors.E(op, err) - } - - // if there are no errors, and TTL > 0, we put the key with timeout to the hashmap, for future check - // we do not need mutex here, since we use sync.Map - if items[i].TTL != "" { - // check correctness of provided TTL - _, err := time.Parse(time.RFC3339, items[i].TTL) - if err != nil { - return errors.E(op, err) - } - // Store key TTL in the separate map - s.gc.Store(items[i].Key, items[i].TTL) - } - - buf.Reset() - } - - return nil -} - -// Delete all keys from DB -func (s *Plugin) Delete(keys ...string) error { - const op = errors.Op("boltdb_plugin_delete") - if keys == nil { - return errors.E(op, errors.NoKeys) - } - - // should not be empty keys - for _, key := range keys { - keyTrimmed := strings.TrimSpace(key) - if keyTrimmed == "" { - return errors.E(op, errors.EmptyKey) - } - } - - // start writable transaction - tx, err := s.DB.Begin(true) - if err != nil { - return errors.E(op, err) - } - - defer func() { - err = tx.Commit() - if err != nil { - errRb := tx.Rollback() - if errRb != nil { - s.log.Error("during the commit, Rollback error occurred", "commit error", err, "rollback error", errRb) - } - } - }() - - b := tx.Bucket(s.bucket) - if b == nil { - return errors.E(op, errors.NoSuchBucket) - } - - for _, key := range keys { - err = b.Delete([]byte(key)) - if err != nil { - return errors.E(op, err) - } - } - - return nil -} - -// MExpire sets the expiration time to the key -// If key already has the expiration time, it will be overwritten -func (s *Plugin) MExpire(items ...kv.Item) error { - const op = errors.Op("boltdb_plugin_mexpire") - for i := range items { - if items[i].TTL == "" || strings.TrimSpace(items[i].Key) == "" { - return errors.E(op, errors.Str("should set timeout and at least one key")) - } - - // verify provided TTL - _, err := time.Parse(time.RFC3339, items[i].TTL) - if err != nil { - return errors.E(op, err) - } - - s.gc.Store(items[i].Key, items[i].TTL) - } - return nil -} - -func (s *Plugin) TTL(keys ...string) (map[string]interface{}, error) { - const op = errors.Op("boltdb_plugin_ttl") - if keys == nil { - return nil, errors.E(op, errors.NoKeys) - } - - // should not be empty keys - for i := range keys { - keyTrimmed := strings.TrimSpace(keys[i]) - if keyTrimmed == "" { - return nil, errors.E(op, errors.EmptyKey) - } - } - - m := make(map[string]interface{}, len(keys)) - - for i := range keys { - if item, ok := s.gc.Load(keys[i]); ok { - // a little bit dangerous operation, but user can't store value other that kv.Item.TTL --> int64 - m[keys[i]] = item.(string) - } - } - return m, nil -} - -// Close the DB connection -func (s *Plugin) Close() error { - // stop the keys GC - s.stop <- struct{}{} - return s.DB.Close() -} - -// RPCService returns associated rpc service. -func (s *Plugin) RPC() interface{} { - return kv.NewRPCServer(s, s.log) -} - -// Name returns plugin name -func (s *Plugin) Name() string { - return PluginName -} - -// ========================= PRIVATE ================================= - -func (s *Plugin) gcPhase() { - t := time.NewTicker(s.timeout) - defer t.Stop() - for { - select { - case <-t.C: - // calculate current time before loop started to be fair - now := time.Now() - s.gc.Range(func(key, value interface{}) bool { - const op = errors.Op("boltdb_plugin_gc") - k := key.(string) - v, err := time.Parse(time.RFC3339, value.(string)) - if err != nil { - return false - } - - if now.After(v) { - // time expired - s.gc.Delete(k) - s.log.Debug("key deleted", "key", k) - err := s.DB.Update(func(tx *bolt.Tx) error { - b := tx.Bucket(s.bucket) - if b == nil { - return errors.E(op, errors.NoSuchBucket) - } - err := b.Delete([]byte(k)) - if err != nil { - return errors.E(op, err) - } - return nil - }) - if err != nil { - s.log.Error("error during the gc phase of update", "error", err) - // todo this error is ignored, it means, that timer still be active - // to prevent this, we need to invoke t.Stop() - return false - } - } - return true - }) - case <-s.stop: - return - } - } -} diff --git a/plugins/kv/boltdb/plugin_unit_test.go b/plugins/kv/boltdb/plugin_unit_test.go deleted file mode 100644 index ad3843e7..00000000 --- a/plugins/kv/boltdb/plugin_unit_test.go +++ /dev/null @@ -1,531 +0,0 @@ -package boltdb - -import ( - "os" - "strconv" - "sync" - "testing" - "time" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/plugins/kv" - "github.com/spiral/roadrunner/v2/plugins/logger" - "github.com/stretchr/testify/assert" - bolt "go.etcd.io/bbolt" - "go.uber.org/zap" -) - -// NewBoltClient instantiate new BOLTDB client -// The parameters are: -// path string -- path to database file (can be placed anywhere), if file is not exist, it will be created -// perm os.FileMode -- file permissions, for example 0777 -// options *bolt.Options -- boltDB options, such as timeouts, noGrows options and other -// bucket string -- name of the bucket to use, should be UTF-8 -func newBoltClient(path string, perm os.FileMode, options *bolt.Options, bucket string, ttl time.Duration) (kv.Storage, error) { - const op = errors.Op("boltdb_plugin_new_bolt_client") - db, err := bolt.Open(path, perm, options) - if err != nil { - return nil, errors.E(op, err) - } - - // bucket should be SET - if bucket == "" { - return nil, errors.E(op, errors.Str("bucket should be set")) - } - - // create bucket if it does not exist - // tx.Commit invokes via the db.Update - err = db.Update(func(tx *bolt.Tx) error { - _, err = tx.CreateBucketIfNotExists([]byte(bucket)) - if err != nil { - return errors.E(op, err) - } - return nil - }) - if err != nil { - return nil, errors.E(op, err) - } - - // if TTL is not set, make it default - if ttl == 0 { - ttl = time.Minute - } - - l, _ := zap.NewDevelopment() - s := &Plugin{ - DB: db, - bucket: []byte(bucket), - stop: make(chan struct{}), - timeout: ttl, - gc: &sync.Map{}, - log: logger.NewZapAdapter(l), - } - - // start the TTL gc - go s.gcPhase() - - return s, nil -} - -func initStorage() kv.Storage { - storage, err := newBoltClient("rr.db", 0777, nil, "rr", time.Second) - if err != nil { - panic(err) - } - return storage -} - -func cleanup(t *testing.T, path string) { - err := os.RemoveAll(path) - if err != nil { - t.Fatal(err) - } -} - -func TestStorage_Has(t *testing.T) { - s := initStorage() - defer func() { - if err := s.Close(); err != nil { - panic(err) - } - cleanup(t, "rr.db") - }() - - v, err := s.Has("key") - assert.NoError(t, err) - assert.False(t, v["key"]) -} - -func TestStorage_Has_Set_Has(t *testing.T) { - s := initStorage() - defer func() { - if err := s.Close(); err != nil { - panic(err) - } - cleanup(t, "rr.db") - }() - - v, err := s.Has("key") - assert.NoError(t, err) - // no such key - assert.False(t, v["key"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "hello world", - TTL: "", - }, kv.Item{ - Key: "key2", - Value: "hello world", - TTL: "", - })) - - v, err = s.Has("key", "key2") - assert.NoError(t, err) - // no such key - assert.True(t, v["key"]) - assert.True(t, v["key2"]) -} - -func TestConcurrentReadWriteTransactions(t *testing.T) { - s := initStorage() - defer func() { - if err := s.Close(); err != nil { - panic(err) - } - cleanup(t, "rr.db") - }() - - v, err := s.Has("key") - assert.NoError(t, err) - // no such key - assert.False(t, v["key"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "hello world", - TTL: "", - }, kv.Item{ - Key: "key2", - Value: "hello world", - TTL: "", - })) - - v, err = s.Has("key", "key2") - assert.NoError(t, err) - // no such key - assert.True(t, v["key"]) - assert.True(t, v["key2"]) - - wg := &sync.WaitGroup{} - wg.Add(3) - - m := &sync.RWMutex{} - // concurrently set the keys - go func(s kv.Storage) { - defer wg.Done() - for i := 0; i <= 100; i++ { - m.Lock() - // set is writable transaction - // it should stop readable - assert.NoError(t, s.Set(kv.Item{ - Key: "key" + strconv.Itoa(i), - Value: "hello world" + strconv.Itoa(i), - TTL: "", - }, kv.Item{ - Key: "key2" + strconv.Itoa(i), - Value: "hello world" + strconv.Itoa(i), - TTL: "", - })) - m.Unlock() - } - }(s) - - // should be no errors - go func(s kv.Storage) { - defer wg.Done() - for i := 0; i <= 100; i++ { - m.RLock() - v, err = s.Has("key") - assert.NoError(t, err) - // no such key - assert.True(t, v["key"]) - m.RUnlock() - } - }(s) - - // should be no errors - go func(s kv.Storage) { - defer wg.Done() - for i := 0; i <= 100; i++ { - m.Lock() - err = s.Delete("key" + strconv.Itoa(i)) - assert.NoError(t, err) - m.Unlock() - } - }(s) - - wg.Wait() -} - -func TestStorage_Has_Set_MGet(t *testing.T) { - s := initStorage() - defer func() { - if err := s.Close(); err != nil { - panic(err) - } - cleanup(t, "rr.db") - }() - - v, err := s.Has("key") - assert.NoError(t, err) - // no such key - assert.False(t, v["key"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "hello world", - TTL: "", - }, kv.Item{ - Key: "key2", - Value: "hello world", - TTL: "", - })) - - v, err = s.Has("key", "key2") - assert.NoError(t, err) - // no such key - assert.True(t, v["key"]) - assert.True(t, v["key2"]) - - res, err := s.MGet("key", "key2") - assert.NoError(t, err) - assert.Len(t, res, 2) -} - -func TestStorage_Has_Set_Get(t *testing.T) { - s := initStorage() - defer func() { - if err := s.Close(); err != nil { - panic(err) - } - cleanup(t, "rr.db") - }() - - v, err := s.Has("key") - assert.NoError(t, err) - // no such key - assert.False(t, v["key"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "hello world", - TTL: "", - }, kv.Item{ - Key: "key2", - Value: "hello world2", - TTL: "", - })) - - v, err = s.Has("key", "key2") - assert.NoError(t, err) - - assert.True(t, v["key"]) - assert.True(t, v["key2"]) - - res, err := s.Get("key") - assert.NoError(t, err) - - if string(res) != "hello world" { - t.Fatal("wrong value by key") - } -} - -func TestStorage_Set_Del_Get(t *testing.T) { - s := initStorage() - defer func() { - if err := s.Close(); err != nil { - panic(err) - } - cleanup(t, "rr.db") - }() - - v, err := s.Has("key") - assert.NoError(t, err) - // no such key - assert.False(t, v["key"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "hello world", - TTL: "", - }, kv.Item{ - Key: "key2", - Value: "hello world", - TTL: "", - })) - - v, err = s.Has("key", "key2") - assert.NoError(t, err) - // no such key - assert.True(t, v["key"]) - assert.True(t, v["key2"]) - - // check that keys are present - res, err := s.MGet("key", "key2") - assert.NoError(t, err) - assert.Len(t, res, 2) - - assert.NoError(t, s.Delete("key", "key2")) - // check that keys are not present - res, err = s.MGet("key", "key2") - assert.NoError(t, err) - assert.Len(t, res, 0) -} - -func TestStorage_Set_GetM(t *testing.T) { - s := initStorage() - defer func() { - if err := s.Close(); err != nil { - panic(err) - } - cleanup(t, "rr.db") - }() - - v, err := s.Has("key") - assert.NoError(t, err) - assert.False(t, v["key"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "hello world", - TTL: "", - }, kv.Item{ - Key: "key2", - Value: "hello world", - TTL: "", - })) - - res, err := s.MGet("key", "key2") - assert.NoError(t, err) - assert.Len(t, res, 2) -} - -func TestNilAndWrongArgs(t *testing.T) { - s := initStorage() - defer func() { - if err := s.Close(); err != nil { - panic(err) - } - cleanup(t, "rr.db") - }() - - // check - v, err := s.Has("key") - assert.NoError(t, err) - assert.False(t, v["key"]) - - _, err = s.Has("") - assert.Error(t, err) - - _, err = s.Get("") - assert.Error(t, err) - - _, err = s.Get(" ") - assert.Error(t, err) - - _, err = s.Get(" ") - assert.Error(t, err) - - _, err = s.MGet("key", "key2", "") - assert.Error(t, err) - - _, err = s.MGet("key", "key2", " ") - assert.Error(t, err) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "hello world", - TTL: "", - })) - - assert.Error(t, s.Set(kv.Item{ - Key: "key", - Value: "hello world", - TTL: "asdf", - })) - - _, err = s.Has("key") - assert.NoError(t, err) - - assert.Error(t, s.Set(kv.Item{})) - - err = s.Delete("") - assert.Error(t, err) - - err = s.Delete("key", "") - assert.Error(t, err) - - err = s.Delete("key", " ") - assert.Error(t, err) - - err = s.Delete("key") - assert.NoError(t, err) -} - -func TestStorage_MExpire_TTL(t *testing.T) { - s := initStorage() - defer func() { - if err := s.Close(); err != nil { - panic(err) - } - cleanup(t, "rr.db") - }() - - // ensure that storage is clean - v, err := s.Has("key", "key2") - assert.NoError(t, err) - assert.False(t, v["key"]) - assert.False(t, v["key2"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "hello world", - TTL: "", - }, - kv.Item{ - Key: "key2", - Value: "hello world", - TTL: "", - })) - // set timeout to 5 sec - nowPlusFive := time.Now().Add(time.Second * 5).Format(time.RFC3339) - - i1 := kv.Item{ - Key: "key", - Value: "", - TTL: nowPlusFive, - } - i2 := kv.Item{ - Key: "key2", - Value: "", - TTL: nowPlusFive, - } - assert.NoError(t, s.MExpire(i1, i2)) - - time.Sleep(time.Second * 7) - - // ensure that storage is clean - v, err = s.Has("key", "key2") - assert.NoError(t, err) - assert.False(t, v["key"]) - assert.False(t, v["key2"]) -} - -func TestStorage_SetExpire_TTL(t *testing.T) { - s := initStorage() - defer func() { - if err := s.Close(); err != nil { - panic(err) - } - cleanup(t, "rr.db") - }() - - // ensure that storage is clean - v, err := s.Has("key", "key2") - assert.NoError(t, err) - assert.False(t, v["key"]) - assert.False(t, v["key2"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "hello world", - TTL: "", - }, - kv.Item{ - Key: "key2", - Value: "hello world", - TTL: "", - })) - - nowPlusFive := time.Now().Add(time.Second * 5).Format(time.RFC3339) - - // set timeout to 5 sec - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "value", - TTL: nowPlusFive, - }, - kv.Item{ - Key: "key2", - Value: "value", - TTL: nowPlusFive, - })) - - time.Sleep(time.Second * 2) - m, err := s.TTL("key", "key2") - assert.NoError(t, err) - - // remove a precision 4.02342342 -> 4 - keyTTL, err := strconv.Atoi(m["key"].(string)[0:1]) - if err != nil { - t.Fatal(err) - } - - // remove a precision 4.02342342 -> 4 - key2TTL, err := strconv.Atoi(m["key"].(string)[0:1]) - if err != nil { - t.Fatal(err) - } - - assert.True(t, keyTTL < 5) - assert.True(t, key2TTL < 5) - - time.Sleep(time.Second * 7) - - // ensure that storage is clean - v, err = s.Has("key", "key2") - assert.NoError(t, err) - assert.False(t, v["key"]) - assert.False(t, v["key2"]) -} diff --git a/plugins/kv/interface.go b/plugins/kv/interface.go deleted file mode 100644 index c1367cdf..00000000 --- a/plugins/kv/interface.go +++ /dev/null @@ -1,41 +0,0 @@ -package kv - -// Item represents general storage item -type Item struct { - // Key of item - Key string - // Value of item - Value string - // live until time provided by TTL in RFC 3339 format - TTL string -} - -// Storage represents single abstract storage. -type Storage interface { - // Has checks if value exists. - Has(keys ...string) (map[string]bool, error) - - // Get loads value content into a byte slice. - Get(key string) ([]byte, error) - - // MGet loads content of multiple values - // Returns the map with existing keys and associated values - MGet(keys ...string) (map[string]interface{}, error) - - // Set used to upload item to KV with TTL - // 0 value in TTL means no TTL - Set(items ...Item) error - - // MExpire sets the TTL for multiply keys - MExpire(items ...Item) error - - // TTL return the rest time to live for provided keys - // Not supported for the memcached and boltdb - TTL(keys ...string) (map[string]interface{}, error) - - // Delete one or multiple keys. - Delete(keys ...string) error - - // Close closes the storage and underlying resources. - Close() error -} diff --git a/plugins/kv/memcached/config.go b/plugins/kv/memcached/config.go deleted file mode 100644 index 7aad53b6..00000000 --- a/plugins/kv/memcached/config.go +++ /dev/null @@ -1,12 +0,0 @@ -package memcached - -type Config struct { - // Addr is url for memcached, 11211 port is used by default - Addr []string -} - -func (s *Config) InitDefaults() { - if s.Addr == nil { - s.Addr = []string{"localhost:11211"} // default url for memcached - } -} diff --git a/plugins/kv/memcached/plugin.go b/plugins/kv/memcached/plugin.go deleted file mode 100644 index 181b8a49..00000000 --- a/plugins/kv/memcached/plugin.go +++ /dev/null @@ -1,256 +0,0 @@ -package memcached - -import ( - "strings" - "time" - - "github.com/bradfitz/gomemcache/memcache" - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/kv" - "github.com/spiral/roadrunner/v2/plugins/logger" -) - -const PluginName = "memcached" - -var EmptyItem = kv.Item{} - -type Plugin struct { - // config - cfg *Config - // logger - log logger.Logger - // memcached client - client *memcache.Client -} - -// NewMemcachedClient returns a memcache client using the provided server(s) -// with equal weight. If a server is listed multiple times, -// it gets a proportional amount of weight. -func NewMemcachedClient(url string) kv.Storage { - m := memcache.New(url) - return &Plugin{ - client: m, - } -} - -func (s *Plugin) Init(log logger.Logger, cfg config.Configurer) error { - const op = errors.Op("memcached_plugin_init") - if !cfg.Has(PluginName) { - return errors.E(op, errors.Disabled) - } - err := cfg.UnmarshalKey(PluginName, &s.cfg) - if err != nil { - return errors.E(op, err) - } - - s.cfg.InitDefaults() - - s.log = log - return nil -} - -func (s *Plugin) Serve() chan error { - errCh := make(chan error, 1) - s.client = memcache.New(s.cfg.Addr...) - return errCh -} - -// Memcached has no stop/close or smt similar to close the connection -func (s *Plugin) Stop() error { - return nil -} - -// RPCService returns associated rpc service. -func (s *Plugin) RPC() interface{} { - return kv.NewRPCServer(s, s.log) -} - -// Name returns plugin user-friendly name -func (s *Plugin) Name() string { - return PluginName -} - -// Has checks the key for existence -func (s *Plugin) Has(keys ...string) (map[string]bool, error) { - const op = errors.Op("memcached_plugin_has") - if keys == nil { - return nil, errors.E(op, errors.NoKeys) - } - m := make(map[string]bool, len(keys)) - for i := range keys { - keyTrimmed := strings.TrimSpace(keys[i]) - if keyTrimmed == "" { - return nil, errors.E(op, errors.EmptyKey) - } - exist, err := s.client.Get(keys[i]) - // ErrCacheMiss means that a Get failed because the item wasn't present. - if err != nil && err != memcache.ErrCacheMiss { - return nil, err - } - if exist != nil { - m[keys[i]] = true - } - } - return m, nil -} - -// Get gets the item for the given key. ErrCacheMiss is returned for a -// memcache cache miss. The key must be at most 250 bytes in length. -func (s *Plugin) Get(key string) ([]byte, error) { - const op = errors.Op("memcached_plugin_get") - // to get cases like " " - keyTrimmed := strings.TrimSpace(key) - if keyTrimmed == "" { - return nil, errors.E(op, errors.EmptyKey) - } - data, err := s.client.Get(key) - // ErrCacheMiss means that a Get failed because the item wasn't present. - if err != nil && err != memcache.ErrCacheMiss { - return nil, err - } - if data != nil { - // return the value by the key - return data.Value, nil - } - // data is nil by some reason and error also nil - return nil, nil -} - -// return map with key -- string -// and map value as value -- []byte -func (s *Plugin) MGet(keys ...string) (map[string]interface{}, error) { - const op = errors.Op("memcached_plugin_mget") - if keys == nil { - return nil, errors.E(op, errors.NoKeys) - } - - // should not be empty keys - for i := range keys { - keyTrimmed := strings.TrimSpace(keys[i]) - if keyTrimmed == "" { - return nil, errors.E(op, errors.EmptyKey) - } - } - - m := make(map[string]interface{}, len(keys)) - for i := range keys { - // Here also MultiGet - data, err := s.client.Get(keys[i]) - // ErrCacheMiss means that a Get failed because the item wasn't present. - if err != nil && err != memcache.ErrCacheMiss { - return nil, err - } - if data != nil { - m[keys[i]] = data.Value - } - } - - return m, nil -} - -// Set sets the KV pairs. Keys should be 250 bytes maximum -// TTL: -// Expiration is the cache expiration time, in seconds: either a relative -// time from now (up to 1 month), or an absolute Unix epoch time. -// Zero means the Item has no expiration time. -func (s *Plugin) Set(items ...kv.Item) error { - const op = errors.Op("memcached_plugin_set") - if items == nil { - return errors.E(op, errors.NoKeys) - } - - for i := range items { - if items[i] == EmptyItem { - return errors.E(op, errors.EmptyItem) - } - - // pre-allocate item - memcachedItem := &memcache.Item{ - Key: items[i].Key, - // unsafe convert - Value: []byte(items[i].Value), - Flags: 0, - } - - // add additional TTL in case of TTL isn't empty - if items[i].TTL != "" { - // verify the TTL - t, err := time.Parse(time.RFC3339, items[i].TTL) - if err != nil { - return err - } - memcachedItem.Expiration = int32(t.Unix()) - } - - err := s.client.Set(memcachedItem) - if err != nil { - return err - } - } - - return nil -} - -// Expiration is the cache expiration time, in seconds: either a relative -// time from now (up to 1 month), or an absolute Unix epoch time. -// Zero means the Item has no expiration time. -func (s *Plugin) MExpire(items ...kv.Item) error { - const op = errors.Op("memcached_plugin_mexpire") - for i := range items { - if items[i].TTL == "" || strings.TrimSpace(items[i].Key) == "" { - return errors.E(op, errors.Str("should set timeout and at least one key")) - } - - // verify provided TTL - t, err := time.Parse(time.RFC3339, items[i].TTL) - if err != nil { - return err - } - - // Touch updates the expiry for the given key. The seconds parameter is either - // a Unix timestamp or, if seconds is less than 1 month, the number of seconds - // into the future at which time the item will expire. Zero means the item has - // no expiration time. ErrCacheMiss is returned if the key is not in the cache. - // The key must be at most 250 bytes in length. - err = s.client.Touch(items[i].Key, int32(t.Unix())) - if err != nil { - return err - } - } - return nil -} - -// return time in seconds (int32) for a given keys -func (s *Plugin) TTL(keys ...string) (map[string]interface{}, error) { - const op = errors.Op("memcached_plugin_ttl") - return nil, errors.E(op, errors.Str("not valid request for memcached, see https://github.com/memcached/memcached/issues/239")) -} - -func (s *Plugin) Delete(keys ...string) error { - const op = errors.Op("memcached_plugin_has") - if keys == nil { - return errors.E(op, errors.NoKeys) - } - - // should not be empty keys - for i := range keys { - keyTrimmed := strings.TrimSpace(keys[i]) - if keyTrimmed == "" { - return errors.E(op, errors.EmptyKey) - } - } - - for i := range keys { - err := s.client.Delete(keys[i]) - // ErrCacheMiss means that a Get failed because the item wasn't present. - if err != nil && err != memcache.ErrCacheMiss { - return err - } - } - return nil -} - -func (s *Plugin) Close() error { - return nil -} diff --git a/plugins/kv/memcached/plugin_unit_test.go b/plugins/kv/memcached/plugin_unit_test.go deleted file mode 100644 index 31423627..00000000 --- a/plugins/kv/memcached/plugin_unit_test.go +++ /dev/null @@ -1,432 +0,0 @@ -package memcached - -import ( - "strconv" - "sync" - "testing" - "time" - - "github.com/spiral/roadrunner/v2/plugins/kv" - "github.com/stretchr/testify/assert" -) - -func initStorage() kv.Storage { - return NewMemcachedClient("localhost:11211") -} - -func cleanup(t *testing.T, s kv.Storage, keys ...string) { - err := s.Delete(keys...) - if err != nil { - t.Fatalf("error during cleanup: %s", err.Error()) - } -} - -func TestStorage_Has(t *testing.T) { - s := initStorage() - - v, err := s.Has("key") - assert.NoError(t, err) - assert.False(t, v["key"]) -} - -func TestStorage_Has_Set_Has(t *testing.T) { - s := initStorage() - defer func() { - cleanup(t, s, "key", "key2") - if err := s.Close(); err != nil { - panic(err) - } - }() - - v, err := s.Has("key") - assert.NoError(t, err) - // no such key - assert.False(t, v["key"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "hello world", - TTL: "", - }, kv.Item{ - Key: "key2", - Value: "hello world", - TTL: "", - })) - - v, err = s.Has("key", "key2") - assert.NoError(t, err) - // no such key - assert.True(t, v["key"]) - assert.True(t, v["key2"]) -} - -func TestStorage_Has_Set_MGet(t *testing.T) { - s := initStorage() - defer func() { - cleanup(t, s, "key", "key2") - if err := s.Close(); err != nil { - panic(err) - } - }() - - v, err := s.Has("key") - assert.NoError(t, err) - // no such key - assert.False(t, v["key"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "hello world", - TTL: "", - }, kv.Item{ - Key: "key2", - Value: "hello world", - TTL: "", - })) - - v, err = s.Has("key", "key2") - assert.NoError(t, err) - // no such key - assert.True(t, v["key"]) - assert.True(t, v["key2"]) - - res, err := s.MGet("key", "key2") - assert.NoError(t, err) - assert.Len(t, res, 2) -} - -func TestStorage_Has_Set_Get(t *testing.T) { - s := initStorage() - defer func() { - cleanup(t, s, "key", "key2") - if err := s.Close(); err != nil { - panic(err) - } - }() - - v, err := s.Has("key") - assert.NoError(t, err) - // no such key - assert.False(t, v["key"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "hello world", - TTL: "", - }, kv.Item{ - Key: "key2", - Value: "hello world", - TTL: "", - })) - - v, err = s.Has("key", "key2") - assert.NoError(t, err) - // no such key - assert.True(t, v["key"]) - assert.True(t, v["key2"]) - - res, err := s.Get("key") - assert.NoError(t, err) - - if string(res) != "hello world" { - t.Fatal("wrong value by key") - } -} - -func TestStorage_Set_Del_Get(t *testing.T) { - s := initStorage() - defer func() { - cleanup(t, s, "key", "key2") - if err := s.Close(); err != nil { - panic(err) - } - }() - - v, err := s.Has("key") - assert.NoError(t, err) - // no such key - assert.False(t, v["key"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "hello world", - TTL: "", - }, kv.Item{ - Key: "key2", - Value: "hello world", - TTL: "", - })) - - v, err = s.Has("key", "key2") - assert.NoError(t, err) - // no such key - assert.True(t, v["key"]) - assert.True(t, v["key2"]) - - // check that keys are present - res, err := s.MGet("key", "key2") - assert.NoError(t, err) - assert.Len(t, res, 2) - - assert.NoError(t, s.Delete("key", "key2")) - // check that keys are not present - res, err = s.MGet("key", "key2") - assert.NoError(t, err) - assert.Len(t, res, 0) -} - -func TestStorage_Set_GetM(t *testing.T) { - s := initStorage() - - defer func() { - cleanup(t, s, "key", "key2") - - if err := s.Close(); err != nil { - t.Fatal(err) - } - }() - - v, err := s.Has("key") - assert.NoError(t, err) - assert.False(t, v["key"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "hello world", - TTL: "", - }, kv.Item{ - Key: "key2", - Value: "hello world", - TTL: "", - })) - - res, err := s.MGet("key", "key2") - assert.NoError(t, err) - assert.Len(t, res, 2) -} - -func TestStorage_MExpire_TTL(t *testing.T) { - s := initStorage() - defer func() { - cleanup(t, s, "key", "key2") - if err := s.Close(); err != nil { - t.Fatal(err) - } - }() - - // ensure that storage is clean - v, err := s.Has("key", "key2") - assert.NoError(t, err) - assert.False(t, v["key"]) - assert.False(t, v["key2"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "hello world", - TTL: "", - }, - kv.Item{ - Key: "key2", - Value: "hello world", - TTL: "", - })) - // set timeout to 5 sec - nowPlusFive := time.Now().Add(time.Second * 5).Format(time.RFC3339) - - i1 := kv.Item{ - Key: "key", - Value: "", - TTL: nowPlusFive, - } - i2 := kv.Item{ - Key: "key2", - Value: "", - TTL: nowPlusFive, - } - assert.NoError(t, s.MExpire(i1, i2)) - - time.Sleep(time.Second * 7) - - // ensure that storage is clean - v, err = s.Has("key", "key2") - assert.NoError(t, err) - assert.False(t, v["key"]) - assert.False(t, v["key2"]) -} - -func TestNilAndWrongArgs(t *testing.T) { - s := initStorage() - defer func() { - cleanup(t, s, "key") - if err := s.Close(); err != nil { - panic(err) - } - }() - - // check - v, err := s.Has("key") - assert.NoError(t, err) - assert.False(t, v["key"]) - - _, err = s.Has("") - assert.Error(t, err) - - _, err = s.Get("") - assert.Error(t, err) - - _, err = s.Get(" ") - assert.Error(t, err) - - _, err = s.Get(" ") - assert.Error(t, err) - - _, err = s.MGet("key", "key2", "") - assert.Error(t, err) - - _, err = s.MGet("key", "key2", " ") - assert.Error(t, err) - - assert.Error(t, s.Set(kv.Item{})) - - err = s.Delete("") - assert.Error(t, err) - - err = s.Delete("key", "") - assert.Error(t, err) - - err = s.Delete("key", " ") - assert.Error(t, err) - - err = s.Delete("key") - assert.NoError(t, err) -} - -func TestStorage_SetExpire_TTL(t *testing.T) { - s := initStorage() - defer func() { - cleanup(t, s, "key", "key2") - if err := s.Close(); err != nil { - t.Fatal(err) - } - }() - - // ensure that storage is clean - v, err := s.Has("key", "key2") - assert.NoError(t, err) - assert.False(t, v["key"]) - assert.False(t, v["key2"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "hello world", - TTL: "", - }, - kv.Item{ - Key: "key2", - Value: "hello world", - TTL: "", - })) - - nowPlusFive := time.Now().Add(time.Second * 5).Format(time.RFC3339) - - // set timeout to 5 sec - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "value", - TTL: nowPlusFive, - }, - kv.Item{ - Key: "key2", - Value: "value", - TTL: nowPlusFive, - })) - - time.Sleep(time.Second * 7) - - // ensure that storage is clean - v, err = s.Has("key", "key2") - assert.NoError(t, err) - assert.False(t, v["key"]) - assert.False(t, v["key2"]) -} - -func TestConcurrentReadWriteTransactions(t *testing.T) { - s := initStorage() - defer func() { - cleanup(t, s, "key", "key2") - if err := s.Close(); err != nil { - t.Fatal(err) - } - }() - - v, err := s.Has("key") - assert.NoError(t, err) - // no such key - assert.False(t, v["key"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "hello world", - TTL: "", - }, kv.Item{ - Key: "key2", - Value: "hello world", - TTL: "", - })) - - v, err = s.Has("key", "key2") - assert.NoError(t, err) - // no such key - assert.True(t, v["key"]) - assert.True(t, v["key2"]) - - wg := &sync.WaitGroup{} - wg.Add(3) - - m := &sync.RWMutex{} - // concurrently set the keys - go func(s kv.Storage) { - defer wg.Done() - for i := 0; i <= 100; i++ { - m.Lock() - // set is writable transaction - // it should stop readable - assert.NoError(t, s.Set(kv.Item{ - Key: "key" + strconv.Itoa(i), - Value: "hello world" + strconv.Itoa(i), - TTL: "", - }, kv.Item{ - Key: "key2" + strconv.Itoa(i), - Value: "hello world" + strconv.Itoa(i), - TTL: "", - })) - m.Unlock() - } - }(s) - - // should be no errors - go func(s kv.Storage) { - defer wg.Done() - for i := 0; i <= 100; i++ { - m.RLock() - v, err = s.Has("key") - assert.NoError(t, err) - // no such key - assert.True(t, v["key"]) - m.RUnlock() - } - }(s) - - // should be no errors - go func(s kv.Storage) { - defer wg.Done() - for i := 0; i <= 100; i++ { - m.Lock() - err = s.Delete("key" + strconv.Itoa(i)) - assert.NoError(t, err) - m.Unlock() - } - }(s) - - wg.Wait() -} diff --git a/plugins/kv/memory/config.go b/plugins/kv/memory/config.go deleted file mode 100644 index e51d09c5..00000000 --- a/plugins/kv/memory/config.go +++ /dev/null @@ -1,14 +0,0 @@ -package memory - -// Config is default config for the in-memory driver -type Config struct { - // Interval for the check - Interval int -} - -// InitDefaults by default driver is turned off -func (c *Config) InitDefaults() { - if c.Interval == 0 { - c.Interval = 60 // seconds - } -} diff --git a/plugins/kv/memory/plugin.go b/plugins/kv/memory/plugin.go deleted file mode 100644 index 4201a1c0..00000000 --- a/plugins/kv/memory/plugin.go +++ /dev/null @@ -1,264 +0,0 @@ -package memory - -import ( - "strings" - "sync" - "time" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/kv" - "github.com/spiral/roadrunner/v2/plugins/logger" -) - -// PluginName is user friendly name for the plugin -const PluginName = "memory" - -type Plugin struct { - // heap is user map for the key-value pairs - heap sync.Map - stop chan struct{} - - log logger.Logger - cfg *Config -} - -func (s *Plugin) Init(cfg config.Configurer, log logger.Logger) error { - const op = errors.Op("in_memory_plugin_init") - if !cfg.Has(PluginName) { - return errors.E(op, errors.Disabled) - } - err := cfg.UnmarshalKey(PluginName, &s.cfg) - if err != nil { - return errors.E(op, err) - } - - s.cfg.InitDefaults() - s.log = log - - s.stop = make(chan struct{}, 1) - return nil -} - -func (s *Plugin) Serve() chan error { - errCh := make(chan error, 1) - // start in-memory gc for kv - go s.gc() - - return errCh -} - -func (s *Plugin) Stop() error { - const op = errors.Op("in_memory_plugin_stop") - err := s.Close() - if err != nil { - return errors.E(op, err) - } - return nil -} - -func (s *Plugin) Has(keys ...string) (map[string]bool, error) { - const op = errors.Op("in_memory_plugin_has") - if keys == nil { - return nil, errors.E(op, errors.NoKeys) - } - m := make(map[string]bool) - for i := range keys { - keyTrimmed := strings.TrimSpace(keys[i]) - if keyTrimmed == "" { - return nil, errors.E(op, errors.EmptyKey) - } - - if _, ok := s.heap.Load(keys[i]); ok { - m[keys[i]] = true - } - } - - return m, nil -} - -func (s *Plugin) Get(key string) ([]byte, error) { - const op = errors.Op("in_memory_plugin_get") - // to get cases like " " - keyTrimmed := strings.TrimSpace(key) - if keyTrimmed == "" { - return nil, errors.E(op, errors.EmptyKey) - } - - if data, exist := s.heap.Load(key); exist { - // here might be a panic - // but data only could be a string, see Set function - return []byte(data.(kv.Item).Value), nil - } - return nil, nil -} - -func (s *Plugin) MGet(keys ...string) (map[string]interface{}, error) { - const op = errors.Op("in_memory_plugin_mget") - if keys == nil { - return nil, errors.E(op, errors.NoKeys) - } - - // should not be empty keys - for i := range keys { - keyTrimmed := strings.TrimSpace(keys[i]) - if keyTrimmed == "" { - return nil, errors.E(op, errors.EmptyKey) - } - } - - m := make(map[string]interface{}, len(keys)) - - for i := range keys { - if value, ok := s.heap.Load(keys[i]); ok { - m[keys[i]] = value.(kv.Item).Value - } - } - - return m, nil -} - -func (s *Plugin) Set(items ...kv.Item) error { - const op = errors.Op("in_memory_plugin_set") - if items == nil { - return errors.E(op, errors.NoKeys) - } - - for i := range items { - // TTL is set - if items[i].TTL != "" { - // check the TTL in the item - _, err := time.Parse(time.RFC3339, items[i].TTL) - if err != nil { - return err - } - } - - s.heap.Store(items[i].Key, items[i]) - } - return nil -} - -// MExpire sets the expiration time to the key -// If key already has the expiration time, it will be overwritten -func (s *Plugin) MExpire(items ...kv.Item) error { - const op = errors.Op("in_memory_plugin_mexpire") - for i := range items { - if items[i].TTL == "" || strings.TrimSpace(items[i].Key) == "" { - return errors.E(op, errors.Str("should set timeout and at least one key")) - } - - // if key exist, overwrite it value - if pItem, ok := s.heap.Load(items[i].Key); ok { - // check that time is correct - _, err := time.Parse(time.RFC3339, items[i].TTL) - if err != nil { - return errors.E(op, err) - } - tmp := pItem.(kv.Item) - // guess that t is in the future - // in memory is just FOR TESTING PURPOSES - // LOGIC ISN'T IDEAL - s.heap.Store(items[i].Key, kv.Item{ - Key: items[i].Key, - Value: tmp.Value, - TTL: items[i].TTL, - }) - } - } - - return nil -} - -func (s *Plugin) TTL(keys ...string) (map[string]interface{}, error) { - const op = errors.Op("in_memory_plugin_ttl") - if keys == nil { - return nil, errors.E(op, errors.NoKeys) - } - - // should not be empty keys - for i := range keys { - keyTrimmed := strings.TrimSpace(keys[i]) - if keyTrimmed == "" { - return nil, errors.E(op, errors.EmptyKey) - } - } - - m := make(map[string]interface{}, len(keys)) - - for i := range keys { - if item, ok := s.heap.Load(keys[i]); ok { - m[keys[i]] = item.(kv.Item).TTL - } - } - return m, nil -} - -func (s *Plugin) Delete(keys ...string) error { - const op = errors.Op("in_memory_plugin_delete") - if keys == nil { - return errors.E(op, errors.NoKeys) - } - - // should not be empty keys - for i := range keys { - keyTrimmed := strings.TrimSpace(keys[i]) - if keyTrimmed == "" { - return errors.E(op, errors.EmptyKey) - } - } - - for i := range keys { - s.heap.Delete(keys[i]) - } - return nil -} - -// Close clears the in-memory storage -func (s *Plugin) Close() error { - s.stop <- struct{}{} - return nil -} - -// RPCService returns associated rpc service. -func (s *Plugin) RPC() interface{} { - return kv.NewRPCServer(s, s.log) -} - -// Name returns plugin user-friendly name -func (s *Plugin) Name() string { - return PluginName -} - -// ================================== PRIVATE ====================================== - -func (s *Plugin) gc() { - // TODO check - ticker := time.NewTicker(time.Duration(s.cfg.Interval) * time.Second) - for { - select { - case <-s.stop: - ticker.Stop() - return - case now := <-ticker.C: - // check every second - s.heap.Range(func(key, value interface{}) bool { - v := value.(kv.Item) - if v.TTL == "" { - return true - } - - t, err := time.Parse(time.RFC3339, v.TTL) - if err != nil { - return false - } - - if now.After(t) { - s.log.Debug("key deleted", "key", key) - s.heap.Delete(key) - } - return true - }) - } - } -} diff --git a/plugins/kv/memory/plugin_unit_test.go b/plugins/kv/memory/plugin_unit_test.go deleted file mode 100644 index 1965a696..00000000 --- a/plugins/kv/memory/plugin_unit_test.go +++ /dev/null @@ -1,472 +0,0 @@ -package memory - -import ( - "strconv" - "sync" - "testing" - "time" - - "github.com/spiral/roadrunner/v2/plugins/kv" - "github.com/spiral/roadrunner/v2/plugins/logger" - "github.com/stretchr/testify/assert" - "go.uber.org/zap" -) - -func initStorage() kv.Storage { - p := &Plugin{ - stop: make(chan struct{}), - } - p.cfg = &Config{ - Interval: 1, - } - - l, _ := zap.NewDevelopment() - p.log = logger.NewZapAdapter(l) - - go p.gc() - - return p -} - -func cleanup(t *testing.T, s kv.Storage, keys ...string) { - err := s.Delete(keys...) - if err != nil { - t.Fatalf("error during cleanup: %s", err.Error()) - } -} - -func TestStorage_Has(t *testing.T) { - s := initStorage() - - v, err := s.Has("key") - assert.NoError(t, err) - assert.False(t, v["key"]) -} - -func TestStorage_Has_Set_Has(t *testing.T) { - s := initStorage() - defer func() { - cleanup(t, s, "key", "key2") - if err := s.Close(); err != nil { - panic(err) - } - }() - - v, err := s.Has("key") - assert.NoError(t, err) - // no such key - assert.False(t, v["key"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "value", - TTL: "", - }, - kv.Item{ - Key: "key2", - Value: "value", - TTL: "", - })) - - v, err = s.Has("key", "key2") - assert.NoError(t, err) - // no such key - assert.True(t, v["key"]) - assert.True(t, v["key2"]) -} - -func TestStorage_Has_Set_MGet(t *testing.T) { - s := initStorage() - defer func() { - cleanup(t, s, "key", "key2") - if err := s.Close(); err != nil { - panic(err) - } - }() - - v, err := s.Has("key") - assert.NoError(t, err) - // no such key - assert.False(t, v["key"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "value", - TTL: "", - }, - kv.Item{ - Key: "key2", - Value: "value", - TTL: "", - })) - - v, err = s.Has("key", "key2") - assert.NoError(t, err) - // no such key - assert.True(t, v["key"]) - assert.True(t, v["key2"]) - - res, err := s.MGet("key", "key2") - assert.NoError(t, err) - assert.Len(t, res, 2) -} - -func TestStorage_Has_Set_Get(t *testing.T) { - s := initStorage() - defer func() { - cleanup(t, s, "key", "key2") - if err := s.Close(); err != nil { - panic(err) - } - }() - - v, err := s.Has("key") - assert.NoError(t, err) - // no such key - assert.False(t, v["key"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "value", - TTL: "", - }, - kv.Item{ - Key: "key2", - Value: "value", - TTL: "", - })) - - v, err = s.Has("key", "key2") - assert.NoError(t, err) - // no such key - assert.True(t, v["key"]) - assert.True(t, v["key2"]) - - res, err := s.Get("key") - assert.NoError(t, err) - - if string(res) != "value" { - t.Fatal("wrong value by key") - } -} - -func TestStorage_Set_Del_Get(t *testing.T) { - s := initStorage() - defer func() { - cleanup(t, s, "key", "key2") - if err := s.Close(); err != nil { - panic(err) - } - }() - - v, err := s.Has("key") - assert.NoError(t, err) - // no such key - assert.False(t, v["key"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "value", - TTL: "", - }, - kv.Item{ - Key: "key2", - Value: "value", - TTL: "", - })) - - v, err = s.Has("key", "key2") - assert.NoError(t, err) - // no such key - assert.True(t, v["key"]) - assert.True(t, v["key2"]) - - // check that keys are present - res, err := s.MGet("key", "key2") - assert.NoError(t, err) - assert.Len(t, res, 2) - - assert.NoError(t, s.Delete("key", "key2")) - // check that keys are not presents -eo state,uid,pid,ppid,rtprio,time,comm - res, err = s.MGet("key", "key2") - assert.NoError(t, err) - assert.Len(t, res, 0) -} - -func TestStorage_Set_GetM(t *testing.T) { - s := initStorage() - - defer func() { - cleanup(t, s, "key", "key2") - - if err := s.Close(); err != nil { - t.Fatal(err) - } - }() - - v, err := s.Has("key") - assert.NoError(t, err) - assert.False(t, v["key"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "value", - TTL: "", - }, - kv.Item{ - Key: "key2", - Value: "value", - TTL: "", - })) - - res, err := s.MGet("key", "key2") - assert.NoError(t, err) - assert.Len(t, res, 2) -} - -func TestStorage_MExpire_TTL(t *testing.T) { - s := initStorage() - defer func() { - cleanup(t, s, "key", "key2") - - if err := s.Close(); err != nil { - t.Fatal(err) - } - }() - - // ensure that storage is clean - v, err := s.Has("key", "key2") - assert.NoError(t, err) - assert.False(t, v["key"]) - assert.False(t, v["key2"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "hello world", - TTL: "", - }, - kv.Item{ - Key: "key2", - Value: "hello world", - TTL: "", - })) - // set timeout to 5 sec - nowPlusFive := time.Now().Add(time.Second * 5).Format(time.RFC3339) - - i1 := kv.Item{ - Key: "key", - Value: "", - TTL: nowPlusFive, - } - i2 := kv.Item{ - Key: "key2", - Value: "", - TTL: nowPlusFive, - } - assert.NoError(t, s.MExpire(i1, i2)) - - time.Sleep(time.Second * 7) - - // ensure that storage is clean - v, err = s.Has("key", "key2") - assert.NoError(t, err) - assert.False(t, v["key"]) - assert.False(t, v["key2"]) -} - -func TestNilAndWrongArgs(t *testing.T) { - s := initStorage() - defer func() { - if err := s.Close(); err != nil { - panic(err) - } - }() - - // check - v, err := s.Has("key") - assert.NoError(t, err) - assert.False(t, v["key"]) - - _, err = s.Has("") - assert.Error(t, err) - - _, err = s.Get("") - assert.Error(t, err) - - _, err = s.Get(" ") - assert.Error(t, err) - - _, err = s.Get(" ") - assert.Error(t, err) - - _, err = s.MGet("key", "key2", "") - assert.Error(t, err) - - _, err = s.MGet("key", "key2", " ") - assert.Error(t, err) - - assert.NoError(t, s.Set(kv.Item{})) - _, err = s.Has("key") - assert.NoError(t, err) - - err = s.Delete("") - assert.Error(t, err) - - err = s.Delete("key", "") - assert.Error(t, err) - - err = s.Delete("key", " ") - assert.Error(t, err) - - err = s.Delete("key") - assert.NoError(t, err) -} - -func TestStorage_SetExpire_TTL(t *testing.T) { - s := initStorage() - defer func() { - cleanup(t, s, "key", "key2") - if err := s.Close(); err != nil { - t.Fatal(err) - } - }() - - // ensure that storage is clean - v, err := s.Has("key", "key2") - assert.NoError(t, err) - assert.False(t, v["key"]) - assert.False(t, v["key2"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "hello world", - TTL: "", - }, - kv.Item{ - Key: "key2", - Value: "hello world", - TTL: "", - })) - - nowPlusFive := time.Now().Add(time.Second * 5).Format(time.RFC3339) - - // set timeout to 5 sec - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "value", - TTL: nowPlusFive, - }, - kv.Item{ - Key: "key2", - Value: "value", - TTL: nowPlusFive, - })) - - time.Sleep(time.Second * 2) - m, err := s.TTL("key", "key2") - assert.NoError(t, err) - - // remove a precision 4.02342342 -> 4 - keyTTL, err := strconv.Atoi(m["key"].(string)[0:1]) - if err != nil { - t.Fatal(err) - } - - // remove a precision 4.02342342 -> 4 - key2TTL, err := strconv.Atoi(m["key"].(string)[0:1]) - if err != nil { - t.Fatal(err) - } - - assert.True(t, keyTTL < 5) - assert.True(t, key2TTL < 5) - - time.Sleep(time.Second * 4) - - // ensure that storage is clean - v, err = s.Has("key", "key2") - assert.NoError(t, err) - assert.False(t, v["key"]) - assert.False(t, v["key2"]) -} - -func TestConcurrentReadWriteTransactions(t *testing.T) { - s := initStorage() - defer func() { - cleanup(t, s, "key", "key2") - if err := s.Close(); err != nil { - t.Fatal(err) - } - }() - - v, err := s.Has("key") - assert.NoError(t, err) - // no such key - assert.False(t, v["key"]) - - assert.NoError(t, s.Set(kv.Item{ - Key: "key", - Value: "hello world", - TTL: "", - }, kv.Item{ - Key: "key2", - Value: "hello world", - TTL: "", - })) - - v, err = s.Has("key", "key2") - assert.NoError(t, err) - // no such key - assert.True(t, v["key"]) - assert.True(t, v["key2"]) - - wg := &sync.WaitGroup{} - wg.Add(3) - - m := &sync.RWMutex{} - // concurrently set the keys - go func(s kv.Storage) { - defer wg.Done() - for i := 0; i <= 100; i++ { - m.Lock() - // set is writable transaction - // it should stop readable - assert.NoError(t, s.Set(kv.Item{ - Key: "key" + strconv.Itoa(i), - Value: "hello world" + strconv.Itoa(i), - TTL: "", - }, kv.Item{ - Key: "key2" + strconv.Itoa(i), - Value: "hello world" + strconv.Itoa(i), - TTL: "", - })) - m.Unlock() - } - }(s) - - // should be no errors - go func(s kv.Storage) { - defer wg.Done() - for i := 0; i <= 100; i++ { - m.RLock() - v, err = s.Has("key") - assert.NoError(t, err) - // no such key - assert.True(t, v["key"]) - m.RUnlock() - } - }(s) - - // should be no errors - go func(s kv.Storage) { - defer wg.Done() - for i := 0; i <= 100; i++ { - m.Lock() - err = s.Delete("key" + strconv.Itoa(i)) - assert.NoError(t, err) - m.Unlock() - } - }(s) - - wg.Wait() -} diff --git a/plugins/kv/rpc.go b/plugins/kv/rpc.go deleted file mode 100644 index 751f0d12..00000000 --- a/plugins/kv/rpc.go +++ /dev/null @@ -1,110 +0,0 @@ -package kv - -import ( - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/plugins/logger" -) - -// Wrapper for the plugin -type RPCServer struct { - // svc is a plugin implementing Storage interface - svc Storage - // Logger - log logger.Logger -} - -// NewRPCServer construct RPC server for the particular plugin -func NewRPCServer(srv Storage, log logger.Logger) *RPCServer { - return &RPCServer{ - svc: srv, - log: log, - } -} - -// data Data -func (r *RPCServer) Has(in []string, res *map[string]bool) error { - const op = errors.Op("rpc server Has") - ret, err := r.svc.Has(in...) - if err != nil { - return errors.E(op, err) - } - - // update the value in the pointer - *res = ret - return nil -} - -// in SetData -func (r *RPCServer) Set(in []Item, ok *bool) error { - const op = errors.Op("rpc server Set") - - err := r.svc.Set(in...) - if err != nil { - return errors.E(op, err) - } - - *ok = true - return nil -} - -// in Data -func (r *RPCServer) MGet(in []string, res *map[string]interface{}) error { - const op = errors.Op("rpc server MGet") - ret, err := r.svc.MGet(in...) - if err != nil { - return errors.E(op, err) - } - - // update return value - *res = ret - return nil -} - -// in Data -func (r *RPCServer) MExpire(in []Item, ok *bool) error { - const op = errors.Op("rpc server MExpire") - - err := r.svc.MExpire(in...) - if err != nil { - return errors.E(op, err) - } - - *ok = true - return nil -} - -// in Data -func (r *RPCServer) TTL(in []string, res *map[string]interface{}) error { - const op = errors.Op("rpc server TTL") - - ret, err := r.svc.TTL(in...) - if err != nil { - return errors.E(op, err) - } - - *res = ret - return nil -} - -// in Data -func (r *RPCServer) Delete(in []string, ok *bool) error { - const op = errors.Op("rpc server Delete") - err := r.svc.Delete(in...) - if err != nil { - return errors.E(op, err) - } - *ok = true - return nil -} - -// in string, storages -func (r *RPCServer) Close(storage string, ok *bool) error { - const op = errors.Op("rpc server Close") - err := r.svc.Close() - if err != nil { - return errors.E(op, err) - } - *ok = true - - return nil -} diff --git a/plugins/logger/config.go b/plugins/logger/config.go deleted file mode 100644 index 8cc88d02..00000000 --- a/plugins/logger/config.go +++ /dev/null @@ -1,94 +0,0 @@ -package logger - -import ( - "strings" - - "go.uber.org/zap" - "go.uber.org/zap/zapcore" -) - -// ChannelConfig configures loggers per channel. -type ChannelConfig struct { - // Dedicated channels per logger. By default logger allocated via named logger. - Channels map[string]Config `json:"channels" mapstructure:"channels"` -} - -type Config struct { - // Mode configures logger based on some default template (development, production, off). - Mode string `json:"mode" mapstructure:"mode"` - - // Level is the minimum enabled logging level. Note that this is a dynamic - // level, so calling ChannelConfig.Level.SetLevel will atomically change the log - // level of all loggers descended from this config. - Level string `json:"level" mapstructure:"level"` - - // Encoding sets the logger's encoding. Valid values are "json" and - // "console", as well as any third-party encodings registered via - // RegisterEncoder. - Encoding string `json:"encoding" mapstructure:"encoding"` - - // Output is a list of URLs or file paths to write logging output to. - // See Open for details. - Output []string `json:"output" mapstructure:"output"` - - // ErrorOutput is a list of URLs to write internal logger errors to. - // The default is standard error. - // - // Note that this setting only affects internal errors; for sample code that - // sends error-level logs to a different location from info- and debug-level - // logs, see the package-level AdvancedConfiguration example. - ErrorOutput []string `json:"errorOutput" mapstructure:"errorOutput"` -} - -// ZapConfig converts config into Zap configuration. -func (cfg *Config) BuildLogger() (*zap.Logger, error) { - var zCfg zap.Config - switch strings.ToLower(cfg.Mode) { - case "off", "none": - return zap.NewNop(), nil - case "production": - zCfg = zap.NewProductionConfig() - case "development": - zCfg = zap.NewDevelopmentConfig() - default: - zCfg = zap.Config{ - Level: zap.NewAtomicLevelAt(zap.DebugLevel), - Encoding: "console", - EncoderConfig: zapcore.EncoderConfig{ - MessageKey: "message", - LevelKey: "level", - TimeKey: "time", - NameKey: "name", - EncodeName: ColoredHashedNameEncoder, - EncodeLevel: ColoredLevelEncoder, - EncodeTime: UTCTimeEncoder, - EncodeCaller: zapcore.ShortCallerEncoder, - }, - OutputPaths: []string{"stderr"}, - ErrorOutputPaths: []string{"stderr"}, - } - } - - if cfg.Level != "" { - level := zap.NewAtomicLevel() - if err := level.UnmarshalText([]byte(cfg.Level)); err == nil { - zCfg.Level = level - } - } - - if cfg.Encoding != "" { - zCfg.Encoding = cfg.Encoding - } - - if len(cfg.Output) != 0 { - zCfg.OutputPaths = cfg.Output - } - - if len(cfg.ErrorOutput) != 0 { - zCfg.ErrorOutputPaths = cfg.ErrorOutput - } - - // todo: https://github.com/uber-go/zap/blob/master/FAQ.md#does-zap-support-log-rotation - - return zCfg.Build() -} diff --git a/plugins/logger/encoder.go b/plugins/logger/encoder.go deleted file mode 100644 index 4ff583c4..00000000 --- a/plugins/logger/encoder.go +++ /dev/null @@ -1,66 +0,0 @@ -package logger - -import ( - "hash/fnv" - "strings" - "time" - - "github.com/fatih/color" - "go.uber.org/zap/zapcore" -) - -var colorMap = []func(string, ...interface{}) string{ - color.HiYellowString, - color.HiGreenString, - color.HiBlueString, - color.HiRedString, - color.HiCyanString, - color.HiMagentaString, -} - -// ColoredLevelEncoder colorizes log levels. -func ColoredLevelEncoder(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) { - switch level { - case zapcore.DebugLevel: - enc.AppendString(color.HiWhiteString(level.CapitalString())) - case zapcore.InfoLevel: - enc.AppendString(color.HiCyanString(level.CapitalString())) - case zapcore.WarnLevel: - enc.AppendString(color.HiYellowString(level.CapitalString())) - case zapcore.ErrorLevel, zapcore.DPanicLevel: - enc.AppendString(color.HiRedString(level.CapitalString())) - case zapcore.PanicLevel, zapcore.FatalLevel: - enc.AppendString(color.HiMagentaString(level.CapitalString())) - } -} - -// ColoredNameEncoder colorizes service names. -func ColoredNameEncoder(s string, enc zapcore.PrimitiveArrayEncoder) { - if len(s) < 12 { - s += strings.Repeat(" ", 12-len(s)) - } - - enc.AppendString(color.HiGreenString(s)) -} - -// ColoredHashedNameEncoder colorizes service names and assigns different colors to different names. -func ColoredHashedNameEncoder(s string, enc zapcore.PrimitiveArrayEncoder) { - if len(s) < 12 { - s += strings.Repeat(" ", 12-len(s)) - } - - colorID := stringHash(s, len(colorMap)) - enc.AppendString(colorMap[colorID](s)) -} - -// UTCTimeEncoder encodes time into short UTC specific timestamp. -func UTCTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) { - enc.AppendString(t.UTC().Format("2006/01/02 15:04:05")) -} - -// returns string hash -func stringHash(name string, base int) int { - h := fnv.New32a() - _, _ = h.Write([]byte(name)) - return int(h.Sum32()) % base -} diff --git a/plugins/logger/interface.go b/plugins/logger/interface.go deleted file mode 100644 index 876629a9..00000000 --- a/plugins/logger/interface.go +++ /dev/null @@ -1,16 +0,0 @@ -package logger - -type ( - // Logger is an general RR log interface - Logger interface { - Debug(msg string, keyvals ...interface{}) - Info(msg string, keyvals ...interface{}) - Warn(msg string, keyvals ...interface{}) - Error(msg string, keyvals ...interface{}) - } -) - -// With creates a child logger and adds structured context to it -type WithLogger interface { - With(keyvals ...interface{}) Logger -} diff --git a/plugins/logger/plugin.go b/plugins/logger/plugin.go deleted file mode 100644 index 141ede64..00000000 --- a/plugins/logger/plugin.go +++ /dev/null @@ -1,72 +0,0 @@ -package logger - -import ( - endure "github.com/spiral/endure/pkg/container" - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/plugins/config" - "go.uber.org/zap" -) - -// PluginName declares plugin name. -const PluginName = "logs" - -// ZapLogger manages zap logger. -type ZapLogger struct { - base *zap.Logger - cfg *Config - channels ChannelConfig -} - -// Init logger service. -func (z *ZapLogger) Init(cfg config.Configurer) error { - const op = errors.Op("config_plugin_init") - if !cfg.Has(PluginName) { - return errors.E(op, errors.Disabled) - } - - err := cfg.UnmarshalKey(PluginName, &z.cfg) - if err != nil { - return errors.E(op, errors.Disabled, err) - } - - err = cfg.UnmarshalKey(PluginName, &z.channels) - if err != nil { - return errors.E(op, errors.Disabled, err) - } - - z.base, err = z.cfg.BuildLogger() - if err != nil { - return errors.E(op, errors.Disabled, err) - } - return nil -} - -// DefaultLogger returns default logger. -func (z *ZapLogger) DefaultLogger() (Logger, error) { - return NewZapAdapter(z.base), nil -} - -// NamedLogger returns logger dedicated to the specific channel. Similar to Named() but also reads the core params. -func (z *ZapLogger) NamedLogger(name string) (Logger, error) { - if cfg, ok := z.channels.Channels[name]; ok { - l, err := cfg.BuildLogger() - if err != nil { - return nil, err - } - return NewZapAdapter(l), nil - } - - return NewZapAdapter(z.base.Named(name)), nil -} - -// NamedLogger returns logger dedicated to the specific channel. Similar to Named() but also reads the core params. -func (z *ZapLogger) ServiceLogger(n endure.Named) (Logger, error) { - return z.NamedLogger(n.Name()) -} - -// Provides declares factory methods. -func (z *ZapLogger) Provides() []interface{} { - return []interface{}{ - z.ServiceLogger, - } -} diff --git a/plugins/logger/zap_adapter.go b/plugins/logger/zap_adapter.go deleted file mode 100644 index 0a0855b8..00000000 --- a/plugins/logger/zap_adapter.go +++ /dev/null @@ -1,56 +0,0 @@ -package logger - -import ( - "fmt" - - "go.uber.org/zap" -) - -type ZapAdapter struct { - zl *zap.Logger -} - -// Create NewZapAdapter which uses general log interface -func NewZapAdapter(zapLogger *zap.Logger) *ZapAdapter { - return &ZapAdapter{ - zl: zapLogger.WithOptions(zap.AddCallerSkip(1)), - } -} - -func (log *ZapAdapter) fields(keyvals []interface{}) []zap.Field { - // we should have even number of keys and values - if len(keyvals)%2 != 0 { - return []zap.Field{zap.Error(fmt.Errorf("odd number of keyvals pairs: %v", keyvals))} - } - - var fields []zap.Field - for i := 0; i < len(keyvals); i += 2 { - key, ok := keyvals[i].(string) - if !ok { - key = fmt.Sprintf("%v", keyvals[i]) - } - fields = append(fields, zap.Any(key, keyvals[i+1])) - } - - return fields -} - -func (log *ZapAdapter) Debug(msg string, keyvals ...interface{}) { - log.zl.Debug(msg, log.fields(keyvals)...) -} - -func (log *ZapAdapter) Info(msg string, keyvals ...interface{}) { - log.zl.Info(msg, log.fields(keyvals)...) -} - -func (log *ZapAdapter) Warn(msg string, keyvals ...interface{}) { - log.zl.Warn(msg, log.fields(keyvals)...) -} - -func (log *ZapAdapter) Error(msg string, keyvals ...interface{}) { - log.zl.Error(msg, log.fields(keyvals)...) -} - -func (log *ZapAdapter) With(keyvals ...interface{}) Logger { - return NewZapAdapter(log.zl.With(log.fields(keyvals)...)) -} diff --git a/plugins/metrics/config.go b/plugins/metrics/config.go deleted file mode 100644 index dd36005e..00000000 --- a/plugins/metrics/config.go +++ /dev/null @@ -1,140 +0,0 @@ -package metrics - -import ( - "fmt" - - "github.com/prometheus/client_golang/prometheus" -) - -// Config configures metrics service. -type Config struct { - // Address to listen - Address string - - // Collect define application specific metrics. - Collect map[string]Collector -} - -type NamedCollector struct { - // Name of the collector - Name string `json:"name"` - - // Collector structure - Collector `json:"collector"` -} - -// CollectorType represents prometheus collector types -type CollectorType string - -const ( - // Histogram type - Histogram CollectorType = "histogram" - - // Gauge type - Gauge CollectorType = "gauge" - - // Counter type - Counter CollectorType = "counter" - - // Summary type - Summary CollectorType = "summary" -) - -// Collector describes single application specific metric. -type Collector struct { - // Namespace of the metric. - Namespace string `json:"namespace"` - // Subsystem of the metric. - Subsystem string `json:"subsystem"` - // Collector type (histogram, gauge, counter, summary). - Type CollectorType `json:"type"` - // Help of collector. - Help string `json:"help"` - // Labels for vectorized metrics. - Labels []string `json:"labels"` - // Buckets for histogram metric. - Buckets []float64 `json:"buckets"` - // Objectives for the summary opts - Objectives map[float64]float64 `json:"objectives"` -} - -// register application specific metrics. -func (c *Config) getCollectors() (map[string]prometheus.Collector, error) { - if c.Collect == nil { - return nil, nil - } - - collectors := make(map[string]prometheus.Collector) - - for name, m := range c.Collect { - var collector prometheus.Collector - switch m.Type { - case Histogram: - opts := prometheus.HistogramOpts{ - Name: name, - Namespace: m.Namespace, - Subsystem: m.Subsystem, - Help: m.Help, - Buckets: m.Buckets, - } - - if len(m.Labels) != 0 { - collector = prometheus.NewHistogramVec(opts, m.Labels) - } else { - collector = prometheus.NewHistogram(opts) - } - case Gauge: - opts := prometheus.GaugeOpts{ - Name: name, - Namespace: m.Namespace, - Subsystem: m.Subsystem, - Help: m.Help, - } - - if len(m.Labels) != 0 { - collector = prometheus.NewGaugeVec(opts, m.Labels) - } else { - collector = prometheus.NewGauge(opts) - } - case Counter: - opts := prometheus.CounterOpts{ - Name: name, - Namespace: m.Namespace, - Subsystem: m.Subsystem, - Help: m.Help, - } - - if len(m.Labels) != 0 { - collector = prometheus.NewCounterVec(opts, m.Labels) - } else { - collector = prometheus.NewCounter(opts) - } - case Summary: - opts := prometheus.SummaryOpts{ - Name: name, - Namespace: m.Namespace, - Subsystem: m.Subsystem, - Help: m.Help, - Objectives: m.Objectives, - } - - if len(m.Labels) != 0 { - collector = prometheus.NewSummaryVec(opts, m.Labels) - } else { - collector = prometheus.NewSummary(opts) - } - default: - return nil, fmt.Errorf("invalid metric type `%s` for `%s`", m.Type, name) - } - - collectors[name] = collector - } - - return collectors, nil -} - -func (c *Config) InitDefaults() { - if c.Address == "" { - c.Address = "localhost:2112" - } -} diff --git a/plugins/metrics/config_test.go b/plugins/metrics/config_test.go deleted file mode 100644 index 665ec9cd..00000000 --- a/plugins/metrics/config_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package metrics - -import ( - "bytes" - "testing" - - j "github.com/json-iterator/go" - "github.com/prometheus/client_golang/prometheus" - "github.com/stretchr/testify/assert" -) - -var json = j.ConfigCompatibleWithStandardLibrary - -func Test_Config_Hydrate_Error1(t *testing.T) { - cfg := `{"request": {"From": "Something"}}` - c := &Config{} - f := new(bytes.Buffer) - f.WriteString(cfg) - - err := json.Unmarshal(f.Bytes(), &c) - if err != nil { - t.Fatal(err) - } -} - -func Test_Config_Hydrate_Error2(t *testing.T) { - cfg := `{"dir": "/dir/"` - c := &Config{} - - f := new(bytes.Buffer) - f.WriteString(cfg) - - err := json.Unmarshal(f.Bytes(), &c) - assert.Error(t, err) -} - -func Test_Config_Metrics(t *testing.T) { - cfg := `{ -"collect":{ - "metric1":{"type": "gauge"}, - "metric2":{ "type": "counter"}, - "metric3":{"type": "summary"}, - "metric4":{"type": "histogram"} -} -}` - c := &Config{} - f := new(bytes.Buffer) - f.WriteString(cfg) - - err := json.Unmarshal(f.Bytes(), &c) - if err != nil { - t.Fatal(err) - } - - m, err := c.getCollectors() - assert.NoError(t, err) - - assert.IsType(t, prometheus.NewGauge(prometheus.GaugeOpts{}), m["metric1"]) - assert.IsType(t, prometheus.NewCounter(prometheus.CounterOpts{}), m["metric2"]) - assert.IsType(t, prometheus.NewSummary(prometheus.SummaryOpts{}), m["metric3"]) - assert.IsType(t, prometheus.NewHistogram(prometheus.HistogramOpts{}), m["metric4"]) -} - -func Test_Config_MetricsVector(t *testing.T) { - cfg := `{ -"collect":{ - "metric1":{"type": "gauge","labels":["label"]}, - "metric2":{ "type": "counter","labels":["label"]}, - "metric3":{"type": "summary","labels":["label"]}, - "metric4":{"type": "histogram","labels":["label"]} -} -}` - c := &Config{} - f := new(bytes.Buffer) - f.WriteString(cfg) - - err := json.Unmarshal(f.Bytes(), &c) - if err != nil { - t.Fatal(err) - } - - m, err := c.getCollectors() - assert.NoError(t, err) - - assert.IsType(t, prometheus.NewGaugeVec(prometheus.GaugeOpts{}, []string{}), m["metric1"]) - assert.IsType(t, prometheus.NewCounterVec(prometheus.CounterOpts{}, []string{}), m["metric2"]) - assert.IsType(t, prometheus.NewSummaryVec(prometheus.SummaryOpts{}, []string{}), m["metric3"]) - assert.IsType(t, prometheus.NewHistogramVec(prometheus.HistogramOpts{}, []string{}), m["metric4"]) -} diff --git a/plugins/metrics/doc.go b/plugins/metrics/doc.go deleted file mode 100644 index 1abe097a..00000000 --- a/plugins/metrics/doc.go +++ /dev/null @@ -1 +0,0 @@ -package metrics diff --git a/plugins/metrics/interface.go b/plugins/metrics/interface.go deleted file mode 100644 index 87ba4017..00000000 --- a/plugins/metrics/interface.go +++ /dev/null @@ -1,7 +0,0 @@ -package metrics - -import "github.com/prometheus/client_golang/prometheus" - -type StatProvider interface { - MetricsCollector() []prometheus.Collector -} diff --git a/plugins/metrics/plugin.go b/plugins/metrics/plugin.go deleted file mode 100644 index fefe92bd..00000000 --- a/plugins/metrics/plugin.go +++ /dev/null @@ -1,232 +0,0 @@ -package metrics - -import ( - "context" - "crypto/tls" - "net/http" - "sync" - "time" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" - endure "github.com/spiral/endure/pkg/container" - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/logger" - "golang.org/x/sys/cpu" -) - -const ( - // PluginName declares plugin name. - PluginName = "metrics" - // maxHeaderSize declares max header size for prometheus server - maxHeaderSize = 1024 * 1024 * 100 // 104MB -) - -type statsProvider struct { - collectors []prometheus.Collector - name string -} - -// Plugin to manage application metrics using Prometheus. -type Plugin struct { - cfg *Config - log logger.Logger - mu sync.Mutex // all receivers are pointers - http *http.Server - collectors sync.Map // all receivers are pointers - registry *prometheus.Registry -} - -// Init service. -func (m *Plugin) Init(cfg config.Configurer, log logger.Logger) error { - const op = errors.Op("metrics_plugin_init") - if !cfg.Has(PluginName) { - return errors.E(op, errors.Disabled) - } - - err := cfg.UnmarshalKey(PluginName, &m.cfg) - if err != nil { - return errors.E(op, errors.Disabled, err) - } - - m.cfg.InitDefaults() - - m.log = log - m.registry = prometheus.NewRegistry() - - // Default - err = m.registry.Register(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{})) - if err != nil { - return errors.E(op, err) - } - - // Default - err = m.registry.Register(prometheus.NewGoCollector()) - if err != nil { - return errors.E(op, err) - } - - collectors, err := m.cfg.getCollectors() - if err != nil { - return errors.E(op, err) - } - - // Register invocation will be later in the Serve method - for k, v := range collectors { - m.collectors.Store(k, statsProvider{ - collectors: []prometheus.Collector{v}, - name: k, - }) - } - return nil -} - -// Register new prometheus collector. -func (m *Plugin) Register(c prometheus.Collector) error { - return m.registry.Register(c) -} - -// Serve prometheus metrics service. -func (m *Plugin) Serve() chan error { - errCh := make(chan error, 1) - m.collectors.Range(func(key, value interface{}) bool { - // key - name - // value - statsProvider struct - c := value.(statsProvider) - for _, v := range c.collectors { - if err := m.registry.Register(v); err != nil { - errCh <- err - return false - } - } - - return true - }) - - var topCipherSuites []uint16 - var defaultCipherSuitesTLS13 []uint16 - - hasGCMAsmAMD64 := cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ - hasGCMAsmARM64 := cpu.ARM64.HasAES && cpu.ARM64.HasPMULL - // Keep in sync with crypto/aes/cipher_s390x.go. - hasGCMAsmS390X := cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM) - - hasGCMAsm := hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X - - if hasGCMAsm { - // If AES-GCM hardware is provided then prioritise AES-GCM - // cipher suites. - topCipherSuites = []uint16{ - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, - tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, - } - defaultCipherSuitesTLS13 = []uint16{ - tls.TLS_AES_128_GCM_SHA256, - tls.TLS_CHACHA20_POLY1305_SHA256, - tls.TLS_AES_256_GCM_SHA384, - } - } else { - // Without AES-GCM hardware, we put the ChaCha20-Poly1305 - // cipher suites first. - topCipherSuites = []uint16{ - tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, - tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - } - defaultCipherSuitesTLS13 = []uint16{ - tls.TLS_CHACHA20_POLY1305_SHA256, - tls.TLS_AES_128_GCM_SHA256, - tls.TLS_AES_256_GCM_SHA384, - } - } - - DefaultCipherSuites := make([]uint16, 0, 22) - DefaultCipherSuites = append(DefaultCipherSuites, topCipherSuites...) - DefaultCipherSuites = append(DefaultCipherSuites, defaultCipherSuitesTLS13...) - - m.http = &http.Server{ - Addr: m.cfg.Address, - Handler: promhttp.HandlerFor(m.registry, promhttp.HandlerOpts{}), - IdleTimeout: time.Hour * 24, - ReadTimeout: time.Minute * 60, - MaxHeaderBytes: maxHeaderSize, - ReadHeaderTimeout: time.Minute * 60, - WriteTimeout: time.Minute * 60, - TLSConfig: &tls.Config{ - CurvePreferences: []tls.CurveID{ - tls.CurveP256, - tls.CurveP384, - tls.CurveP521, - tls.X25519, - }, - CipherSuites: DefaultCipherSuites, - MinVersion: tls.VersionTLS12, - PreferServerCipherSuites: true, - }, - } - - go func() { - err := m.http.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - errCh <- err - return - } - }() - - return errCh -} - -// Stop prometheus metrics service. -func (m *Plugin) Stop() error { - m.mu.Lock() - defer m.mu.Unlock() - - if m.http != nil { - // timeout is 10 seconds - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - defer cancel() - err := m.http.Shutdown(ctx) - if err != nil { - // Function should be Stop() error - m.log.Error("stop error", "error", errors.Errorf("error shutting down the metrics server: error %v", err)) - } - } - return nil -} - -// Collects used to collect all plugins which implement metrics.StatProvider interface (and Named) -func (m *Plugin) Collects() []interface{} { - return []interface{}{ - m.AddStatProvider, - } -} - -// Collector returns application specific collector by name or nil if collector not found. -func (m *Plugin) AddStatProvider(name endure.Named, stat StatProvider) error { - m.collectors.Store(name.Name(), statsProvider{ - collectors: stat.MetricsCollector(), - name: name.Name(), - }) - return nil -} - -// RPC interface satisfaction -func (m *Plugin) Name() string { - return PluginName -} - -// RPC interface satisfaction -func (m *Plugin) RPC() interface{} { - return &rpcServer{ - svc: m, - log: m.log, - } -} diff --git a/plugins/metrics/rpc.go b/plugins/metrics/rpc.go deleted file mode 100644 index 538cdb78..00000000 --- a/plugins/metrics/rpc.go +++ /dev/null @@ -1,294 +0,0 @@ -package metrics - -import ( - "github.com/prometheus/client_golang/prometheus" - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/plugins/logger" -) - -type rpcServer struct { - svc *Plugin - log logger.Logger -} - -// Metric represent single metric produced by the application. -type Metric struct { - // Collector name. - Name string - - // Collector value. - Value float64 - - // Labels associated with metric. Only for vector metrics. Must be provided in a form of label values. - Labels []string -} - -// Add new metric to the designated collector. -func (rpc *rpcServer) Add(m *Metric, ok *bool) error { - const op = errors.Op("metrics_plugin_add") - rpc.log.Info("adding metric", "name", m.Name, "value", m.Value, "labels", m.Labels) - c, exist := rpc.svc.collectors.Load(m.Name) - if !exist { - rpc.log.Error("undefined collector", "collector", m.Name) - return errors.E(op, errors.Errorf("undefined collector %s, try first Declare the desired collector", m.Name)) - } - - switch c := c.(type) { - case prometheus.Gauge: - c.Add(m.Value) - - case *prometheus.GaugeVec: - if len(m.Labels) == 0 { - rpc.log.Error("required labels for collector", "collector", m.Name) - return errors.E(op, errors.Errorf("required labels for collector %s", m.Name)) - } - - gauge, err := c.GetMetricWithLabelValues(m.Labels...) - if err != nil { - rpc.log.Error("failed to get metrics with label values", "collector", m.Name, "labels", m.Labels) - return errors.E(op, err) - } - gauge.Add(m.Value) - case prometheus.Counter: - c.Add(m.Value) - - case *prometheus.CounterVec: - if len(m.Labels) == 0 { - return errors.E(op, errors.Errorf("required labels for collector `%s`", m.Name)) - } - - gauge, err := c.GetMetricWithLabelValues(m.Labels...) - if err != nil { - rpc.log.Error("failed to get metrics with label values", "collector", m.Name, "labels", m.Labels) - return errors.E(op, err) - } - gauge.Add(m.Value) - - default: - return errors.E(op, errors.Errorf("collector %s does not support method `Add`", m.Name)) - } - - // RPC, set ok to true as return value. Need by rpc.Call reply argument - *ok = true - rpc.log.Info("metric successfully added", "name", m.Name, "labels", m.Labels, "value", m.Value) - return nil -} - -// Sub subtract the value from the specific metric (gauge only). -func (rpc *rpcServer) Sub(m *Metric, ok *bool) error { - const op = errors.Op("metrics_plugin_sub") - rpc.log.Info("subtracting value from metric", "name", m.Name, "value", m.Value, "labels", m.Labels) - c, exist := rpc.svc.collectors.Load(m.Name) - if !exist { - rpc.log.Error("undefined collector", "name", m.Name, "value", m.Value, "labels", m.Labels) - return errors.E(op, errors.Errorf("undefined collector %s", m.Name)) - } - if c == nil { - // can it be nil ??? I guess can't - return errors.E(op, errors.Errorf("undefined collector %s", m.Name)) - } - - switch c := c.(type) { - case prometheus.Gauge: - c.Sub(m.Value) - - case *prometheus.GaugeVec: - if len(m.Labels) == 0 { - rpc.log.Error("required labels for collector, but none was provided", "name", m.Name, "value", m.Value) - return errors.E(op, errors.Errorf("required labels for collector %s", m.Name)) - } - - gauge, err := c.GetMetricWithLabelValues(m.Labels...) - if err != nil { - rpc.log.Error("failed to get metrics with label values", "collector", m.Name, "labels", m.Labels) - return errors.E(op, err) - } - gauge.Sub(m.Value) - default: - return errors.E(op, errors.Errorf("collector `%s` does not support method `Sub`", m.Name)) - } - rpc.log.Info("subtracting operation finished successfully", "name", m.Name, "labels", m.Labels, "value", m.Value) - - *ok = true - return nil -} - -// Observe the value (histogram and summary only). -func (rpc *rpcServer) Observe(m *Metric, ok *bool) error { - const op = errors.Op("metrics_plugin_observe") - rpc.log.Info("observing metric", "name", m.Name, "value", m.Value, "labels", m.Labels) - - c, exist := rpc.svc.collectors.Load(m.Name) - if !exist { - rpc.log.Error("undefined collector", "name", m.Name, "value", m.Value, "labels", m.Labels) - return errors.E(op, errors.Errorf("undefined collector %s", m.Name)) - } - if c == nil { - return errors.E(op, errors.Errorf("undefined collector %s", m.Name)) - } - - switch c := c.(type) { - case *prometheus.SummaryVec: - if len(m.Labels) == 0 { - return errors.E(op, errors.Errorf("required labels for collector `%s`", m.Name)) - } - - observer, err := c.GetMetricWithLabelValues(m.Labels...) - if err != nil { - return errors.E(op, err) - } - observer.Observe(m.Value) - - case prometheus.Histogram: - c.Observe(m.Value) - - case *prometheus.HistogramVec: - if len(m.Labels) == 0 { - return errors.E(op, errors.Errorf("required labels for collector `%s`", m.Name)) - } - - observer, err := c.GetMetricWithLabelValues(m.Labels...) - if err != nil { - rpc.log.Error("failed to get metrics with label values", "collector", m.Name, "labels", m.Labels) - return errors.E(op, err) - } - observer.Observe(m.Value) - default: - return errors.E(op, errors.Errorf("collector `%s` does not support method `Observe`", m.Name)) - } - - rpc.log.Info("observe operation finished successfully", "name", m.Name, "labels", m.Labels, "value", m.Value) - - *ok = true - return nil -} - -// Declare is used to register new collector in prometheus -// THE TYPES ARE: -// NamedCollector -> Collector with the name -// bool -> RPC reply value -// RETURNS: -// error -func (rpc *rpcServer) Declare(nc *NamedCollector, ok *bool) error { - const op = errors.Op("metrics_plugin_declare") - rpc.log.Info("declaring new metric", "name", nc.Name, "type", nc.Type, "namespace", nc.Namespace) - _, exist := rpc.svc.collectors.Load(nc.Name) - if exist { - rpc.log.Error("metric with provided name already exist", "name", nc.Name, "type", nc.Type, "namespace", nc.Namespace) - return errors.E(op, errors.Errorf("tried to register existing collector with the name `%s`", nc.Name)) - } - - var collector prometheus.Collector - switch nc.Type { - case Histogram: - opts := prometheus.HistogramOpts{ - Name: nc.Name, - Namespace: nc.Namespace, - Subsystem: nc.Subsystem, - Help: nc.Help, - Buckets: nc.Buckets, - } - - if len(nc.Labels) != 0 { - collector = prometheus.NewHistogramVec(opts, nc.Labels) - } else { - collector = prometheus.NewHistogram(opts) - } - case Gauge: - opts := prometheus.GaugeOpts{ - Name: nc.Name, - Namespace: nc.Namespace, - Subsystem: nc.Subsystem, - Help: nc.Help, - } - - if len(nc.Labels) != 0 { - collector = prometheus.NewGaugeVec(opts, nc.Labels) - } else { - collector = prometheus.NewGauge(opts) - } - case Counter: - opts := prometheus.CounterOpts{ - Name: nc.Name, - Namespace: nc.Namespace, - Subsystem: nc.Subsystem, - Help: nc.Help, - } - - if len(nc.Labels) != 0 { - collector = prometheus.NewCounterVec(opts, nc.Labels) - } else { - collector = prometheus.NewCounter(opts) - } - case Summary: - opts := prometheus.SummaryOpts{ - Name: nc.Name, - Namespace: nc.Namespace, - Subsystem: nc.Subsystem, - Help: nc.Help, - } - - if len(nc.Labels) != 0 { - collector = prometheus.NewSummaryVec(opts, nc.Labels) - } else { - collector = prometheus.NewSummary(opts) - } - - default: - return errors.E(op, errors.Errorf("unknown collector type %s", nc.Type)) - } - - // add collector to sync.Map - rpc.svc.collectors.Store(nc.Name, collector) - // that method might panic, we handle it by recover - err := rpc.svc.Register(collector) - if err != nil { - *ok = false - return errors.E(op, err) - } - - rpc.log.Info("metric successfully added", "name", nc.Name, "type", nc.Type, "namespace", nc.Namespace) - - *ok = true - return nil -} - -// Set the metric value (only for gaude). -func (rpc *rpcServer) Set(m *Metric, ok *bool) (err error) { - const op = errors.Op("metrics_plugin_set") - rpc.log.Info("observing metric", "name", m.Name, "value", m.Value, "labels", m.Labels) - - c, exist := rpc.svc.collectors.Load(m.Name) - if !exist { - return errors.E(op, errors.Errorf("undefined collector %s", m.Name)) - } - if c == nil { - return errors.E(op, errors.Errorf("undefined collector %s", m.Name)) - } - - switch c := c.(type) { - case prometheus.Gauge: - c.Set(m.Value) - - case *prometheus.GaugeVec: - if len(m.Labels) == 0 { - rpc.log.Error("required labels for collector", "collector", m.Name) - return errors.E(op, errors.Errorf("required labels for collector %s", m.Name)) - } - - gauge, err := c.GetMetricWithLabelValues(m.Labels...) - if err != nil { - rpc.log.Error("failed to get metrics with label values", "collector", m.Name, "labels", m.Labels) - return errors.E(op, err) - } - gauge.Set(m.Value) - - default: - return errors.E(op, errors.Errorf("collector `%s` does not support method Set", m.Name)) - } - - rpc.log.Info("set operation finished successfully", "name", m.Name, "labels", m.Labels, "value", m.Value) - - *ok = true - return nil -} diff --git a/plugins/redis/config.go b/plugins/redis/config.go deleted file mode 100644 index 41348236..00000000 --- a/plugins/redis/config.go +++ /dev/null @@ -1,34 +0,0 @@ -package redis - -import "time" - -type Config struct { - Addrs []string `mapstructure:"addrs"` - DB int `mapstructure:"db"` - Username string `mapstructure:"username"` - Password string `mapstructure:"password"` - MasterName string `mapstructure:"master_name"` - SentinelPassword string `mapstructure:"sentinel_password"` - RouteByLatency bool `mapstructure:"route_by_latency"` - RouteRandomly bool `mapstructure:"route_randomly"` - MaxRetries int `mapstructure:"max_retries"` - DialTimeout time.Duration `mapstructure:"dial_timeout"` - MinRetryBackoff time.Duration `mapstructure:"min_retry_backoff"` - MaxRetryBackoff time.Duration `mapstructure:"max_retry_backoff"` - PoolSize int `mapstructure:"pool_size"` - MinIdleConns int `mapstructure:"min_idle_conns"` - MaxConnAge time.Duration `mapstructure:"max_conn_age"` - ReadTimeout time.Duration `mapstructure:"read_timeout"` - WriteTimeout time.Duration `mapstructure:"write_timeout"` - PoolTimeout time.Duration `mapstructure:"pool_timeout"` - IdleTimeout time.Duration `mapstructure:"idle_timeout"` - IdleCheckFreq time.Duration `mapstructure:"idle_check_freq"` - ReadOnly bool `mapstructure:"read_only"` -} - -// InitDefaults initializing fill config with default values -func (s *Config) InitDefaults() { - if s.Addrs == nil { - s.Addrs = []string{"localhost:6379"} // default addr is pointing to local storage - } -} diff --git a/plugins/redis/interface.go b/plugins/redis/interface.go deleted file mode 100644 index 909c8ca4..00000000 --- a/plugins/redis/interface.go +++ /dev/null @@ -1,9 +0,0 @@ -package redis - -import "github.com/go-redis/redis/v8" - -// Redis in the redis KV plugin interface -type Redis interface { - // GetClient - GetClient() redis.UniversalClient -} diff --git a/plugins/redis/plugin.go b/plugins/redis/plugin.go deleted file mode 100644 index 204abd17..00000000 --- a/plugins/redis/plugin.go +++ /dev/null @@ -1,78 +0,0 @@ -package redis - -import ( - "github.com/go-redis/redis/v8" - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/logger" -) - -const PluginName = "redis" - -type Plugin struct { - // config for RR integration - cfg *Config - // logger - log logger.Logger - // redis universal client - universalClient redis.UniversalClient -} - -func (s *Plugin) GetClient() redis.UniversalClient { - return s.universalClient -} - -func (s *Plugin) Init(cfg config.Configurer, log logger.Logger) error { - const op = errors.Op("redis_plugin_init") - - if !cfg.Has(PluginName) { - return errors.E(op, errors.Disabled) - } - - err := cfg.UnmarshalKey(PluginName, &s.cfg) - if err != nil { - return errors.E(op, errors.Disabled, err) - } - - s.cfg.InitDefaults() - s.log = log - - s.universalClient = redis.NewUniversalClient(&redis.UniversalOptions{ - Addrs: s.cfg.Addrs, - DB: s.cfg.DB, - Username: s.cfg.Username, - Password: s.cfg.Password, - SentinelPassword: s.cfg.SentinelPassword, - MaxRetries: s.cfg.MaxRetries, - MinRetryBackoff: s.cfg.MaxRetryBackoff, - MaxRetryBackoff: s.cfg.MaxRetryBackoff, - DialTimeout: s.cfg.DialTimeout, - ReadTimeout: s.cfg.ReadTimeout, - WriteTimeout: s.cfg.WriteTimeout, - PoolSize: s.cfg.PoolSize, - MinIdleConns: s.cfg.MinIdleConns, - MaxConnAge: s.cfg.MaxConnAge, - PoolTimeout: s.cfg.PoolTimeout, - IdleTimeout: s.cfg.IdleTimeout, - IdleCheckFrequency: s.cfg.IdleCheckFreq, - ReadOnly: s.cfg.ReadOnly, - RouteByLatency: s.cfg.RouteByLatency, - RouteRandomly: s.cfg.RouteRandomly, - MasterName: s.cfg.MasterName, - }) - - return nil -} - -func (s *Plugin) Serve() chan error { - errCh := make(chan error, 1) - return errCh -} - -func (s Plugin) Stop() error { - return s.universalClient.Close() -} - -func (s *Plugin) Name() string { - return PluginName -} diff --git a/plugins/reload/config.go b/plugins/reload/config.go deleted file mode 100644 index 6fd3af70..00000000 --- a/plugins/reload/config.go +++ /dev/null @@ -1,62 +0,0 @@ -package reload - -import ( - "time" - - "github.com/spiral/errors" -) - -// Config is a Reload configuration point. -type Config struct { - // Interval is a global refresh interval - Interval time.Duration - - // Patterns is a global file patterns to watch. It will be applied to every directory in project - Patterns []string - - // Services is set of services which would be reloaded in case of FS changes - Services map[string]ServiceConfig -} - -type ServiceConfig struct { - // Enabled indicates that service must be watched, doest not required when any other option specified - Enabled bool - - // Recursive is options to use nested files from root folder - Recursive bool - - // Patterns is per-service specific files to watch - Patterns []string - - // Dirs is per-service specific dirs which will be combined with Patterns - Dirs []string - - // Ignore is set of files which would not be watched - Ignore []string -} - -// InitDefaults sets missing values to their default values. -func (c *Config) InitDefaults() { - if c.Interval == 0 { - c.Interval = time.Second - } - if c.Patterns == nil { - c.Patterns = []string{".php"} - } -} - -// Valid validates the configuration. -func (c *Config) Valid() error { - const op = errors.Op("reload_plugin_valid") - if c.Interval < time.Second { - return errors.E(op, errors.Str("too short interval")) - } - - if c.Services == nil { - return errors.E(op, errors.Str("should add at least 1 service")) - } else if len(c.Services) == 0 { - return errors.E(op, errors.Str("service initialized, however, no config added")) - } - - return nil -} diff --git a/plugins/reload/plugin.go b/plugins/reload/plugin.go deleted file mode 100644 index d76fb0a4..00000000 --- a/plugins/reload/plugin.go +++ /dev/null @@ -1,163 +0,0 @@ -package reload - -import ( - "os" - "strings" - "time" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/logger" - "github.com/spiral/roadrunner/v2/plugins/resetter" -) - -// PluginName contains default plugin name. -const PluginName string = "reload" -const thresholdChanBuffer uint = 1000 - -type Plugin struct { - cfg *Config - log logger.Logger - watcher *Watcher - services map[string]interface{} - res resetter.Resetter - stopc chan struct{} -} - -// Init controller service -func (s *Plugin) Init(cfg config.Configurer, log logger.Logger, res resetter.Resetter) error { - const op = errors.Op("reload_plugin_init") - if !cfg.Has(PluginName) { - return errors.E(op, errors.Disabled) - } - - err := cfg.UnmarshalKey(PluginName, &s.cfg) - if err != nil { - // disable plugin in case of error - return errors.E(op, errors.Disabled, err) - } - - s.cfg.InitDefaults() - - s.log = log - s.res = res - s.stopc = make(chan struct{}, 1) - s.services = make(map[string]interface{}) - - var configs []WatcherConfig - - for serviceName, serviceConfig := range s.cfg.Services { - ignored, err := ConvertIgnored(serviceConfig.Ignore) - if err != nil { - return errors.E(op, err) - } - configs = append(configs, WatcherConfig{ - ServiceName: serviceName, - Recursive: serviceConfig.Recursive, - Directories: serviceConfig.Dirs, - FilterHooks: func(filename string, patterns []string) error { - for i := 0; i < len(patterns); i++ { - if strings.Contains(filename, patterns[i]) { - return nil - } - } - return errors.E(op, errors.SkipFile) - }, - Files: make(map[string]os.FileInfo), - Ignored: ignored, - FilePatterns: append(serviceConfig.Patterns, s.cfg.Patterns...), - }) - } - - s.watcher, err = NewWatcher(configs, s.log) - if err != nil { - return errors.E(op, err) - } - - return nil -} - -func (s *Plugin) Serve() chan error { - const op = errors.Op("reload_plugin_serve") - errCh := make(chan error, 1) - if s.cfg.Interval < time.Second { - errCh <- errors.E(op, errors.Str("reload interval is too fast")) - return errCh - } - - // make a map with unique services - // so, if we would have a 100 events from http service - // in map we would see only 1 key and it's config - treshholdc := make(chan struct { - serviceConfig ServiceConfig - service string - }, thresholdChanBuffer) - - // use the same interval - timer := time.NewTimer(s.cfg.Interval) - - go func() { - for e := range s.watcher.Event { - treshholdc <- struct { - serviceConfig ServiceConfig - service string - }{serviceConfig: s.cfg.Services[e.service], service: e.service} - } - }() - - // map with config by services - updated := make(map[string]ServiceConfig, len(s.cfg.Services)) - - go func() { - for { - select { - case cfg := <-treshholdc: - // logic is following: - // restart - timer.Stop() - // replace previous value in map by more recent without adding new one - updated[cfg.service] = cfg.serviceConfig - // if we getting a lot of events, we shouldn't restart particular service on each of it (user doing batch move or very fast typing) - // instead, we are resetting the timer and wait for s.cfg.Interval time - // If there is no more events, we restart service only once - timer.Reset(s.cfg.Interval) - case <-timer.C: - if len(updated) > 0 { - for name := range updated { - err := s.res.ResetByName(name) - if err != nil { - timer.Stop() - errCh <- errors.E(op, err) - return - } - } - // zero map - updated = make(map[string]ServiceConfig, len(s.cfg.Services)) - } - case <-s.stopc: - timer.Stop() - return - } - } - }() - - go func() { - err := s.watcher.StartPolling(s.cfg.Interval) - if err != nil { - errCh <- errors.E(op, err) - return - } - }() - - return errCh -} - -func (s *Plugin) Stop() error { - s.watcher.Stop() - s.stopc <- struct{}{} - return nil -} - -func (s *Plugin) Name() string { - return PluginName -} diff --git a/plugins/reload/watcher.go b/plugins/reload/watcher.go deleted file mode 100644 index 8dde38de..00000000 --- a/plugins/reload/watcher.go +++ /dev/null @@ -1,374 +0,0 @@ -package reload - -import ( - "io/ioutil" - "os" - "path/filepath" - "sync" - "time" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/plugins/logger" -) - -// SimpleHook is used to filter by simple criteria, CONTAINS -type SimpleHook func(filename string, pattern []string) error - -// An Event describes an event that is received when files or directory -// changes occur. It includes the os.FileInfo of the changed file or -// directory and the type of event that's occurred and the full path of the file. -type Event struct { - Path string - Info os.FileInfo - - service string // type of service, http, grpc, etc... -} - -type WatcherConfig struct { - // service name - ServiceName string - - // Recursive or just add by singe directory - Recursive bool - - // Directories used per-service - Directories []string - - // simple hook, just CONTAINS - FilterHooks func(filename string, pattern []string) error - - // path to file with Files - Files map[string]os.FileInfo - - // Ignored Directories, used map for O(1) amortized get - Ignored map[string]struct{} - - // FilePatterns to ignore - FilePatterns []string -} - -type Watcher struct { - // main event channel - Event chan Event - close chan struct{} - - // ============================= - mu *sync.Mutex - - // indicates is walker started or not - started bool - - // config for each service - // need pointer here to assign files - watcherConfigs map[string]WatcherConfig - - // logger - log logger.Logger -} - -// Options is used to set Watcher Options -type Options func(*Watcher) - -// NewWatcher returns new instance of File Watcher -func NewWatcher(configs []WatcherConfig, log logger.Logger, options ...Options) (*Watcher, error) { - w := &Watcher{ - Event: make(chan Event), - mu: &sync.Mutex{}, - - log: log, - - close: make(chan struct{}), - - //workingDir: workDir, - watcherConfigs: make(map[string]WatcherConfig), - } - - // add watcherConfigs by service names - for _, v := range configs { - w.watcherConfigs[v.ServiceName] = v - } - - // apply options - for _, option := range options { - option(w) - } - err := w.initFs() - if err != nil { - return nil, err - } - - return w, nil -} - -// initFs makes initial map with files -func (w *Watcher) initFs() error { - const op = errors.Op("watcher_init_fs") - for srvName, config := range w.watcherConfigs { - fileList, err := w.retrieveFileList(srvName, config) - if err != nil { - return errors.E(op, err) - } - // workaround. in golang you can't assign to map in struct field - tmp := w.watcherConfigs[srvName] - tmp.Files = fileList - w.watcherConfigs[srvName] = tmp - } - return nil -} - -// ConvertIgnored is used to convert slice to map with ignored files -func ConvertIgnored(ignored []string) (map[string]struct{}, error) { - if len(ignored) == 0 { - return nil, nil - } - - ign := make(map[string]struct{}, len(ignored)) - for i := 0; i < len(ignored); i++ { - abs, err := filepath.Abs(ignored[i]) - if err != nil { - return nil, err - } - ign[abs] = struct{}{} - } - - return ign, nil -} - -// https://en.wikipedia.org/wiki/Inotify -// SetMaxFileEvents sets max file notify events for Watcher -// In case of file watch errors, this value can be increased system-wide -// For linux: set --> fs.inotify.max_user_watches = 600000 (under /etc/<choose_name_here>.conf) -// Add apply: sudo sysctl -p --system -// func SetMaxFileEvents(events int) Options { -// return func(watcher *Watcher) { -// watcher.maxFileWatchEvents = events -// } -// -// } - -// pass map from outside -func (w *Watcher) retrieveFilesSingle(serviceName, path string) (map[string]os.FileInfo, error) { - const op = errors.Op("watcher_retrieve_files_single") - stat, err := os.Stat(path) - if err != nil { - return nil, err - } - - filesList := make(map[string]os.FileInfo, 10) - filesList[path] = stat - - // if it's not a dir, return - if !stat.IsDir() { - return filesList, nil - } - - fileInfoList, err := ioutil.ReadDir(path) - if err != nil { - return nil, err - } - - // recursive calls are slow in compare to goto - // so, we will add files with goto pattern -outer: - for i := 0; i < len(fileInfoList); i++ { - // if file in ignored --> continue - if _, ignored := w.watcherConfigs[serviceName].Ignored[path]; ignored { - continue - } - - // if filename does not contain pattern --> ignore that file - if w.watcherConfigs[serviceName].FilePatterns != nil && w.watcherConfigs[serviceName].FilterHooks != nil { - err = w.watcherConfigs[serviceName].FilterHooks(fileInfoList[i].Name(), w.watcherConfigs[serviceName].FilePatterns) - if errors.Is(errors.SkipFile, err) { - continue outer - } - } - - filesList[fileInfoList[i].Name()] = fileInfoList[i] - } - - return filesList, nil -} - -func (w *Watcher) StartPolling(duration time.Duration) error { - w.mu.Lock() - const op = errors.Op("watcher_start_polling") - if w.started { - w.mu.Unlock() - return errors.E(op, errors.Str("already started")) - } - - w.started = true - w.mu.Unlock() - - return w.waitEvent(duration) -} - -// this is blocking operation -func (w *Watcher) waitEvent(d time.Duration) error { - ticker := time.NewTicker(d) - for { - select { - case <-w.close: - ticker.Stop() - // just exit - // no matter for the pollEvents - return nil - case <-ticker.C: - // this is not very effective way - // because we have to wait on Lock - // better is to listen files in parallel, but, since that would be used in debug... TODO - for serviceName := range w.watcherConfigs { - // TODO sync approach - fileList, _ := w.retrieveFileList(serviceName, w.watcherConfigs[serviceName]) - w.pollEvents(w.watcherConfigs[serviceName].ServiceName, fileList) - } - } - } -} - -// retrieveFileList get file list for service -func (w *Watcher) retrieveFileList(serviceName string, config WatcherConfig) (map[string]os.FileInfo, error) { - fileList := make(map[string]os.FileInfo) - if config.Recursive { - // walk through directories recursively - for i := 0; i < len(config.Directories); i++ { - // full path is workdir/relative_path - fullPath, err := filepath.Abs(config.Directories[i]) - if err != nil { - return nil, err - } - list, err := w.retrieveFilesRecursive(serviceName, fullPath) - if err != nil { - return nil, err - } - - for k := range list { - fileList[k] = list[k] - } - } - return fileList, nil - } - - for i := 0; i < len(config.Directories); i++ { - // full path is workdir/relative_path - fullPath, err := filepath.Abs(config.Directories[i]) - if err != nil { - return nil, err - } - - // list is pathToFiles with files - list, err := w.retrieveFilesSingle(serviceName, fullPath) - if err != nil { - return nil, err - } - - for pathToFile, file := range list { - fileList[pathToFile] = file - } - } - - return fileList, nil -} - -func (w *Watcher) retrieveFilesRecursive(serviceName, root string) (map[string]os.FileInfo, error) { - fileList := make(map[string]os.FileInfo) - - return fileList, filepath.Walk(root, func(path string, info os.FileInfo, err error) error { - const op = errors.Op("retrieve files recursive") - if err != nil { - return errors.E(op, err) - } - - // If path is ignored and it's a directory, skip the directory. If it's - // ignored and it's a single file, skip the file. - _, ignored := w.watcherConfigs[serviceName].Ignored[path] - if ignored { - if info.IsDir() { - // if it's dir, ignore whole - return filepath.SkipDir - } - return nil - } - - // if filename does not contain pattern --> ignore that file - err = w.watcherConfigs[serviceName].FilterHooks(info.Name(), w.watcherConfigs[serviceName].FilePatterns) - if errors.Is(errors.SkipFile, err) { - return nil - } - - // Add the path and it's info to the file list. - fileList[path] = info - return nil - }) -} - -func (w *Watcher) pollEvents(serviceName string, files map[string]os.FileInfo) { - w.mu.Lock() - defer w.mu.Unlock() - - // Store create and remove events for use to check for rename events. - creates := make(map[string]os.FileInfo) - removes := make(map[string]os.FileInfo) - - // Check for removed files. - for pth := range w.watcherConfigs[serviceName].Files { - if _, found := files[pth]; !found { - removes[pth] = w.watcherConfigs[serviceName].Files[pth] - w.log.Debug("file added to the list of removed files", "path", pth, "name", w.watcherConfigs[serviceName].Files[pth].Name(), "size", w.watcherConfigs[serviceName].Files[pth].Size()) - } - } - - // Check for created files, writes and chmods. - for pth := range files { - if files[pth].IsDir() { - continue - } - oldInfo, found := w.watcherConfigs[serviceName].Files[pth] - if !found { - // A file was created. - creates[pth] = files[pth] - w.log.Debug("file was created", "path", pth, "name", files[pth].Name(), "size", files[pth].Size()) - continue - } - - if oldInfo.ModTime() != files[pth].ModTime() || oldInfo.Mode() != files[pth].Mode() { - w.watcherConfigs[serviceName].Files[pth] = files[pth] - w.log.Debug("file was updated", "path", pth, "name", files[pth].Name(), "size", files[pth].Size()) - w.Event <- Event{ - Path: pth, - Info: files[pth], - service: serviceName, - } - } - } - - // Send all the remaining create and remove events. - for pth := range creates { - // add file to the plugin watch files - w.watcherConfigs[serviceName].Files[pth] = creates[pth] - w.log.Debug("file was added to watcher", "path", pth, "name", creates[pth].Name(), "size", creates[pth].Size()) - - w.Event <- Event{ - Path: pth, - Info: creates[pth], - service: serviceName, - } - } - - for pth := range removes { - // delete path from the config - delete(w.watcherConfigs[serviceName].Files, pth) - w.log.Debug("file was removed from watcher", "path", pth, "name", removes[pth].Name(), "size", removes[pth].Size()) - - w.Event <- Event{ - Path: pth, - Info: removes[pth], - service: serviceName, - } - } -} - -func (w *Watcher) Stop() { - w.close <- struct{}{} -} diff --git a/plugins/resetter/interface.go b/plugins/resetter/interface.go deleted file mode 100644 index 47d8d791..00000000 --- a/plugins/resetter/interface.go +++ /dev/null @@ -1,17 +0,0 @@ -package resetter - -// If plugin implements Resettable interface, than it state can be resetted without reload in runtime via RPC/HTTP -type Resettable interface { - // Reset reload all plugins - Reset() error -} - -// Resetter interface is the Resetter plugin main interface -type Resetter interface { - // Reset all registered plugins - ResetAll() error - // Reset by plugin name - ResetByName(string) error - // GetAll registered plugins - GetAll() []string -} diff --git a/plugins/resetter/plugin.go b/plugins/resetter/plugin.go deleted file mode 100644 index ee0deda6..00000000 --- a/plugins/resetter/plugin.go +++ /dev/null @@ -1,80 +0,0 @@ -package resetter - -import ( - endure "github.com/spiral/endure/pkg/container" - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/plugins/logger" -) - -const PluginName = "resetter" - -type Plugin struct { - registry map[string]Resettable - log logger.Logger -} - -func (p *Plugin) ResetAll() error { - const op = errors.Op("resetter_plugin_reset_all") - for name := range p.registry { - err := p.registry[name].Reset() - if err != nil { - return errors.E(op, err) - } - } - return nil -} - -func (p *Plugin) ResetByName(plugin string) error { - const op = errors.Op("resetter_plugin_reset_by_name") - if plugin, ok := p.registry[plugin]; ok { - return plugin.Reset() - } - return errors.E(op, errors.Errorf("can't find plugin: %s", plugin)) -} - -func (p *Plugin) GetAll() []string { - all := make([]string, 0, len(p.registry)) - for name := range p.registry { - all = append(all, name) - } - return all -} - -func (p *Plugin) Init(log logger.Logger) error { - p.registry = make(map[string]Resettable) - p.log = log - return nil -} - -// Reset named service. -func (p *Plugin) Reset(name string) error { - svc, ok := p.registry[name] - if !ok { - return errors.E("no such service", errors.Str(name)) - } - - return svc.Reset() -} - -// RegisterTarget resettable service. -func (p *Plugin) RegisterTarget(name endure.Named, r Resettable) error { - p.registry[name.Name()] = r - return nil -} - -// Collects declares services to be collected. -func (p *Plugin) Collects() []interface{} { - return []interface{}{ - p.RegisterTarget, - } -} - -// Name of the service. -func (p *Plugin) Name() string { - return PluginName -} - -// RPCService returns associated rpc service. -func (p *Plugin) RPC() interface{} { - return &rpc{srv: p, log: p.log} -} diff --git a/plugins/resetter/rpc.go b/plugins/resetter/rpc.go deleted file mode 100644 index 69c955b0..00000000 --- a/plugins/resetter/rpc.go +++ /dev/null @@ -1,30 +0,0 @@ -package resetter - -import "github.com/spiral/roadrunner/v2/plugins/logger" - -type rpc struct { - srv *Plugin - log logger.Logger -} - -// List all resettable plugins. -func (rpc *rpc) List(_ bool, list *[]string) error { - rpc.log.Debug("started List method") - *list = make([]string, 0) - - for name := range rpc.srv.registry { - *list = append(*list, name) - } - rpc.log.Debug("services list", "services", *list) - - rpc.log.Debug("finished List method") - return nil -} - -// Reset named plugin. -func (rpc *rpc) Reset(service string, done *bool) error { - rpc.log.Debug("started Reset method for the service", "service", service) - defer rpc.log.Debug("finished Reset method for the service", "service", service) - *done = true - return rpc.srv.Reset(service) -} diff --git a/plugins/rpc/config.go b/plugins/rpc/config.go deleted file mode 100644 index 88ad7f0e..00000000 --- a/plugins/rpc/config.go +++ /dev/null @@ -1,46 +0,0 @@ -package rpc - -import ( - "errors" - "net" - "strings" - - "github.com/spiral/roadrunner/v2/utils" -) - -// Config defines RPC service config. -type Config struct { - // Listen string - Listen string -} - -// InitDefaults allows to init blank config with pre-defined set of default values. -func (c *Config) InitDefaults() { - if c.Listen == "" { - c.Listen = "tcp://127.0.0.1:6001" - } -} - -// Valid returns nil if config is valid. -func (c *Config) Valid() error { - if dsn := strings.Split(c.Listen, "://"); len(dsn) != 2 { - return errors.New("invalid socket DSN (tcp://:6001, unix://file.sock)") - } - - return nil -} - -// Listener creates new rpc socket Listener. -func (c *Config) Listener() (net.Listener, error) { - return utils.CreateListener(c.Listen) -} - -// Dialer creates rpc socket Dialer. -func (c *Config) Dialer() (net.Conn, error) { - dsn := strings.Split(c.Listen, "://") - if len(dsn) != 2 { - return nil, errors.New("invalid socket DSN (tcp://:6001, unix://file.sock)") - } - - return net.Dial(dsn[0], dsn[1]) -} diff --git a/plugins/rpc/doc/plugin_arch.drawio b/plugins/rpc/doc/plugin_arch.drawio deleted file mode 100644 index dec5f0b2..00000000 --- a/plugins/rpc/doc/plugin_arch.drawio +++ /dev/null @@ -1 +0,0 @@ -<mxfile host="Electron" modified="2020-10-19T17:14:19.125Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.7.9 Chrome/85.0.4183.121 Electron/10.1.3 Safari/537.36" etag="2J39x4EyFr1zaE9BXKM4" version="13.7.9" type="device"><diagram id="q2oMKs6VHyn7y0AfAXBL" name="Page-1">7Vttc9o4EP41zLQfksE2GPIxQHPXu7RlQntt7ptiC1sX2XJlOUB//a1sGdtIJDQFnE6YyUys1YutfR7trlai44yj5R8cJeEH5mPasbv+suNMOrZtORcO/JOSVSEZWv1CEHDiq0aVYEZ+YCXsKmlGfJw2GgrGqCBJU+ixOMaeaMgQ52zRbDZntPnWBAVYE8w8RHXpV+KLUEkt96Kq+BOTIFSvHtqDoiJCZWM1kzREPlvURM67jjPmjIniKVqOMZXKK/VS9LvaUrv+MI5jsUuHL/zu0yx7//HT3Pln8vfN59vvS/usVHMqVuWMsQ8KUEXGRcgCFiP6rpKOOMtiH8thu1Cq2lwzloDQAuF/WIiVQhNlgoEoFBFVtXhJxLfa860c6ryvSpOlGjkvrMpCLPjqW71Q6yWLVbe8VPabs1hcoYhQKRizjBPMYcIf8UJVqq+8gGKhC6mArTpWohQG8lSrfz88xF8/ds/+uiLe7MsXtLiyZ2clVxEPsHik3WDNBFhCmEUYvh36cUyRIA/N70CKy8G6XQU3PCjEfwZ9q030K8RvazVPoV8BftvA+7dE33KOBP9jX/mAaKbedDOFkbpTmgUk1qjRBH4REoFnCcr1sADj3wT55xVv0PMD5gIvayJdU6rWGSi3otyMYw3OlWRRme21VwlrFtsdHEi9jqbe9zERha+ak0DTL0xVNJWIKAliePZAMaA+ZyQVQsA5XaqKiPh+sShxSn6gu3woiU7CSCzyCfVHnf5EjgXrMC103go+3Q18hho6QwM4pfPcOzg9DZwJTnDspyBk8Rqk8ylnDxCB8N8DLcveD1z2BlxWWa4vpu4x8epreOmuK/YvZcQnIaAoTYm34XeO5kMMun/aFRjdj45QDYG+AYBStrMHUW+YSgpWBOgNtxCgHKJwgapXPercGKhvbwxkbQxUKEYbKCfJetrP542r8aa0vt0U9gsE1rpzKfWVeK97ia+Xc41glolhB1viA32Jj+3O5YhIXc9loAHFEczdpRKWO95Ay/2eyZ1UrqqzQq8S14tkmeurrIanQP0vRvmVQYA052WwVAwHE7+rXrHBp/bCI3f4tPu1jMGReyCwLT06KoLPVPDMExnHmvrSBYkoinGpIVWz07oUcm8y8kJC/Wu0YpmcXiqQd1+WRiHj5AcMi0qIoJqXMNhuo8VM9lQLO1/oeFqiY22IPqBlo+E1SoUSeIxSlKTkbj2NCGwhiUdMCBbt0/k8P47uuQarULapE8Vye4diytDg+ke7R2hAKHaPx4wyIMYkZgWBCKUbopJDFM/FVgalsOEhcXCdt5n0KsmNUoUUMeg7p3kgEoI/wHG+axZIbPUHI9DyWIYl4BnsMZStqpw7iwT22WMWw1wQycHFwKMFTsUvU+Tx1fk0cUr34e7GE/tQBqV0SxpNpJGeYf6QK+VNjMX5TeK9PbGlTbb07ZbZYl1sYUsKTCEeltvAIlKr+aNuSqHqxJw2mTMwBC7HZY6eOSiYMydYni3IeHH8aILnxIk9c8Lq9tomxQ7pCUpyqAszUZ4lWc/iw3qXqQjwOc+8n1kaSRydJI6BEBTdYTqF3WixH57woq1h0/ryueDsGLAOD0UFPeNQ2AcYPmT+G7FK8NvCTMjHkzdply1HdCfmIzhDHvMIR3Av9jDVrKTOjjnUCzPaRzpN1Ra+Ciafk9Xo/nK6wmAsfpMMhrZ+DazZmsHoNTNdPcvgD1xDpmuwB4dgpIX9dLxY8aTKdZ78wp7osn2t/lQyw8SZg3kFPTmqcSZGkTIsgNeJLS2yxZTMOCpb9IizMigcByQFmyITGlYxV4A2o0iqyc+PvOGvYYPmTNbl2Xgzq17Wgdie/Ia1cYFkqO8pHftAx2FGVPUMVVJkul8VLK61cXJl67gc6pTSbAvcVgJ245259TW5Vm5M1k6i9xPlO7uG+b1Ww3zdOVdXCk5h/pHsgtM0C64p7WNywqWz3j8tdsgLX0tXHJ+itiNFbVsu176UIN/SL7xMOQOFR2lOl7a9fN3MP4rYHpbzxq7dsGk/1O1QMzT6nYOAqSAZFqaPvY78hYecQIBjzJGQgbNgsk2UeaH8Ji93RdLvefdY3ohDeZyNlx7G8iGjJMqvA5/pV61fE9YGy93fU6ANxer3NcWNwupXSs67/wE=</diagram></mxfile>
\ No newline at end of file diff --git a/plugins/rpc/interface.go b/plugins/rpc/interface.go deleted file mode 100644 index 683fd2ec..00000000 --- a/plugins/rpc/interface.go +++ /dev/null @@ -1,7 +0,0 @@ -package rpc - -// RPCer declares the ability to create set of public RPC methods. -type RPCer interface { - // Provides RPC methods for the given service. - RPC() interface{} -} diff --git a/plugins/rpc/plugin.go b/plugins/rpc/plugin.go deleted file mode 100644 index e13768f0..00000000 --- a/plugins/rpc/plugin.go +++ /dev/null @@ -1,161 +0,0 @@ -package rpc - -import ( - "net" - "net/rpc" - "sync/atomic" - - endure "github.com/spiral/endure/pkg/container" - "github.com/spiral/errors" - goridgeRpc "github.com/spiral/goridge/v3/pkg/rpc" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/logger" -) - -// PluginName contains default plugin name. -const PluginName = "RPC" - -type pluggable struct { - service RPCer - name string -} - -// Plugin is RPC service. -type Plugin struct { - cfg Config - log logger.Logger - rpc *rpc.Server - // set of the plugins, which are implement RPCer interface and can be plugged into the RR via RPC - plugins []pluggable - listener net.Listener - closed *uint32 -} - -// Init rpc service. Must return true if service is enabled. -func (s *Plugin) Init(cfg config.Configurer, log logger.Logger) error { - const op = errors.Op("rpc_plugin_init") - if !cfg.Has(PluginName) { - return errors.E(op, errors.Disabled) - } - - err := cfg.UnmarshalKey(PluginName, &s.cfg) - if err != nil { - return errors.E(op, errors.Disabled, err) - } - s.cfg.InitDefaults() - - s.log = log - state := uint32(0) - s.closed = &state - atomic.StoreUint32(s.closed, 0) - - return s.cfg.Valid() -} - -// Serve serves the service. -func (s *Plugin) Serve() chan error { - const op = errors.Op("rpc_plugin_serve") - errCh := make(chan error, 1) - - s.rpc = rpc.NewServer() - - services := make([]string, 0, len(s.plugins)) - - // Attach all services - for i := 0; i < len(s.plugins); i++ { - err := s.Register(s.plugins[i].name, s.plugins[i].service.RPC()) - if err != nil { - errCh <- errors.E(op, err) - return errCh - } - - services = append(services, s.plugins[i].name) - } - - var err error - s.listener, err = s.cfg.Listener() - if err != nil { - errCh <- err - return errCh - } - - s.log.Debug("Started RPC service", "address", s.cfg.Listen, "services", services) - - go func() { - for { - conn, err := s.listener.Accept() - if err != nil { - if atomic.LoadUint32(s.closed) == 1 { - // just continue, this is not a critical issue, we just called Stop - return - } - - s.log.Error("listener accept error", "error", err) - errCh <- errors.E(errors.Op("listener accept"), errors.Serve, err) - return - } - - go s.rpc.ServeCodec(goridgeRpc.NewCodec(conn)) - } - }() - - return errCh -} - -// Stop stops the service. -func (s *Plugin) Stop() error { - const op = errors.Op("rpc_plugin_stop") - // store closed state - atomic.StoreUint32(s.closed, 1) - err := s.listener.Close() - if err != nil { - return errors.E(op, err) - } - return nil -} - -// Name contains service name. -func (s *Plugin) Name() string { - return PluginName -} - -// Depends declares services to collect for RPC. -func (s *Plugin) Collects() []interface{} { - return []interface{}{ - s.RegisterPlugin, - } -} - -// RegisterPlugin registers RPC service plugin. -func (s *Plugin) RegisterPlugin(name endure.Named, p RPCer) { - s.plugins = append(s.plugins, pluggable{ - service: p, - name: name.Name(), - }) -} - -// Register publishes in the server the set of methods of the -// receiver value that satisfy the following conditions: -// - exported method of exported type -// - two arguments, both of exported type -// - the second argument is a pointer -// - one return value, of type error -// It returns an error if the receiver is not an exported type or has -// no suitable methods. It also logs the error using package log. -func (s *Plugin) Register(name string, svc interface{}) error { - if s.rpc == nil { - return errors.E("RPC service is not configured") - } - - return s.rpc.RegisterName(name, svc) -} - -// Client creates new RPC client. -func (s *Plugin) Client() (*rpc.Client, error) { - conn, err := s.cfg.Dialer() - if err != nil { - return nil, err - } - - return rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)), nil -} diff --git a/plugins/server/config.go b/plugins/server/config.go deleted file mode 100644 index a4b0d91c..00000000 --- a/plugins/server/config.go +++ /dev/null @@ -1,60 +0,0 @@ -package server - -import ( - "time" -) - -// All config (.rr.yaml) -// For other section use pointer to distinguish between `empty` and `not present` -type Config struct { - // Server config section - Server struct { - // Command to run as application. - Command string `mapstructure:"command"` - // User to run application under. - User string `mapstructure:"user"` - // Group to run application under. - Group string `mapstructure:"group"` - // Env represents application environment. - Env Env `mapstructure:"env"` - // Relay defines connection method and factory to be used to connect to workers: - // "pipes", "tcp://:6001", "unix://rr.sock" - // This config section must not change on re-configuration. - Relay string `mapstructure:"relay"` - // RelayTimeout defines for how long socket factory will be waiting for worker connection. This config section - // must not change on re-configuration. Defaults to 60s. - RelayTimeout time.Duration `mapstructure:"relay_timeout"` - } `mapstructure:"server"` - - // we just need to know if the section exist, we don't need to read config from it - RPC *struct { - Listen string `mapstructure:"listen"` - } `mapstructure:"rpc"` - Logs *struct { - } `mapstructure:"logs"` - HTTP *struct { - } `mapstructure:"http"` - Redis *struct { - } `mapstructure:"redis"` - Boltdb *struct { - } `mapstructure:"boltdb"` - Memcached *struct { - } `mapstructure:"memcached"` - Memory *struct { - } `mapstructure:"memory"` - Metrics *struct { - } `mapstructure:"metrics"` - Reload *struct { - } `mapstructure:"reload"` -} - -// InitDefaults for the server config -func (cfg *Config) InitDefaults() { - if cfg.Server.Relay == "" { - cfg.Server.Relay = "pipes" - } - - if cfg.Server.RelayTimeout == 0 { - cfg.Server.RelayTimeout = time.Second * 60 - } -} diff --git a/plugins/server/interface.go b/plugins/server/interface.go deleted file mode 100644 index 22f02685..00000000 --- a/plugins/server/interface.go +++ /dev/null @@ -1,20 +0,0 @@ -package server - -import ( - "context" - "os/exec" - - "github.com/spiral/roadrunner/v2/pkg/events" - "github.com/spiral/roadrunner/v2/pkg/pool" - "github.com/spiral/roadrunner/v2/pkg/worker" -) - -// Env variables type alias -type Env map[string]string - -// Server creates workers for the application. -type Server interface { - CmdFactory(env Env) (func() *exec.Cmd, error) - NewWorker(ctx context.Context, env Env, listeners ...events.Listener) (*worker.Process, error) - NewWorkerPool(ctx context.Context, opt pool.Config, env Env, listeners ...events.Listener) (pool.Pool, error) -} diff --git a/plugins/server/plugin.go b/plugins/server/plugin.go deleted file mode 100644 index 9cdb8401..00000000 --- a/plugins/server/plugin.go +++ /dev/null @@ -1,254 +0,0 @@ -package server - -import ( - "context" - "fmt" - "os" - "os/exec" - "strings" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/pkg/transport" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/logger" - - // core imports - "github.com/spiral/roadrunner/v2/pkg/events" - "github.com/spiral/roadrunner/v2/pkg/pool" - "github.com/spiral/roadrunner/v2/pkg/transport/pipe" - "github.com/spiral/roadrunner/v2/pkg/transport/socket" - "github.com/spiral/roadrunner/v2/pkg/worker" - "github.com/spiral/roadrunner/v2/utils" -) - -// PluginName for the server -const PluginName = "server" - -// RR_RELAY env variable key (internal) -const RR_RELAY = "RR_RELAY" //nolint:golint,stylecheck -// RR_RPC env variable key (internal) if the RPC presents -const RR_RPC = "RR_RPC" //nolint:golint,stylecheck - -// Plugin manages worker -type Plugin struct { - cfg Config - log logger.Logger - factory transport.Factory -} - -// Init application provider. -func (server *Plugin) Init(cfg config.Configurer, log logger.Logger) error { - const op = errors.Op("server_plugin_init") - if !cfg.Has(PluginName) { - return errors.E(op, errors.Disabled) - } - err := cfg.Unmarshal(&server.cfg) - if err != nil { - return errors.E(op, errors.Init, err) - } - server.cfg.InitDefaults() - server.log = log - - server.factory, err = server.initFactory() - if err != nil { - return errors.E(err) - } - - return nil -} - -// Name contains service name. -func (server *Plugin) Name() string { - return PluginName -} - -// Serve (Start) server plugin (just a mock here to satisfy interface) -func (server *Plugin) Serve() chan error { - errCh := make(chan error, 1) - return errCh -} - -// Stop used to close chosen in config factory -func (server *Plugin) Stop() error { - if server.factory == nil { - return nil - } - - return server.factory.Close() -} - -// CmdFactory provides worker command factory associated with given context. -func (server *Plugin) CmdFactory(env Env) (func() *exec.Cmd, error) { - const op = errors.Op("server_plugin_cmd_factory") - var cmdArgs []string - - // create command according to the config - cmdArgs = append(cmdArgs, strings.Split(server.cfg.Server.Command, " ")...) - if len(cmdArgs) < 2 { - return nil, errors.E(op, errors.Str("should be in form of `php <script>")) - } - if cmdArgs[0] != "php" { - return nil, errors.E(op, errors.Str("first arg in command should be `php`")) - } - - _, err := os.Stat(cmdArgs[1]) - if err != nil { - return nil, errors.E(op, err) - } - return func() *exec.Cmd { - cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) //nolint:gosec - utils.IsolateProcess(cmd) - - // if user is not empty, and OS is linux or macos - // execute php worker from that particular user - if server.cfg.Server.User != "" { - err := utils.ExecuteFromUser(cmd, server.cfg.Server.User) - if err != nil { - return nil - } - } - - cmd.Env = server.setEnv(env) - - return cmd - }, nil -} - -// NewWorker issues new standalone worker. -func (server *Plugin) NewWorker(ctx context.Context, env Env, listeners ...events.Listener) (*worker.Process, error) { - const op = errors.Op("server_plugin_new_worker") - - list := make([]events.Listener, 0, len(listeners)) - list = append(list, server.collectWorkerLogs) - - spawnCmd, err := server.CmdFactory(env) - if err != nil { - return nil, errors.E(op, err) - } - - w, err := server.factory.SpawnWorkerWithTimeout(ctx, spawnCmd(), list...) - if err != nil { - return nil, errors.E(op, err) - } - - return w, nil -} - -// NewWorkerPool issues new worker pool. -func (server *Plugin) NewWorkerPool(ctx context.Context, opt pool.Config, env Env, listeners ...events.Listener) (pool.Pool, error) { - const op = errors.Op("server_plugin_new_worker_pool") - spawnCmd, err := server.CmdFactory(env) - if err != nil { - return nil, errors.E(op, err) - } - - list := make([]events.Listener, 0, 1) - list = append(list, server.collectPoolLogs) - if len(listeners) != 0 { - list = append(list, listeners...) - } - - p, err := pool.Initialize(ctx, spawnCmd, server.factory, opt, pool.AddListeners(list...)) - if err != nil { - return nil, errors.E(op, err) - } - - return p, nil -} - -// creates relay and worker factory. -func (server *Plugin) initFactory() (transport.Factory, error) { - const op = errors.Op("server_plugin_init_factory") - if server.cfg.Server.Relay == "" || server.cfg.Server.Relay == "pipes" { - return pipe.NewPipeFactory(), nil - } - - dsn := strings.Split(server.cfg.Server.Relay, "://") - if len(dsn) != 2 { - return nil, errors.E(op, errors.Network, errors.Str("invalid DSN (tcp://:6001, unix://file.sock)")) - } - - lsn, err := utils.CreateListener(server.cfg.Server.Relay) - if err != nil { - return nil, errors.E(op, errors.Network, err) - } - - switch dsn[0] { - // sockets group - case "unix": - return socket.NewSocketServer(lsn, server.cfg.Server.RelayTimeout), nil - case "tcp": - return socket.NewSocketServer(lsn, server.cfg.Server.RelayTimeout), nil - default: - return nil, errors.E(op, errors.Network, errors.Str("invalid DSN (tcp://:6001, unix://file.sock)")) - } -} - -func (server *Plugin) setEnv(e Env) []string { - env := append(os.Environ(), fmt.Sprintf(RR_RELAY+"=%s", server.cfg.Server.Relay)) - for k, v := range e { - env = append(env, fmt.Sprintf("%s=%s", strings.ToUpper(k), v)) - } - - if server.cfg.RPC != nil && server.cfg.RPC.Listen != "" { - env = append(env, fmt.Sprintf("%s=%s", RR_RPC, server.cfg.RPC.Listen)) - } - - // set env variables from the config - if len(server.cfg.Server.Env) > 0 { - for k, v := range server.cfg.Server.Env { - env = append(env, fmt.Sprintf("%s=%s", strings.ToUpper(k), v)) - } - } - - return env -} - -func (server *Plugin) collectPoolLogs(event interface{}) { - if we, ok := event.(events.PoolEvent); ok { - switch we.Event { - case events.EventMaxMemory: - server.log.Warn("worker max memory reached", "pid", we.Payload.(worker.BaseProcess).Pid()) - case events.EventNoFreeWorkers: - server.log.Warn("no free workers in pool", "error", we.Payload.(error).Error()) - case events.EventPoolError: - server.log.Error("pool error", "error", we.Payload.(error).Error()) - case events.EventSupervisorError: - server.log.Error("pool supervisor error", "error", we.Payload.(error).Error()) - case events.EventTTL: - server.log.Warn("worker TTL reached", "pid", we.Payload.(worker.BaseProcess).Pid()) - case events.EventWorkerConstruct: - if _, ok := we.Payload.(error); ok { - server.log.Error("worker construction error", "error", we.Payload.(error).Error()) - return - } - server.log.Debug("worker constructed", "pid", we.Payload.(worker.BaseProcess).Pid()) - case events.EventWorkerDestruct: - server.log.Debug("worker destructed", "pid", we.Payload.(worker.BaseProcess).Pid()) - case events.EventExecTTL: - server.log.Warn("worker exec timeout reached", "error", we.Payload.(error).Error()) - case events.EventIdleTTL: - server.log.Warn("worker idle timeout reached", "pid", we.Payload.(worker.BaseProcess).Pid()) - } - } - - if we, ok := event.(events.WorkerEvent); ok { - switch we.Event { - case events.EventWorkerError: - server.log.Error(we.Payload.(error).Error(), "pid", we.Worker.(worker.BaseProcess).Pid()) - case events.EventWorkerLog: - server.log.Debug(strings.TrimRight(string(we.Payload.([]byte)), " \n\t"), "pid", we.Worker.(worker.BaseProcess).Pid()) - } - } -} - -func (server *Plugin) collectWorkerLogs(event interface{}) { - if we, ok := event.(events.WorkerEvent); ok { - switch we.Event { - case events.EventWorkerError: - server.log.Error(we.Payload.(error).Error(), "pid", we.Worker.(worker.BaseProcess).Pid()) - case events.EventWorkerLog: - server.log.Debug(strings.TrimRight(string(we.Payload.([]byte)), " \n\t"), "pid", we.Worker.(worker.BaseProcess).Pid()) - } - } -} diff --git a/plugins/static/config.go b/plugins/static/config.go deleted file mode 100644 index 90efea76..00000000 --- a/plugins/static/config.go +++ /dev/null @@ -1,76 +0,0 @@ -package static - -import ( - "os" - "path" - "strings" - - "github.com/spiral/errors" -) - -// Config describes file location and controls access to them. -type Config struct { - Static *struct { - // Dir contains name of directory to control access to. - Dir string - - // Forbid specifies list of file extensions which are forbidden for access. - // Example: .php, .exe, .bat, .htaccess and etc. - Forbid []string - - // Always specifies list of extensions which must always be served by static - // service, even if file not found. - Always []string - - // Request headers to add to every static. - Request map[string]string - - // Response headers to add to every static. - Response map[string]string - } -} - -// Valid returns nil if config is valid. -func (c *Config) Valid() error { - const op = errors.Op("static_plugin_valid") - st, err := os.Stat(c.Static.Dir) - if err != nil { - if os.IsNotExist(err) { - return errors.E(op, errors.Errorf("root directory '%s' does not exists", c.Static.Dir)) - } - - return err - } - - if !st.IsDir() { - return errors.E(op, errors.Errorf("invalid root directory '%s'", c.Static.Dir)) - } - - return nil -} - -// AlwaysForbid must return true if file extension is not allowed for the upload. -func (c *Config) AlwaysForbid(filename string) bool { - ext := strings.ToLower(path.Ext(filename)) - - for _, v := range c.Static.Forbid { - if ext == v { - return true - } - } - - return false -} - -// AlwaysServe must indicate that file is expected to be served by static service. -func (c *Config) AlwaysServe(filename string) bool { - ext := strings.ToLower(path.Ext(filename)) - - for _, v := range c.Static.Always { - if ext == v { - return true - } - } - - return false -} diff --git a/plugins/static/plugin.go b/plugins/static/plugin.go deleted file mode 100644 index 1687cf11..00000000 --- a/plugins/static/plugin.go +++ /dev/null @@ -1,118 +0,0 @@ -package static - -import ( - "net/http" - "path" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/logger" -) - -// ID contains default service name. -const PluginName = "static" - -const RootPluginName = "http" - -// Plugin serves static files. Potentially convert into middleware? -type Plugin struct { - // server configuration (location, forbidden files and etc) - cfg *Config - - log logger.Logger - - // root is initiated http directory - root http.Dir -} - -// Init must return configure service and return true if service hasStatus enabled. Must return error in case of -// misconfiguration. Services must not be used without proper configuration pushed first. -func (s *Plugin) Init(cfg config.Configurer, log logger.Logger) error { - const op = errors.Op("static_plugin_init") - if !cfg.Has(RootPluginName) { - return errors.E(op, errors.Disabled) - } - - err := cfg.UnmarshalKey(RootPluginName, &s.cfg) - if err != nil { - return errors.E(op, errors.Disabled, err) - } - - if s.cfg.Static == nil { - return errors.E(op, errors.Disabled) - } - - s.log = log - s.root = http.Dir(s.cfg.Static.Dir) - - err = s.cfg.Valid() - if err != nil { - return errors.E(op, errors.Disabled, err) - } - - return nil -} - -func (s *Plugin) Name() string { - return PluginName -} - -// middleware must return true if request/response pair is handled within the middleware. -func (s *Plugin) Middleware(next http.Handler) http.HandlerFunc { - // Define the http.HandlerFunc - return func(w http.ResponseWriter, r *http.Request) { - if s.cfg.Static.Request != nil { - for k, v := range s.cfg.Static.Request { - r.Header.Add(k, v) - } - } - - if s.cfg.Static.Response != nil { - for k, v := range s.cfg.Static.Response { - w.Header().Set(k, v) - } - } - - if !s.handleStatic(w, r) { - next.ServeHTTP(w, r) - } - } -} - -func (s *Plugin) handleStatic(w http.ResponseWriter, r *http.Request) bool { - fPath := path.Clean(r.URL.Path) - - if s.cfg.AlwaysForbid(fPath) { - return false - } - - f, err := s.root.Open(fPath) - if err != nil { - s.log.Error("file open error", "error", err) - if s.cfg.AlwaysServe(fPath) { - w.WriteHeader(404) - return true - } - - return false - } - defer func() { - err = f.Close() - if err != nil { - s.log.Error("file closing error", "error", err) - } - }() - - d, err := f.Stat() - if err != nil { - return false - } - - // do not serve directories - if d.IsDir() { - return false - } - - http.ServeContent(w, r, d.Name(), d.ModTime(), f) - return true -} diff --git a/systemd/rr.service b/systemd/rr.service deleted file mode 100755 index 6e4d5914..00000000 --- a/systemd/rr.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=High-performance PHP application server - -[Service] -Type=simple -ExecStart=/usr/local/bin/roadrunner serve -v -d -c <path/to/.rr.yaml> -Restart=always -RestartSec=30 - -[Install] -WantedBy=default.target
\ No newline at end of file diff --git a/tests/broken.php b/tests/broken.php deleted file mode 100644 index 1f869b2d..00000000 --- a/tests/broken.php +++ /dev/null @@ -1,14 +0,0 @@ -<?php -/** - * @var Goridge\RelayInterface $relay - */ - -use Spiral\Goridge; -use Spiral\RoadRunner; - -$rr = new RoadRunner\Worker($relay); - -while ($in = $rr->waitPayload()) { - echo undefined_function(); - $rr->send((string)$in->body, null); -} diff --git a/tests/client.php b/tests/client.php deleted file mode 100644 index c00cece1..00000000 --- a/tests/client.php +++ /dev/null @@ -1,35 +0,0 @@ -<?php - -use Spiral\Goridge; - -ini_set('display_errors', 'stderr'); -require __DIR__ . "/vendor/autoload.php"; - -if (count($argv) < 3) { - die("need 2 arguments"); -} - -list($test, $goridge) = [$argv[1], $argv[2]]; - -switch ($goridge) { - case "pipes": - $relay = new Goridge\StreamRelay(STDIN, STDOUT); - break; - - case "tcp": - $relay = new Goridge\SocketRelay("localhost", 9007); - break; - - case "unix": - $relay = new Goridge\SocketRelay( - "sock.unix", - null, - Goridge\SocketRelay::SOCK_UNIX - ); - break; - - default: - die("invalid protocol selection"); -} - -require_once sprintf("%s/%s.php", __DIR__, $test); diff --git a/tests/composer.json b/tests/composer.json deleted file mode 100644 index 0cf74581..00000000 --- a/tests/composer.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "minimum-stability": "dev", - "prefer-stable": true, - "require": { - "nyholm/psr7": "^1.3", - "spiral/roadrunner": "^2.0", - "spiral/roadrunner-http": "^2.0", - "temporal/sdk": "dev-master" - } -} diff --git a/tests/delay.php b/tests/delay.php deleted file mode 100644 index f0435b05..00000000 --- a/tests/delay.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php -/** - * @var Goridge\RelayInterface $relay - */ - -use Spiral\Goridge; -use Spiral\RoadRunner; - -$rr = new RoadRunner\Worker($relay); - -while ($in = $rr->waitPayload()) { - try { - usleep($in->body * 1000); - $rr->send(''); - } catch (\Throwable $e) { - $rr->error((string)$e); - } -} diff --git a/tests/docker-compose.yaml b/tests/docker-compose.yaml deleted file mode 100644 index fa1070e1..00000000 --- a/tests/docker-compose.yaml +++ /dev/null @@ -1,39 +0,0 @@ -version: '3' - -services: - memcached: - image: memcached:latest - ports: - - "0.0.0.0:11211:11211" - cassandra: - image: cassandra:3.11 - ports: - - "9042:9042" - temporal: - image: temporalio/auto-setup:${SERVER_TAG:-1.1.0} - ports: - - "7233:7233" - volumes: - - ${DYNAMIC_CONFIG_DIR:-../config/dynamicconfig}:/etc/temporal/config/dynamicconfig - environment: - - "CASSANDRA_SEEDS=cassandra" - - "DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development.yaml" - depends_on: - - cassandra - temporal-admin-tools: - image: temporalio/admin-tools:${SERVER_TAG:-1.1.0} - stdin_open: true - tty: true - environment: - - "TEMPORAL_CLI_ADDRESS=temporal:7233" - depends_on: - - temporal - temporal-web: - image: temporalio/web:${WEB_TAG:-1.5.0} - environment: - - "TEMPORAL_GRPC_ENDPOINT=temporal:7233" - - "TEMPORAL_PERMIT_WRITE_API=true" - ports: - - "8088:8088" - depends_on: - - temporal
\ No newline at end of file diff --git a/tests/echo.php b/tests/echo.php deleted file mode 100644 index 83eec92e..00000000 --- a/tests/echo.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php -/** - * @var Goridge\RelayInterface $relay - */ - -use Spiral\Goridge; -use Spiral\RoadRunner; - -$rr = new RoadRunner\Worker($relay); - -while ($in = $rr->waitPayload()) { - try { - $rr->send((string)$in->body); - } catch (\Throwable $e) { - $rr->error((string)$e); - } -} diff --git a/tests/error.php b/tests/error.php deleted file mode 100644 index c77e6817..00000000 --- a/tests/error.php +++ /dev/null @@ -1,13 +0,0 @@ -<?php -/** - * @var Goridge\RelayInterface $relay - */ - -use Spiral\Goridge; -use Spiral\RoadRunner; - -$rr = new RoadRunner\Worker($relay); - -while ($in = $rr->waitPayload()) { - $rr->error((string)$in->body); -} diff --git a/tests/failboot.php b/tests/failboot.php deleted file mode 100644 index d59462cd..00000000 --- a/tests/failboot.php +++ /dev/null @@ -1,3 +0,0 @@ -<?php -ini_set('display_errors', 'stderr'); -throw new Error("failboot error"); diff --git a/tests/gzip-large-file.txt b/tests/gzip-large-file.txt deleted file mode 100644 index 4c3eef8f..00000000 --- a/tests/gzip-large-file.txt +++ /dev/null @@ -1,19 +0,0 @@ -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus sodales consectetur fringilla. Phasellus rhoncus lectus nec diam vehicula euismod. Proin eget mollis libero, at ullamcorper ex. Proin sollicitudin, ligula vitae efficitur iaculis, est arcu iaculis ex, in finibus dolor lorem sit amet orci. Pellentesque placerat fermentum est, sed aliquam nulla aliquam non. Donec non tellus vitae elit iaculis ullamcorper. Sed ultricies arcu vel nunc maximus, nec iaculis eros varius. Duis iaculis a mi sed rutrum. Aliquam aliquet ornare arcu id pulvinar. Maecenas luctus orci at efficitur mollis. Aliquam eu placerat magna, id blandit libero. - -Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Pellentesque mattis nibh in sagittis dictum. Ut eu neque mollis, pharetra nunc in, sodales neque. Aenean viverra commodo arcu, sit amet egestas magna interdum non. Sed pulvinar ornare urna quis sagittis. Duis tempor at augue sed laoreet. Curabitur aliquam at arcu a semper. Sed eu sem ante. Praesent pellentesque magna lacus, eget ultricies elit pharetra et. Sed ut nisl ultricies orci ultricies porttitor nec id dui. Etiam eu ipsum sagittis, maximus lectus non, semper justo. Fusce orci tellus, congue et consequat nec, facilisis accumsan enim. Proin facilisis quis libero a viverra. Curabitur neque magna, euismod in lacinia nec, scelerisque nec ex. - -Donec nec ullamcorper turpis. Praesent ultrices nunc non dignissim auctor. Morbi at congue lacus. Ut ut diam nec magna finibus ullamcorper. Donec auctor est ut tempor euismod. Proin tincidunt ipsum ac leo aliquet finibus. Curabitur laoreet turpis id enim rutrum, quis tincidunt sem maximus. Vivamus quis diam at lorem viverra pretium. Quisque eget diam scelerisque, hendrerit purus at, blandit lacus. Sed quis consectetur lectus, vitae molestie neque. Quisque in nisi augue. - -Proin ultricies maximus tellus, sed malesuada metus posuere at. Vestibulum a purus ut libero tincidunt dictum et vel sapien. In auctor a metus in ullamcorper. Nullam pharetra pharetra ipsum, non gravida massa tempus non. Proin et varius nunc. Interdum et malesuada fames ac ante ipsum primis in faucibus. Quisque ullamcorper porttitor gravida. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In hac habitasse platea dictumst. Donec sit amet rhoncus turpis. Cras vel dui non felis consectetur varius vitae id turpis. Sed vitae hendrerit diam. Aenean neque turpis, laoreet eget libero a, pellentesque malesuada ipsum. Praesent bibendum ultrices nisi eu ornare. Nulla dignissim eu ipsum non iaculis. - -Ut eget lacus porta, posuere neque a, feugiat mi. Sed vehicula sed dui non imperdiet. Nulla quis fringilla nunc. Nunc ipsum dui, hendrerit eu diam sit amet, vehicula convallis libero. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Pellentesque vel diam id felis ornare lobortis a in metus. Mauris feugiat tellus sit amet felis pellentesque ornare sit amet ac augue. Phasellus ut porta purus. Morbi vestibulum, nunc sed pellentesque rhoncus, nibh turpis varius justo, vel mollis ipsum risus at libero. Praesent eget convallis arcu. Donec placerat justo et odio dignissim interdum. Vestibulum rhoncus faucibus tempor. Quisque ipsum tortor, ullamcorper ut sagittis quis, elementum eget metus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; - -Suspendisse in mi non eros fermentum euismod a a tortor. Morbi tincidunt sagittis arcu, ut cursus urna luctus vel. Curabitur malesuada, dui at rutrum pretium, turpis sem vulputate urna, eget suscipit odio ipsum vitae enim. Nunc pellentesque nibh sed facilisis aliquam. Sed ac est sit amet dolor aliquam volutpat in ac risus. Nulla facilisi. Etiam gravida tincidunt metus vitae consectetur. Phasellus rutrum metus ut porttitor varius. Nullam sollicitudin orci ac vehicula accumsan. Maecenas vitae malesuada est. Nam viverra ante ante, id mollis justo sagittis sed. Ut facilisis, lectus id dignissim volutpat, nisl elit fringilla elit, vel facilisis neque lectus et neque. Etiam erat justo, interdum quis neque at, mattis tristique lorem. Mauris malesuada nibh ante, eget eleifend sapien condimentum efficitur. Suspendisse metus turpis, viverra vitae tortor eget, porttitor scelerisque leo. Aenean ac fermentum tortor, ut dapibus mi. - -Fusce semper velit ac tempor lobortis. Suspendisse venenatis eros est, quis sollicitudin dolor consequat vitae. Vivamus tristique erat quis eros laoreet, et dictum nibh sagittis. Integer iaculis pretium orci non venenatis. Aenean ultricies purus ac dui tempor, ut fringilla turpis feugiat. Suspendisse sodales vestibulum aliquet. Donec elit lectus, porta sit amet mollis eu, porttitor eu sem. Vivamus sit amet nisi vel leo commodo suscipit faucibus porttitor orci. Quisque ut sollicitudin quam, luctus pharetra risus. Ut tempus diam odio, elementum porttitor lacus sollicitudin non. Curabitur eget eleifend nisi. Nullam porttitor sagittis gravida. Donec sodales a lorem vel fermentum. Aliquam ac rhoncus quam. - -Fusce pulvinar imperdiet diam et sollicitudin. Quisque a urna id ex porttitor venenatis sit amet id ligula. In fringilla euismod orci vitae pulvinar. In facilisis non nibh ac rutrum. Quisque aliquam sem sit amet pharetra pellentesque. Donec imperdiet libero sed sapien consequat vehicula. Integer vitae ornare sapien. Vivamus pretium felis at urna gravida, quis luctus sem feugiat. Fusce nisl metus, dictum vitae maximus in, interdum id nunc. Phasellus sodales nibh at mi porta egestas quis nec nisl. - -Fusce vel suscipit erat, quis vulputate quam. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam a odio nulla. Suspendisse ac tincidunt erat. Duis tortor sem, blandit sed arcu vehicula, faucibus vehicula eros. Sed gravida faucibus quam sed condimentum. Donec sed imperdiet sem. Quisque purus mauris, ultrices sit amet nisl ut, luctus convallis enim. Quisque sit amet nisi ut lacus ullamcorper placerat et id dolor. Mauris tincidunt quam id posuere sodales. Integer tristique efficitur varius. Nulla facilisi. Morbi sed odio eget neque ornare congue. Duis dui massa, maximus ac ullamcorper nec, tempus ut magna. Morbi dictum nisl sagittis orci hendrerit, ac consequat odio pulvinar. Ut mollis sodales egestas. - -Maecenas ante libero, aliquam in enim quis, aliquet malesuada mauris. Quisque porta massa id sem lacinia, id blandit diam mollis. Donec rhoncus vestibulum ante. Aliquam id ligula a erat bibendum condimentum congue non eros. Praesent ac lectus convallis, laoreet quam vitae, viverra arcu. Morbi finibus ligula risus, et ornare turpis iaculis eget. Donec placerat metus vel ex mattis tristique. Donec varius dapibus leo eu pulvinar. Fusce et diam mauris. Duis consequat pharetra urna id malesuada.
\ No newline at end of file diff --git a/tests/head.php b/tests/head.php deleted file mode 100644 index 3c57258f..00000000 --- a/tests/head.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php -/** - * @var Goridge\RelayInterface $relay - */ - -use Spiral\Goridge; -use Spiral\RoadRunner; - -$rr = new RoadRunner\Worker($relay); - -while ($in = $rr->waitPayload()) { - try { - $rr->send("", (string)$in->header); - } catch (\Throwable $e) { - $rr->error((string)$e); - } -} diff --git a/tests/http/client.php b/tests/http/client.php deleted file mode 100644 index ad5cce24..00000000 --- a/tests/http/client.php +++ /dev/null @@ -1,51 +0,0 @@ -<?php - -use Spiral\Goridge; -use Spiral\RoadRunner; - -ini_set('display_errors', 'stderr'); -require dirname(__DIR__) . "/vendor/autoload.php"; - -if (count($argv) < 3) { - die("need 2 arguments"); -} - -list($test, $goridge) = [$argv[1], $argv[2]]; - -switch ($goridge) { - case "pipes": - $relay = new Goridge\StreamRelay(STDIN, STDOUT); - break; - - case "tcp": - $relay = new Goridge\SocketRelay("localhost", 9007); - break; - - case "unix": - $relay = new Goridge\SocketRelay( - "sock.unix", - null, - Goridge\SocketRelay::SOCK_UNIX - ); - break; - - default: - die("invalid protocol selection"); -} - -$psr7 = new RoadRunner\Http\PSR7Worker( - new RoadRunner\Worker($relay), - new \Nyholm\Psr7\Factory\Psr17Factory(), - new \Nyholm\Psr7\Factory\Psr17Factory(), - new \Nyholm\Psr7\Factory\Psr17Factory() -); - -require_once sprintf("%s/%s.php", __DIR__, $test); - -while ($req = $psr7->waitRequest()) { - try { - $psr7->respond(handleRequest($req, new \Nyholm\Psr7\Response())); - } catch (\Throwable $e) { - $psr7->getWorker()->error((string)$e); - } -} diff --git a/tests/http/cookie.php b/tests/http/cookie.php deleted file mode 100644 index 97673ef5..00000000 --- a/tests/http/cookie.php +++ /dev/null @@ -1,334 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write(strtoupper($req->getCookieParams()['input'])); - - return $resp->withAddedHeader( - "Set-Cookie", - (new Cookie('output', 'cookie-output'))->createHeader() - ); -} - -final class Cookie -{ - /** - * The name of the cookie. - * - * @var string - */ - private $name = ''; - /** - * The value of the cookie. This value is stored on the clients computer; do not store sensitive - * information. - * - * @var string|null - */ - private $value = null; - /** - * Cookie lifetime. This value specified in seconds and declares period of time in which cookie - * will expire relatively to current time() value. - * - * @var int|null - */ - private $lifetime = null; - /** - * The path on the server in which the cookie will be available on. - * - * If set to '/', the cookie will be available within the entire domain. If set to '/foo/', - * the cookie will only be available within the /foo/ directory and all sub-directories such as - * /foo/bar/ of domain. The default value is the current directory that the cookie is being set - * in. - * - * @var string|null - */ - private $path = null; - /** - * The domain that the cookie is available. To make the cookie available on all subdomains of - * example.com then you'd set it to '.example.com'. The . is not required but makes it - * compatible with more browsers. Setting it to www.example.com will make the cookie only - * available in the www subdomain. Refer to tail matching in the spec for details. - * - * @var string|null - */ - private $domain = null; - /** - * Indicates that the cookie should only be transmitted over a secure HTTPS connection from the - * client. When set to true, the cookie will only be set if a secure connection exists. - * On the server-side, it's on the programmer to send this kind of cookie only on secure - * connection - * (e.g. with respect to $_SERVER["HTTPS"]). - * - * @var bool|null - */ - private $secure = null; - /** - * When true the cookie will be made accessible only through the HTTP protocol. This means that - * the cookie won't be accessible by scripting languages, such as JavaScript. This setting can - * effectively help to reduce identity theft through XSS attacks (although it is not supported - * by all browsers). - * - * @var bool - */ - private $httpOnly = true; - - /** - * New Cookie instance, cookies used to schedule cookie set while dispatching Response. - * - * @link http://php.net/manual/en/function.setcookie.php - * - * @param string $name The name of the cookie. - * @param string $value The value of the cookie. This value is stored on the clients - * computer; do not store sensitive information. - * @param int $lifetime Cookie lifetime. This value specified in seconds and declares period - * of time in which cookie will expire relatively to current time() - * value. - * @param string $path The path on the server in which the cookie will be available on. - * If set to '/', the cookie will be available within the entire - * domain. - * If set to '/foo/', the cookie will only be available within the - * /foo/ - * directory and all sub-directories such as /foo/bar/ of domain. The - * default value is the current directory that the cookie is being set - * in. - * @param string $domain The domain that the cookie is available. To make the cookie - * available - * on all subdomains of example.com then you'd set it to - * '.example.com'. - * The . is not required but makes it compatible with more browsers. - * Setting it to www.example.com will make the cookie only available in - * the www subdomain. Refer to tail matching in the spec for details. - * @param bool $secure Indicates that the cookie should only be transmitted over a secure - * HTTPS connection from the client. When set to true, the cookie will - * only be set if a secure connection exists. On the server-side, it's - * on the programmer to send this kind of cookie only on secure - * connection (e.g. with respect to $_SERVER["HTTPS"]). - * @param bool $httpOnly When true the cookie will be made accessible only through the HTTP - * protocol. This means that the cookie won't be accessible by - * scripting - * languages, such as JavaScript. This setting can effectively help to - * reduce identity theft through XSS attacks (although it is not - * supported by all browsers). - */ - public function __construct( - string $name, - string $value = null, - int $lifetime = null, - string $path = null, - string $domain = null, - bool $secure = false, - bool $httpOnly = true - ) { - $this->name = $name; - $this->value = $value; - $this->lifetime = $lifetime; - $this->path = $path; - $this->domain = $domain; - $this->secure = $secure; - $this->httpOnly = $httpOnly; - } - - /** - * The name of the cookie. - * - * @return string - */ - public function getName(): string - { - return $this->name; - } - - /** - * The value of the cookie. This value is stored on the clients computer; do not store sensitive - * information. - * - * @return string|null - */ - public function getValue() - { - return $this->value; - } - - /** - * The time the cookie expires. This is a Unix timestamp so is in number of seconds since the - * epoch. In other words, you'll most likely set this with the time function plus the number of - * seconds before you want it to expire. Or you might use mktime. - * - * Will return null if lifetime is not specified. - * - * @return int|null - */ - public function getExpires() - { - if ($this->lifetime === null) { - return null; - } - - return time() + $this->lifetime; - } - - /** - * The path on the server in which the cookie will be available on. - * - * If set to '/', the cookie will be available within the entire domain. If set to '/foo/', - * the cookie will only be available within the /foo/ directory and all sub-directories such as - * /foo/bar/ of domain. The default value is the current directory that the cookie is being set - * in. - * - * @return string|null - */ - public function getPath() - { - return $this->path; - } - - /** - * The domain that the cookie is available. To make the cookie available on all subdomains of - * example.com then you'd set it to '.example.com'. The . is not required but makes it - * compatible with more browsers. Setting it to www.example.com will make the cookie only - * available in the www subdomain. Refer to tail matching in the spec for details. - * - * @return string|null - */ - public function getDomain() - { - return $this->domain; - } - - /** - * Indicates that the cookie should only be transmitted over a secure HTTPS connection from the - * client. When set to true, the cookie will only be set if a secure connection exists. - * On the server-side, it's on the programmer to send this kind of cookie only on secure - * connection - * (e.g. with respect to $_SERVER["HTTPS"]). - * - * @return bool - */ - public function isSecure(): bool - { - return $this->secure; - } - - /** - * When true the cookie will be made accessible only through the HTTP protocol. This means that - * the cookie won't be accessible by scripting languages, such as JavaScript. This setting can - * effectively help to reduce identity theft through XSS attacks (although it is not supported - * by all browsers). - * - * @return bool - */ - public function isHttpOnly(): bool - { - return $this->httpOnly; - } - - /** - * Get new cookie with altered value. Original cookie object should not be changed. - * - * @param string $value - * - * @return Cookie - */ - public function withValue(string $value): self - { - $cookie = clone $this; - $cookie->value = $value; - - return $cookie; - } - - /** - * Convert cookie instance to string. - * - * @link http://www.w3.org/Protocols/rfc2109/rfc2109 - * @return string - */ - public function createHeader(): string - { - $header = [ - rawurlencode($this->name) . '=' . rawurlencode($this->value) - ]; - if ($this->lifetime !== null) { - $header[] = 'Expires=' . gmdate(\DateTime::COOKIE, $this->getExpires()); - $header[] = 'Max-Age=' . $this->lifetime; - } - if (!empty($this->path)) { - $header[] = 'Path=' . $this->path; - } - if (!empty($this->domain)) { - $header[] = 'Domain=' . $this->domain; - } - if ($this->secure) { - $header[] = 'Secure'; - } - if ($this->httpOnly) { - $header[] = 'HttpOnly'; - } - - return join('; ', $header); - } - - /** - * New Cookie instance, cookies used to schedule cookie set while dispatching Response. - * Static constructor. - * - * @link http://php.net/manual/en/function.setcookie.php - * - * @param string $name The name of the cookie. - * @param string $value The value of the cookie. This value is stored on the clients - * computer; do not store sensitive information. - * @param int $lifetime Cookie lifetime. This value specified in seconds and declares period - * of time in which cookie will expire relatively to current time() - * value. - * @param string $path The path on the server in which the cookie will be available on. - * If set to '/', the cookie will be available within the entire - * domain. - * If set to '/foo/', the cookie will only be available within the - * /foo/ - * directory and all sub-directories such as /foo/bar/ of domain. The - * default value is the current directory that the cookie is being set - * in. - * @param string $domain The domain that the cookie is available. To make the cookie - * available - * on all subdomains of example.com then you'd set it to - * '.example.com'. - * The . is not required but makes it compatible with more browsers. - * Setting it to www.example.com will make the cookie only available in - * the www subdomain. Refer to tail matching in the spec for details. - * @param bool $secure Indicates that the cookie should only be transmitted over a secure - * HTTPS connection from the client. When set to true, the cookie will - * only be set if a secure connection exists. On the server-side, it's - * on the programmer to send this kind of cookie only on secure - * connection (e.g. with respect to $_SERVER["HTTPS"]). - * @param bool $httpOnly When true the cookie will be made accessible only through the HTTP - * protocol. This means that the cookie won't be accessible by - * scripting - * languages, such as JavaScript. This setting can effectively help to - * reduce identity theft through XSS attacks (although it is not - * supported by all browsers). - * - * @return Cookie - */ - public static function create( - string $name, - string $value = null, - int $lifetime = null, - string $path = null, - string $domain = null, - bool $secure = false, - bool $httpOnly = true - ): self { - return new self($name, $value, $lifetime, $path, $domain, $secure, $httpOnly); - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->createHeader(); - } -} diff --git a/tests/http/data.php b/tests/http/data.php deleted file mode 100644 index 6570936a..00000000 --- a/tests/http/data.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $data = $req->getParsedBody(); - - ksort($data); - ksort($data['arr']); - ksort($data['arr']['x']['y']); - - $resp->getBody()->write(json_encode($data)); - - return $resp; -} diff --git a/tests/http/echo.php b/tests/http/echo.php deleted file mode 100644 index 08e29a26..00000000 --- a/tests/http/echo.php +++ /dev/null @@ -1,10 +0,0 @@ -<?php - -use \Psr\Http\Message\ServerRequestInterface; -use \Psr\Http\Message\ResponseInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write(strtoupper($req->getQueryParams()['hello'])); - return $resp->withStatus(201); -} diff --git a/tests/http/echoDelay.php b/tests/http/echoDelay.php deleted file mode 100644 index 78e85477..00000000 --- a/tests/http/echoDelay.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php - -use \Psr\Http\Message\ServerRequestInterface; -use \Psr\Http\Message\ResponseInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - sleep(1); - $resp->getBody()->write(strtoupper($req->getQueryParams()['hello'])); - return $resp->withStatus(201); -} diff --git a/tests/http/echoerr.php b/tests/http/echoerr.php deleted file mode 100644 index 7e1d05e7..00000000 --- a/tests/http/echoerr.php +++ /dev/null @@ -1,12 +0,0 @@ -<?php - -use \Psr\Http\Message\ServerRequestInterface; -use \Psr\Http\Message\ResponseInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - error_log(strtoupper($req->getQueryParams()['hello'])); - - $resp->getBody()->write(strtoupper($req->getQueryParams()['hello'])); - return $resp->withStatus(201); -} diff --git a/tests/http/env.php b/tests/http/env.php deleted file mode 100644 index 3755bdea..00000000 --- a/tests/http/env.php +++ /dev/null @@ -1,10 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write($_SERVER['ENV_KEY']); - return $resp; -} diff --git a/tests/http/error.php b/tests/http/error.php deleted file mode 100644 index 527e4068..00000000 --- a/tests/http/error.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - throw new Error("error"); -} diff --git a/tests/http/error2.php b/tests/http/error2.php deleted file mode 100644 index 12a672ac..00000000 --- a/tests/http/error2.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - exit(); -} diff --git a/tests/http/header.php b/tests/http/header.php deleted file mode 100644 index 393d9623..00000000 --- a/tests/http/header.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write(strtoupper($req->getHeaderLine('input'))); - - return $resp->withAddedHeader("Header", $req->getQueryParams()['hello']); -} diff --git a/tests/http/headers.php b/tests/http/headers.php deleted file mode 100644 index b6f3967a..00000000 --- a/tests/http/headers.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write(json_encode($req->getHeaders())); - - return $resp; -} diff --git a/tests/http/ip.php b/tests/http/ip.php deleted file mode 100644 index 49eb9285..00000000 --- a/tests/http/ip.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write($req->getServerParams()['REMOTE_ADDR']); - - return $resp; -} diff --git a/tests/http/memleak.php b/tests/http/memleak.php deleted file mode 100644 index 197a7fb1..00000000 --- a/tests/http/memleak.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $mem = ''; - $mem .= str_repeat(" ", 1024*1024); - return $resp; -} diff --git a/tests/http/payload.php b/tests/http/payload.php deleted file mode 100644 index b7a0311f..00000000 --- a/tests/http/payload.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - if ($req->getHeaderLine("Content-Type") != 'application/json') { - $resp->getBody()->write("invalid content-type"); - return $resp; - } - - // we expect json body - $p = json_decode($req->getBody(), true); - $resp->getBody()->write(json_encode(array_flip($p))); - - return $resp; -} diff --git a/tests/http/pid.php b/tests/http/pid.php deleted file mode 100644 index f22d8e23..00000000 --- a/tests/http/pid.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write((string)getmypid()); - - return $resp; -} diff --git a/tests/http/push.php b/tests/http/push.php deleted file mode 100644 index d88fc076..00000000 --- a/tests/http/push.php +++ /dev/null @@ -1,10 +0,0 @@ -<?php - -use \Psr\Http\Message\ServerRequestInterface; -use \Psr\Http\Message\ResponseInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write(strtoupper($req->getQueryParams()['hello'])); - return $resp->withAddedHeader("Http2-Push", __FILE__)->withStatus(201); -} diff --git a/tests/http/request-uri.php b/tests/http/request-uri.php deleted file mode 100644 index d4c87551..00000000 --- a/tests/http/request-uri.php +++ /dev/null @@ -1,10 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write($_SERVER['REQUEST_URI']); - return $resp; -} diff --git a/tests/http/server.php b/tests/http/server.php deleted file mode 100644 index 393d9623..00000000 --- a/tests/http/server.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write(strtoupper($req->getHeaderLine('input'))); - - return $resp->withAddedHeader("Header", $req->getQueryParams()['hello']); -} diff --git a/tests/http/slow-client.php b/tests/http/slow-client.php deleted file mode 100644 index 731232f7..00000000 --- a/tests/http/slow-client.php +++ /dev/null @@ -1,52 +0,0 @@ -<?php - -use Spiral\Goridge; -use Spiral\RoadRunner; - -ini_set('display_errors', 'stderr'); -require dirname(__DIR__) . "/vendor/autoload.php"; - -if (count($argv) < 3) { - die("need 2 arguments"); -} - -[$test, $goridge, $bootDelay] = [$argv[1], $argv[2], $argv[3]]; -usleep($bootDelay * 1000); - -switch ($goridge) { - case "pipes": - $relay = new Goridge\StreamRelay(STDIN, STDOUT); - break; - - case "tcp": - $relay = new Goridge\SocketRelay("localhost", 9007); - break; - - case "unix": - $relay = new Goridge\SocketRelay( - "sock.unix", - null, - Goridge\SocketRelay::SOCK_UNIX - ); - break; - - default: - die("invalid protocol selection"); -} - -$psr7 = new RoadRunner\Http\PSR7Worker( - new RoadRunner\Worker($relay), - new \Nyholm\Psr7\Factory\Psr17Factory(), - new \Nyholm\Psr7\Factory\Psr17Factory(), - new \Nyholm\Psr7\Factory\Psr17Factory() -); - -require_once sprintf("%s/%s.php", __DIR__, $test); - -while ($req = $psr7->waitRequest()) { - try { - $psr7->respond(handleRequest($req, new \Nyholm\Psr7\Response())); - } catch (\Throwable $e) { - $psr7->getWorker()->error((string) $e); - } -} diff --git a/tests/http/stuck.php b/tests/http/stuck.php deleted file mode 100644 index 2dea0572..00000000 --- a/tests/http/stuck.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php - -use \Psr\Http\Message\ServerRequestInterface; -use \Psr\Http\Message\ResponseInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - sleep(10); - $resp->getBody()->write(strtoupper($req->getQueryParams()['hello'])); - return $resp->withStatus(201); -} diff --git a/tests/http/upload.php b/tests/http/upload.php deleted file mode 100644 index 57526246..00000000 --- a/tests/http/upload.php +++ /dev/null @@ -1,35 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $files = $req->getUploadedFiles(); - array_walk_recursive($files, function (&$v) { - /** - * @var \Psr\Http\Message\UploadedFileInterface $v - */ - - if ($v->getError()) { - $v = [ - 'name' => $v->getClientFilename(), - 'size' => $v->getSize(), - 'mime' => $v->getClientMediaType(), - 'error' => $v->getError(), - ]; - } else { - $v = [ - 'name' => $v->getClientFilename(), - 'size' => $v->getSize(), - 'mime' => $v->getClientMediaType(), - 'error' => $v->getError(), - 'sha512' => hash('sha512', $v->getStream()->__toString()), - ]; - } - }); - - $resp->getBody()->write(json_encode($files, JSON_UNESCAPED_SLASHES)); - - return $resp; -} diff --git a/tests/http/user-agent.php b/tests/http/user-agent.php deleted file mode 100644 index 03d7a2c8..00000000 --- a/tests/http/user-agent.php +++ /dev/null @@ -1,10 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write($_SERVER['HTTP_USER_AGENT']); - return $resp; -} diff --git a/tests/memleak.php b/tests/memleak.php deleted file mode 100644 index 169fe4f5..00000000 --- a/tests/memleak.php +++ /dev/null @@ -1,15 +0,0 @@ -<?php - -declare(strict_types=1); - -use Spiral\Goridge\StreamRelay; -use Spiral\RoadRunner\Worker as RoadRunner; - -require __DIR__ . "/vendor/autoload.php"; - -$rr = new RoadRunner(new StreamRelay(\STDIN, \STDOUT)); -$mem = ''; -while($rr->waitPayload()){ - $mem .= str_repeat("a", 1024*1024); - $rr->send(""); -} diff --git a/tests/mocks/mock_log.go b/tests/mocks/mock_log.go deleted file mode 100644 index 66c70b91..00000000 --- a/tests/mocks/mock_log.go +++ /dev/null @@ -1,160 +0,0 @@ -package mocks - -import ( - "reflect" - "sync" - - "github.com/golang/mock/gomock" - "github.com/spiral/roadrunner/v2/plugins/logger" -) - -// MockLogger is a mock of Logger interface. -type MockLogger struct { - sync.Mutex - ctrl *gomock.Controller - recorder *MockLoggerMockRecorder -} - -// MockLoggerMockRecorder is the mock recorder for MockLogger. -type MockLoggerMockRecorder struct { - mock *MockLogger -} - -// NewMockLogger creates a new mock instance. -func NewMockLogger(ctrl *gomock.Controller) *MockLogger { - mock := &MockLogger{ctrl: ctrl} - mock.recorder = &MockLoggerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockLogger) EXPECT() *MockLoggerMockRecorder { - return m.recorder -} - -func (m *MockLogger) Init() error { - mock := &MockLogger{ctrl: m.ctrl} - mock.recorder = &MockLoggerMockRecorder{mock} - return nil -} - -// Debug mocks base method. -func (m *MockLogger) Debug(msg string, keyvals ...interface{}) { - m.Lock() - defer m.Unlock() - m.ctrl.T.Helper() - varargs := []interface{}{msg} - for _, a := range keyvals { - varargs = append(varargs, a) - } - m.ctrl.Call(m, "Debug", varargs...) -} - -// Warn mocks base method. -func (m *MockLogger) Warn(msg string, keyvals ...interface{}) { - m.Lock() - defer m.Unlock() - m.ctrl.T.Helper() - varargs := []interface{}{msg} - for _, a := range keyvals { - varargs = append(varargs, a) - } - m.ctrl.Call(m, "Warn", varargs...) -} - -// Info mocks base method. -func (m *MockLogger) Info(msg string, keyvals ...interface{}) { - m.Lock() - defer m.Unlock() - m.ctrl.T.Helper() - varargs := []interface{}{msg} - for _, a := range keyvals { - varargs = append(varargs, a) - } - m.ctrl.Call(m, "Info", varargs...) -} - -// Error mocks base method. -func (m *MockLogger) Error(msg string, keyvals ...interface{}) { - m.Lock() - defer m.Unlock() - m.ctrl.T.Helper() - varargs := []interface{}{msg} - for _, a := range keyvals { - varargs = append(varargs, a) - } - m.ctrl.Call(m, "Error", varargs...) -} - -// Warn indicates an expected call of Warn. -func (mr *MockLoggerMockRecorder) Warn(msg interface{}, keyvals ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{msg}, keyvals...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockLogger)(nil).Warn), varargs...) -} - -// Debug indicates an expected call of Debug. -func (mr *MockLoggerMockRecorder) Debug(msg interface{}, keyvals ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{msg}, keyvals...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debug", reflect.TypeOf((*MockLogger)(nil).Debug), varargs...) -} - -// Error indicates an expected call of Error. -func (mr *MockLoggerMockRecorder) Error(msg interface{}, keyvals ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{msg}, keyvals...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), varargs...) -} - -func (mr *MockLoggerMockRecorder) Init() error { - return nil -} - -// Info indicates an expected call of Info. -func (mr *MockLoggerMockRecorder) Info(msg interface{}, keyvals ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{msg}, keyvals...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLogger)(nil).Info), varargs...) -} - -// MockWithLogger is a mock of WithLogger interface. -type MockWithLogger struct { - ctrl *gomock.Controller - recorder *MockWithLoggerMockRecorder -} - -// MockWithLoggerMockRecorder is the mock recorder for MockWithLogger. -type MockWithLoggerMockRecorder struct { - mock *MockWithLogger -} - -// NewMockWithLogger creates a new mock instance. -func NewMockWithLogger(ctrl *gomock.Controller) *MockWithLogger { - mock := &MockWithLogger{ctrl: ctrl} - mock.recorder = &MockWithLoggerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockWithLogger) EXPECT() *MockWithLoggerMockRecorder { - return m.recorder -} - -// With mocks base method. -func (m *MockWithLogger) With(keyvals ...interface{}) logger.Logger { - m.ctrl.T.Helper() - varargs := []interface{}{} - for _, a := range keyvals { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "With", varargs...) - ret0, _ := ret[0].(logger.Logger) - return ret0 -} - -// With indicates an expected call of With. -func (mr *MockWithLoggerMockRecorder) With(keyvals ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "With", reflect.TypeOf((*MockWithLogger)(nil).With), keyvals...) -} diff --git a/tests/pid.php b/tests/pid.php deleted file mode 100644 index f8b2515d..00000000 --- a/tests/pid.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php - /** - * @var Goridge\RelayInterface $relay - */ - - use Spiral\Goridge; - use Spiral\RoadRunner; - - $rr = new RoadRunner\Worker($relay); - - while ($in = $rr->waitPayload()) { - try { - $rr->send((string)getmypid()); - } catch (\Throwable $e) { - $rr->error((string)$e); - } - } diff --git a/tests/plugins/checker/configs/.rr-checker-init.yaml b/tests/plugins/checker/configs/.rr-checker-init.yaml deleted file mode 100755 index 11804a21..00000000 --- a/tests/plugins/checker/configs/.rr-checker-init.yaml +++ /dev/null @@ -1,30 +0,0 @@ -rpc: - listen: tcp://127.0.0.1:6005 - -server: - command: "php ../../http/client.php echo pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -status: - address: "127.0.0.1:34333" -logs: - mode: development - level: error -http: - debug: true - address: 127.0.0.1:11933 - max_request_size: 1024 - middleware: [ "" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - pool: - num_workers: 2 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s
\ No newline at end of file diff --git a/tests/plugins/checker/plugin_test.go b/tests/plugins/checker/plugin_test.go deleted file mode 100644 index 5e391158..00000000 --- a/tests/plugins/checker/plugin_test.go +++ /dev/null @@ -1,190 +0,0 @@ -package checker - -import ( - "io/ioutil" - "net" - "net/http" - "net/rpc" - "os" - "os/signal" - "sync" - "syscall" - "testing" - "time" - - endure "github.com/spiral/endure/pkg/container" - goridgeRpc "github.com/spiral/goridge/v3/pkg/rpc" - "github.com/spiral/roadrunner/v2/plugins/checker" - "github.com/spiral/roadrunner/v2/plugins/config" - httpPlugin "github.com/spiral/roadrunner/v2/plugins/http" - "github.com/spiral/roadrunner/v2/plugins/logger" - rpcPlugin "github.com/spiral/roadrunner/v2/plugins/rpc" - "github.com/spiral/roadrunner/v2/plugins/server" - "github.com/stretchr/testify/assert" -) - -func TestStatusHttp(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-checker-init.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &checker.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second) - t.Run("CheckerGetStatus", checkHTTPStatus) - - stopCh <- struct{}{} - wg.Wait() -} - -const resp = `Service: http: Status: 200 -Service: rpc not found` - -func checkHTTPStatus(t *testing.T) { - req, err := http.NewRequest("GET", "http://127.0.0.1:34333/v1/health?plugin=http&plugin=rpc", nil) - assert.NoError(t, err) - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - assert.Equal(t, resp, string(b)) - - err = r.Body.Close() - assert.NoError(t, err) -} - -func TestStatusRPC(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-checker-init.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &rpcPlugin.Plugin{}, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &checker.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second) - t.Run("CheckerGetStatusRpc", checkRPCStatus) - stopCh <- struct{}{} - wg.Wait() -} - -func checkRPCStatus(t *testing.T) { - conn, err := net.Dial("tcp", "127.0.0.1:6005") - assert.NoError(t, err) - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - - st := &checker.Status{} - - err = client.Call("status.Status", "http", &st) - assert.NoError(t, err) - assert.Equal(t, st.Code, 200) -} diff --git a/tests/plugins/config/.rr.yaml b/tests/plugins/config/.rr.yaml deleted file mode 100755 index a6e80921..00000000 --- a/tests/plugins/config/.rr.yaml +++ /dev/null @@ -1,20 +0,0 @@ -rpc: - listen: tcp://localhost:6060 - -reload: - interval: 1s - patterns: [".php"] - services: - http: - recursive: true - ignore: ["vendor"] - patterns: [".php", ".go",".md",] - dirs: ["."] - jobs: - recursive: false - ignore: ["service/metrics"] - dirs: ["./jobs"] - rpc: - recursive: true - patterns: [".json"] - dirs: [""] diff --git a/tests/plugins/config/config_test.go b/tests/plugins/config/config_test.go deleted file mode 100755 index 364960db..00000000 --- a/tests/plugins/config/config_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package config - -import ( - "os" - "os/signal" - "testing" - "time" - - endure "github.com/spiral/endure/pkg/container" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/stretchr/testify/assert" -) - -func TestViperProvider_Init(t *testing.T) { - container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.ErrorLevel)) - if err != nil { - t.Fatal(err) - } - vp := &config.Viper{} - vp.Path = ".rr.yaml" - vp.Prefix = "rr" - err = container.Register(vp) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&Foo{}) - if err != nil { - t.Fatal(err) - } - - err = container.Init() - if err != nil { - t.Fatal(err) - } - - errCh, err := container.Serve() - if err != nil { - t.Fatal(err) - } - - // stop by CTRL+C - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - - tt := time.NewTicker(time.Second * 2) - defer tt.Stop() - - for { - select { - case e := <-errCh: - assert.NoError(t, e.Error) - assert.NoError(t, container.Stop()) - return - case <-c: - er := container.Stop() - assert.NoError(t, er) - return - case <-tt.C: - assert.NoError(t, container.Stop()) - return - } - } -} diff --git a/tests/plugins/config/plugin1.go b/tests/plugins/config/plugin1.go deleted file mode 100755 index 1de9a02e..00000000 --- a/tests/plugins/config/plugin1.go +++ /dev/null @@ -1,96 +0,0 @@ -package config - -import ( - "time" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/plugins/config" -) - -type AllConfig struct { - RPC struct { - Listen string `mapstructure:"listen"` - } `mapstructure:"rpc"` - Reload struct { - Enabled bool `mapstructure:"enabled"` - Interval string `mapstructure:"interval"` - Patterns []string `mapstructure:"patterns"` - Services struct { - HTTP struct { - Recursive bool `mapstructure:"recursive"` - Ignore []string `mapstructure:"ignore"` - Patterns []string `mapstructure:"patterns"` - Dirs []string `mapstructure:"dirs"` - } `mapstructure:"http"` - Jobs struct { - Recursive bool `mapstructure:"recursive"` - Ignore []string `mapstructure:"ignore"` - Dirs []string `mapstructure:"dirs"` - } `mapstructure:"jobs"` - RPC struct { - Recursive bool `mapstructure:"recursive"` - Patterns []string `mapstructure:"patterns"` - Dirs []string `mapstructure:"dirs"` - } `mapstructure:"rpc"` - } `mapstructure:"services"` - } `mapstructure:"reload"` -} - -// ReloadConfig is a Reload configuration point. -type ReloadConfig struct { - Interval time.Duration - Patterns []string - Services map[string]ServiceConfig -} - -type ServiceConfig struct { - Enabled bool - Recursive bool - Patterns []string - Dirs []string - Ignore []string -} - -type Foo struct { - configProvider config.Configurer -} - -// Depends on S2 and DB (S3 in the current case) -func (f *Foo) Init(p config.Configurer) error { - f.configProvider = p - return nil -} - -func (f *Foo) Serve() chan error { - const op = errors.Op("foo_plugin_serve") - errCh := make(chan error, 1) - - r := &ReloadConfig{} - err := f.configProvider.UnmarshalKey("reload", r) - if err != nil { - errCh <- err - } - - if len(r.Patterns) == 0 { - errCh <- errors.E(op, errors.Str("should be at least one pattern, but got 0")) - return errCh - } - - var allCfg AllConfig - err = f.configProvider.Unmarshal(&allCfg) - if err != nil { - errCh <- errors.E(op, errors.Str("should be at least one pattern, but got 0")) - return errCh - } - - if allCfg.RPC.Listen != "tcp://localhost:6060" { - errCh <- errors.E(op, errors.Str("RPC.Listen should be parsed")) - return errCh - } - - return errCh -} - -func (f *Foo) Stop() error { - return nil -} diff --git a/tests/plugins/gzip/configs/.rr-http-middlewareNotExist.yaml b/tests/plugins/gzip/configs/.rr-http-middlewareNotExist.yaml deleted file mode 100644 index b900ff30..00000000 --- a/tests/plugins/gzip/configs/.rr-http-middlewareNotExist.yaml +++ /dev/null @@ -1,25 +0,0 @@ -server: - command: "php ../../psr-worker.php" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -http: - debug: true - address: 127.0.0.1:18103 - max_request_size: 1024 - middleware: [ "gzip", "foo" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - pool: - num_workers: 2 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/gzip/configs/.rr-http-withGzip.yaml b/tests/plugins/gzip/configs/.rr-http-withGzip.yaml deleted file mode 100644 index 3ab918fb..00000000 --- a/tests/plugins/gzip/configs/.rr-http-withGzip.yaml +++ /dev/null @@ -1,25 +0,0 @@ -server: - command: "php ../../psr-worker.php" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -http: - debug: true - address: 127.0.0.1:18953 - max_request_size: 1024 - middleware: [ "gzip" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - pool: - num_workers: 2 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/gzip/plugin_test.go b/tests/plugins/gzip/plugin_test.go deleted file mode 100644 index 3e3db0f8..00000000 --- a/tests/plugins/gzip/plugin_test.go +++ /dev/null @@ -1,179 +0,0 @@ -package gzip - -import ( - "net/http" - "os" - "os/signal" - "sync" - "syscall" - "testing" - "time" - - "github.com/golang/mock/gomock" - endure "github.com/spiral/endure/pkg/container" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/gzip" - httpPlugin "github.com/spiral/roadrunner/v2/plugins/http" - "github.com/spiral/roadrunner/v2/plugins/logger" - "github.com/spiral/roadrunner/v2/plugins/server" - "github.com/spiral/roadrunner/v2/tests/mocks" - "github.com/stretchr/testify/assert" -) - -func TestGzipPlugin(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-http-withGzip.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &gzip.Gzip{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - t.Run("GzipCheckHeader", headerCheck) - - stopCh <- struct{}{} - wg.Wait() -} - -func headerCheck(t *testing.T) { - req, err := http.NewRequest("GET", "http://localhost:18953", nil) - assert.NoError(t, err) - client := &http.Client{ - Transport: &http.Transport{ - DisableCompression: false, - }, - } - - r, err := client.Do(req) - assert.NoError(t, err) - assert.True(t, r.Uncompressed) - - err = r.Body.Close() - assert.NoError(t, err) -} - -func TestMiddlewareNotExist(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-http-middlewareNotExist.yaml", - Prefix: "rr", - } - - controller := gomock.NewController(t) - mockLogger := mocks.NewMockLogger(controller) - - mockLogger.EXPECT().Warn("requested middleware does not exist", "requested", "foo").AnyTimes() - mockLogger.EXPECT().Debug("worker constructed", "pid", gomock.Any()).AnyTimes() - mockLogger.EXPECT().Debug("worker destructed", "pid", gomock.Any()).AnyTimes() - mockLogger.EXPECT().Error(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() // placeholder for the workerlogerror - - err = cont.RegisterAll( - cfg, - mockLogger, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &gzip.Gzip{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second) - stopCh <- struct{}{} - wg.Wait() -} diff --git a/tests/plugins/headers/configs/.rr-cors-headers.yaml b/tests/plugins/headers/configs/.rr-cors-headers.yaml deleted file mode 100644 index 9d2ef7e5..00000000 --- a/tests/plugins/headers/configs/.rr-cors-headers.yaml +++ /dev/null @@ -1,38 +0,0 @@ -server: - command: "php ../../http/client.php headers pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -http: - address: 127.0.0.1:22855 - max_request_size: 1024 - middleware: [ "headers" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - # Additional HTTP headers and CORS control. - headers: - cors: - allowed_origin: "*" - allowed_headers: "*" - allowed_methods: "GET,POST,PUT,DELETE" - allow_credentials: true - exposed_headers: "Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma" - max_age: 600 - request: - input: "custom-header" - response: - output: "output-header" - pool: - num_workers: 2 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s -logs: - mode: development - level: error - diff --git a/tests/plugins/headers/configs/.rr-headers-init.yaml b/tests/plugins/headers/configs/.rr-headers-init.yaml deleted file mode 100644 index b2781f2b..00000000 --- a/tests/plugins/headers/configs/.rr-headers-init.yaml +++ /dev/null @@ -1,38 +0,0 @@ -server: - command: "php ../../http/client.php echo pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -http: - address: 127.0.0.1:33453 - max_request_size: 1024 - middleware: [ "headers" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - # Additional HTTP headers and CORS control. - headers: - cors: - allowed_origin: "*" - allowed_headers: "*" - allowed_methods: "GET,POST,PUT,DELETE" - allow_credentials: true - exposed_headers: "Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma" - max_age: 600 - request: - Example-Request-Header: "Value" - response: - X-Powered-By: "RoadRunner" - pool: - num_workers: 2 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s -logs: - mode: development - level: error - diff --git a/tests/plugins/headers/configs/.rr-req-headers.yaml b/tests/plugins/headers/configs/.rr-req-headers.yaml deleted file mode 100644 index a2b97171..00000000 --- a/tests/plugins/headers/configs/.rr-req-headers.yaml +++ /dev/null @@ -1,31 +0,0 @@ -server: - command: "php ../../http/client.php header pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -http: - address: 127.0.0.1:22655 - max_request_size: 1024 - middleware: [ "headers" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - # Additional HTTP headers and CORS control. - headers: - request: - input: "custom-header" - response: - output: "output-header" - pool: - num_workers: 2 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s -logs: - mode: development - level: error - diff --git a/tests/plugins/headers/configs/.rr-res-headers.yaml b/tests/plugins/headers/configs/.rr-res-headers.yaml deleted file mode 100644 index 4448343c..00000000 --- a/tests/plugins/headers/configs/.rr-res-headers.yaml +++ /dev/null @@ -1,31 +0,0 @@ -server: - command: "php ../../http/client.php header pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -http: - address: 127.0.0.1:22455 - max_request_size: 1024 - middleware: [ "headers" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - # Additional HTTP headers and CORS control. - headers: - request: - input: "custom-header" - response: - output: "output-header" - pool: - num_workers: 2 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s -logs: - mode: development - level: error - diff --git a/tests/plugins/headers/headers_plugin_test.go b/tests/plugins/headers/headers_plugin_test.go deleted file mode 100644 index 49d86b00..00000000 --- a/tests/plugins/headers/headers_plugin_test.go +++ /dev/null @@ -1,368 +0,0 @@ -package headers - -import ( - "io/ioutil" - "net/http" - "os" - "os/signal" - "sync" - "syscall" - "testing" - "time" - - endure "github.com/spiral/endure/pkg/container" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/headers" - httpPlugin "github.com/spiral/roadrunner/v2/plugins/http" - "github.com/spiral/roadrunner/v2/plugins/logger" - "github.com/spiral/roadrunner/v2/plugins/server" - "github.com/stretchr/testify/assert" -) - -func TestHeadersInit(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-headers-init.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &headers.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second) - stopCh <- struct{}{} - wg.Wait() -} - -func TestRequestHeaders(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-req-headers.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &headers.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second) - t.Run("RequestHeaders", reqHeaders) - - stopCh <- struct{}{} - wg.Wait() -} - -func reqHeaders(t *testing.T) { - req, err := http.NewRequest("GET", "http://localhost:22655?hello=value", nil) - assert.NoError(t, err) - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.Equal(t, 200, r.StatusCode) - assert.Equal(t, "CUSTOM-HEADER", string(b)) - - err = r.Body.Close() - assert.NoError(t, err) -} - -func TestResponseHeaders(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-res-headers.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &headers.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second) - t.Run("ResponseHeaders", resHeaders) - - stopCh <- struct{}{} - wg.Wait() -} - -func resHeaders(t *testing.T) { - req, err := http.NewRequest("GET", "http://localhost:22455?hello=value", nil) - assert.NoError(t, err) - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - - assert.Equal(t, "output-header", r.Header.Get("output")) - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - assert.Equal(t, "CUSTOM-HEADER", string(b)) - - err = r.Body.Close() - assert.NoError(t, err) -} - -func TestCORSHeaders(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-cors-headers.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &headers.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second) - t.Run("CORSHeaders", corsHeaders) - t.Run("CORSHeadersPass", corsHeadersPass) - - stopCh <- struct{}{} - wg.Wait() -} - -func corsHeadersPass(t *testing.T) { - req, err := http.NewRequest("GET", "http://localhost:22855", nil) - assert.NoError(t, err) - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - - assert.Equal(t, "true", r.Header.Get("Access-Control-Allow-Credentials")) - assert.Equal(t, "*", r.Header.Get("Access-Control-Allow-Headers")) - assert.Equal(t, "*", r.Header.Get("Access-Control-Allow-Origin")) - assert.Equal(t, "true", r.Header.Get("Access-Control-Allow-Credentials")) - - _, err = ioutil.ReadAll(r.Body) - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - - err = r.Body.Close() - assert.NoError(t, err) -} - -func corsHeaders(t *testing.T) { - req, err := http.NewRequest("OPTIONS", "http://localhost:22855", nil) - assert.NoError(t, err) - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - - assert.Equal(t, "true", r.Header.Get("Access-Control-Allow-Credentials")) - assert.Equal(t, "*", r.Header.Get("Access-Control-Allow-Headers")) - assert.Equal(t, "GET,POST,PUT,DELETE", r.Header.Get("Access-Control-Allow-Methods")) - assert.Equal(t, "*", r.Header.Get("Access-Control-Allow-Origin")) - assert.Equal(t, "600", r.Header.Get("Access-Control-Max-Age")) - assert.Equal(t, "true", r.Header.Get("Access-Control-Allow-Credentials")) - - _, err = ioutil.ReadAll(r.Body) - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - - err = r.Body.Close() - assert.NoError(t, err) -} diff --git a/tests/plugins/http/attributes_test.go b/tests/plugins/http/attributes_test.go deleted file mode 100644 index 69200a30..00000000 --- a/tests/plugins/http/attributes_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package http - -import ( - "net/http" - "testing" - - "github.com/spiral/roadrunner/v2/plugins/http/attributes" - "github.com/stretchr/testify/assert" -) - -func TestAllAttributes(t *testing.T) { - r := &http.Request{} - r = attributes.Init(r) - - err := attributes.Set(r, "key", "value") - if err != nil { - t.Errorf("error during the Set: error %v", err) - } - - assert.Equal(t, attributes.All(r), map[string]interface{}{ - "key": "value", - }) -} - -func TestAllAttributesNone(t *testing.T) { - r := &http.Request{} - r = attributes.Init(r) - - assert.Equal(t, attributes.All(r), map[string]interface{}{}) -} - -func TestAllAttributesNone2(t *testing.T) { - r := &http.Request{} - - assert.Equal(t, attributes.All(r), map[string]interface{}{}) -} - -func TestGetAttribute(t *testing.T) { - r := &http.Request{} - r = attributes.Init(r) - - err := attributes.Set(r, "key", "value") - if err != nil { - t.Errorf("error during the Set: error %v", err) - } - assert.Equal(t, attributes.Get(r, "key"), "value") -} - -func TestGetAttributeNone(t *testing.T) { - r := &http.Request{} - r = attributes.Init(r) - - assert.Equal(t, attributes.Get(r, "key"), nil) -} - -func TestGetAttributeNone2(t *testing.T) { - r := &http.Request{} - - assert.Equal(t, attributes.Get(r, "key"), nil) -} - -func TestSetAttribute(t *testing.T) { - r := &http.Request{} - r = attributes.Init(r) - - err := attributes.Set(r, "key", "value") - if err != nil { - t.Errorf("error during the Set: error %v", err) - } - assert.Equal(t, attributes.Get(r, "key"), "value") -} - -func TestSetAttributeNone(t *testing.T) { - r := &http.Request{} - err := attributes.Set(r, "key", "value") - assert.Error(t, err) - assert.Equal(t, attributes.Get(r, "key"), nil) -} diff --git a/tests/plugins/http/configs/.rr-broken-pipes.yaml b/tests/plugins/http/configs/.rr-broken-pipes.yaml deleted file mode 100644 index 9b7d2d0b..00000000 --- a/tests/plugins/http/configs/.rr-broken-pipes.yaml +++ /dev/null @@ -1,30 +0,0 @@ -rpc: - listen: tcp://127.0.0.1:6001 - -server: - command: "php ../../http/client.php broken pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -http: - debug: true - address: 127.0.0.1:12384 - max_request_size: 1024 - middleware: [ "" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - pool: - num_workers: 2 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s -logs: - mode: development - level: error - - diff --git a/tests/plugins/http/configs/.rr-env.yaml b/tests/plugins/http/configs/.rr-env.yaml deleted file mode 100644 index e6b00b69..00000000 --- a/tests/plugins/http/configs/.rr-env.yaml +++ /dev/null @@ -1,32 +0,0 @@ -rpc: - listen: tcp://127.0.0.1:6001 - -server: - command: "php ../../http/client.php env pipes" - user: "" - group: "" - env: - "env_key": "ENV_VALUE" - relay: "pipes" - relay_timeout: "20s" - -http: - debug: true - address: 127.0.0.1:12084 - max_request_size: 1024 - middleware: [ "" ] - env: - "RR_HTTP": "true" - "env_key": "ENV_VALUE" - uploads: - forbid: [ ".php", ".exe", ".bat" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - pool: - num_workers: 2 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s -logs: - mode: development - level: error - diff --git a/tests/plugins/http/configs/.rr-fcgi-reqUri.yaml b/tests/plugins/http/configs/.rr-fcgi-reqUri.yaml deleted file mode 100644 index ab42f4fc..00000000 --- a/tests/plugins/http/configs/.rr-fcgi-reqUri.yaml +++ /dev/null @@ -1,37 +0,0 @@ -server: - command: "php ../../http/client.php request-uri pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -http: - debug: true - address: :8082 - max_request_size: 1024 - middleware: [ "" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - pool: - num_workers: 1 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s - - ssl: - address: :8890 - redirect: false - cert: fixtures/server.crt - key: fixtures/server.key - # root_ca: root.crt - fcgi: - address: tcp://127.0.0.1:6921 - http2: - h2c: false - maxConcurrentStreams: 128 -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/http/configs/.rr-fcgi.yaml b/tests/plugins/http/configs/.rr-fcgi.yaml deleted file mode 100644 index bd5d01bd..00000000 --- a/tests/plugins/http/configs/.rr-fcgi.yaml +++ /dev/null @@ -1,37 +0,0 @@ -server: - command: "php ../../http/client.php echo pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -http: - debug: true - address: :8081 - max_request_size: 1024 - middleware: [ "" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - pool: - num_workers: 1 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s - - ssl: - address: :8889 - redirect: false - cert: fixtures/server.crt - key: fixtures/server.key - # root_ca: root.crt - fcgi: - address: tcp://0.0.0.0:6920 - http2: - h2c: false - maxConcurrentStreams: 128 -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/http/configs/.rr-h2c.yaml b/tests/plugins/http/configs/.rr-h2c.yaml deleted file mode 100644 index 2061a76b..00000000 --- a/tests/plugins/http/configs/.rr-h2c.yaml +++ /dev/null @@ -1,28 +0,0 @@ -server: - command: "php ../../http/client.php echo pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -http: - debug: true - address: :8083 - max_request_size: 1024 - middleware: [ "" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - pool: - num_workers: 1 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s - http2: - h2c: true - maxConcurrentStreams: 128 -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/http/configs/.rr-http-supervised-pool.yaml b/tests/plugins/http/configs/.rr-http-supervised-pool.yaml deleted file mode 100644 index 3e392577..00000000 --- a/tests/plugins/http/configs/.rr-http-supervised-pool.yaml +++ /dev/null @@ -1,33 +0,0 @@ -rpc: - listen: tcp://127.0.0.1:15432 -server: - command: "php ../../http/client.php echo pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -http: - debug: true - address: 127.0.0.1:18888 - max_request_size: 1024 - middleware: [ "" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - pool: - num_workers: 1 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s - supervisor: - watch_tick: 1s - ttl: 0 - idle_ttl: 5s - exec_ttl: 10s - max_worker_memory: 100 -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/http/configs/.rr-http.yaml b/tests/plugins/http/configs/.rr-http.yaml deleted file mode 100644 index 184a353c..00000000 --- a/tests/plugins/http/configs/.rr-http.yaml +++ /dev/null @@ -1,30 +0,0 @@ -rpc: - listen: tcp://127.0.0.1:6001 - -server: - command: "php ../../http/client.php echo pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -http: - debug: true - address: 127.0.0.1:18903 - max_request_size: 1024 - middleware: [ "pluginMiddleware", "pluginMiddleware2" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - pool: - num_workers: 2 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s -logs: - mode: development - level: error - - diff --git a/tests/plugins/http/configs/.rr-init.yaml b/tests/plugins/http/configs/.rr-init.yaml deleted file mode 100644 index 77132b43..00000000 --- a/tests/plugins/http/configs/.rr-init.yaml +++ /dev/null @@ -1,41 +0,0 @@ -rpc: - listen: tcp://127.0.0.1:6001 - -server: - command: "php ../../http/client.php echo pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -http: - debug: true - address: 127.0.0.1:15395 - max_request_size: 1024 - middleware: [ "" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - pool: - num_workers: 2 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s - - ssl: - address: :8892 - redirect: false - cert: fixtures/server.crt - key: fixtures/server.key - # root_ca: root.crt - fcgi: - address: tcp://0.0.0.0:7921 - http2: - h2c: false - maxConcurrentStreams: 128 -logs: - mode: development - level: error - diff --git a/tests/plugins/http/configs/.rr-no-http.yaml b/tests/plugins/http/configs/.rr-no-http.yaml deleted file mode 100644 index a6747b5d..00000000 --- a/tests/plugins/http/configs/.rr-no-http.yaml +++ /dev/null @@ -1,16 +0,0 @@ -rpc: - listen: tcp://127.0.0.1:6001 - -server: - command: "php ../../http/client.php echo pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -logs: - mode: development - level: error - diff --git a/tests/plugins/http/configs/.rr-resetter.yaml b/tests/plugins/http/configs/.rr-resetter.yaml deleted file mode 100644 index a1ef27d1..00000000 --- a/tests/plugins/http/configs/.rr-resetter.yaml +++ /dev/null @@ -1,29 +0,0 @@ -rpc: - listen: tcp://127.0.0.1:6001 - -server: - command: "php ../../http/client.php echo pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -http: - debug: true - address: 127.0.0.1:10084 - max_request_size: 1024 - middleware: [ "" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - pool: - num_workers: 2 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s -logs: - mode: development - level: error - diff --git a/tests/plugins/http/configs/.rr-ssl-push.yaml b/tests/plugins/http/configs/.rr-ssl-push.yaml deleted file mode 100644 index 11a8ddd3..00000000 --- a/tests/plugins/http/configs/.rr-ssl-push.yaml +++ /dev/null @@ -1,31 +0,0 @@ -server: - command: "php ../../http/client.php push pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -http: - debug: true - address: :8086 - max_request_size: 1024 - middleware: [ "" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - pool: - num_workers: 1 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s - - ssl: - address: :8894 - redirect: true - cert: fixtures/server.crt - key: fixtures/server.key -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/http/configs/.rr-ssl-redirect.yaml b/tests/plugins/http/configs/.rr-ssl-redirect.yaml deleted file mode 100644 index e49a73ed..00000000 --- a/tests/plugins/http/configs/.rr-ssl-redirect.yaml +++ /dev/null @@ -1,31 +0,0 @@ -server: - command: "php ../../http/client.php echo pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -http: - debug: true - address: :8087 - max_request_size: 1024 - middleware: [ "" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - pool: - num_workers: 1 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s - - ssl: - address: :8895 - redirect: true - cert: fixtures/server.crt - key: fixtures/server.key -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/http/configs/.rr-ssl.yaml b/tests/plugins/http/configs/.rr-ssl.yaml deleted file mode 100644 index 8a0f16b8..00000000 --- a/tests/plugins/http/configs/.rr-ssl.yaml +++ /dev/null @@ -1,32 +0,0 @@ -server: - command: "php ../../http/client.php echo pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -http: - address: :8085 - max_request_size: 1024 - middleware: [ "" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - pool: - num_workers: 1 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s - - ssl: - address: :8893 - redirect: false - cert: fixtures/server.crt - key: fixtures/server.key - fcgi: - address: tcp://0.0.0.0:16920 -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/http/fixtures/server.crt b/tests/plugins/http/fixtures/server.crt deleted file mode 100644 index 24d67fd7..00000000 --- a/tests/plugins/http/fixtures/server.crt +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICTTCCAdOgAwIBAgIJAOKyUd+llTRKMAoGCCqGSM49BAMCMGMxCzAJBgNVBAYT -AlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2Nv -MRMwEQYDVQQKDApSb2FkUnVubmVyMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTgw -OTMwMTMzNDUzWhcNMjgwOTI3MTMzNDUzWjBjMQswCQYDVQQGEwJVUzETMBEGA1UE -CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzETMBEGA1UECgwK -Um9hZFJ1bm5lcjESMBAGA1UEAwwJbG9jYWxob3N0MHYwEAYHKoZIzj0CAQYFK4EE -ACIDYgAEVnbShsM+l5RR3wfWWmGhzuFGwNzKCk7i9xyobDIyBUxG/UUSfj7KKlUX -puDnDEtF5xXcepl744CyIAYFLOXHb5WqI4jCOzG0o9f/00QQ4bQudJOdbqV910QF -C2vb7Fxro1MwUTAdBgNVHQ4EFgQU9xUexnbB6ORKayA7Pfjzs33otsAwHwYDVR0j -BBgwFoAU9xUexnbB6ORKayA7Pfjzs33otsAwDwYDVR0TAQH/BAUwAwEB/zAKBggq -hkjOPQQDAgNoADBlAjEAue3HhR/MUhxoa9tSDBtOJT3FYbDQswrsdqBTz97CGKst -e7XeZ3HMEvEXy0hGGEMhAjAqcD/4k9vViVppgWFtkk6+NFbm+Kw/QeeAiH5FgFSj -8xQcb+b7nPwNLp3JOkXkVd4= ------END CERTIFICATE----- diff --git a/tests/plugins/http/fixtures/server.key b/tests/plugins/http/fixtures/server.key deleted file mode 100644 index 7501dd46..00000000 --- a/tests/plugins/http/fixtures/server.key +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN EC PARAMETERS----- -BgUrgQQAIg== ------END EC PARAMETERS----- ------BEGIN EC PRIVATE KEY----- -MIGkAgEBBDCQP8utxNbHR6xZOLAJgUhn88r6IrPqmN0MsgGJM/jePB+T9UhkmIU8 -PMm2HeScbcugBwYFK4EEACKhZANiAARWdtKGwz6XlFHfB9ZaYaHO4UbA3MoKTuL3 -HKhsMjIFTEb9RRJ+PsoqVRem4OcMS0XnFdx6mXvjgLIgBgUs5cdvlaojiMI7MbSj -1//TRBDhtC50k51upX3XRAULa9vsXGs= ------END EC PRIVATE KEY----- diff --git a/tests/plugins/http/handler_test.go b/tests/plugins/http/handler_test.go deleted file mode 100644 index fc672e36..00000000 --- a/tests/plugins/http/handler_test.go +++ /dev/null @@ -1,1861 +0,0 @@ -package http - -import ( - "bytes" - "context" - "io/ioutil" - "mime/multipart" - "net/url" - "os/exec" - "runtime" - "strings" - - "github.com/spiral/roadrunner/v2/pkg/pool" - "github.com/spiral/roadrunner/v2/pkg/transport/pipe" - httpPlugin "github.com/spiral/roadrunner/v2/plugins/http" - "github.com/spiral/roadrunner/v2/plugins/http/config" - "github.com/stretchr/testify/assert" - - "net/http" - "os" - "testing" - "time" -) - -func TestHandler_Echo(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "echo", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8177", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - go func(server *http.Server) { - err := server.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }(hs) - time.Sleep(time.Millisecond * 10) - - body, r, err := get("http://localhost:8177/?hello=world") - assert.NoError(t, err) - defer func() { - _ = r.Body.Close() - }() - assert.Equal(t, 201, r.StatusCode) - assert.Equal(t, "WORLD", body) -} - -func Test_HandlerErrors(t *testing.T) { - _, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, nil) - assert.Error(t, err) -} - -func TestHandler_Headers(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "header", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8078", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 100) - - req, err := http.NewRequest("GET", "http://localhost:8078?hello=world", nil) - assert.NoError(t, err) - - req.Header.Add("input", "sample") - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - defer func() { - err := r.Body.Close() - if err != nil { - t.Errorf("error during the closing Body: error %v", err) - } - }() - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - assert.Equal(t, "world", r.Header.Get("Header")) - assert.Equal(t, "SAMPLE", string(b)) -} - -func TestHandler_Empty_User_Agent(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "user-agent", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":19658", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - req, err := http.NewRequest("GET", "http://localhost:19658?hello=world", nil) - assert.NoError(t, err) - - req.Header.Add("user-agent", "") - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - defer func() { - err := r.Body.Close() - if err != nil { - t.Errorf("error during the closing Body: error %v", err) - } - }() - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - assert.Equal(t, "", string(b)) -} - -func TestHandler_User_Agent(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "user-agent", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":25688", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - req, err := http.NewRequest("GET", "http://localhost:25688?hello=world", nil) - assert.NoError(t, err) - - req.Header.Add("User-Agent", "go-agent") - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - defer func() { - err := r.Body.Close() - if err != nil { - t.Errorf("error during the closing Body: error %v", err) - } - }() - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - assert.Equal(t, "go-agent", string(b)) -} - -func TestHandler_Cookies(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "cookie", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8079", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - req, err := http.NewRequest("GET", "http://localhost:8079", nil) - assert.NoError(t, err) - - req.AddCookie(&http.Cookie{Name: "input", Value: "input-value"}) - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - defer func() { - err := r.Body.Close() - if err != nil { - t.Errorf("error during the closing Body: error %v", err) - } - }() - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - assert.Equal(t, "INPUT-VALUE", string(b)) - - for _, c := range r.Cookies() { - assert.Equal(t, "output", c.Name) - assert.Equal(t, "cookie-output", c.Value) - } -} - -func TestHandler_JsonPayload_POST(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "payload", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8090", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - req, err := http.NewRequest( - "POST", - "http://localhost"+hs.Addr, - bytes.NewBufferString(`{"key":"value"}`), - ) - assert.NoError(t, err) - - req.Header.Add("Content-Type", "application/json") - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - defer func() { - err := r.Body.Close() - if err != nil { - t.Errorf("error during the closing Body: error %v", err) - } - }() - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - assert.Equal(t, `{"value":"key"}`, string(b)) -} - -func TestHandler_JsonPayload_PUT(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "payload", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8081", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - req, err := http.NewRequest("PUT", "http://localhost"+hs.Addr, bytes.NewBufferString(`{"key":"value"}`)) - assert.NoError(t, err) - - req.Header.Add("Content-Type", "application/json") - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - defer func() { - err := r.Body.Close() - if err != nil { - t.Errorf("error during the closing Body: error %v", err) - } - }() - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - assert.Equal(t, `{"value":"key"}`, string(b)) -} - -func TestHandler_JsonPayload_PATCH(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "payload", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8082", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - req, err := http.NewRequest("PATCH", "http://localhost"+hs.Addr, bytes.NewBufferString(`{"key":"value"}`)) - assert.NoError(t, err) - - req.Header.Add("Content-Type", "application/json") - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - defer func() { - err := r.Body.Close() - if err != nil { - t.Errorf("error during the closing Body: error %v", err) - } - }() - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - assert.Equal(t, `{"value":"key"}`, string(b)) -} - -func TestHandler_FormData_POST(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "data", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8083", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 500) - - form := url.Values{} - - form.Add("key", "value") - form.Add("name[]", "name1") - form.Add("name[]", "name2") - form.Add("name[]", "name3") - form.Add("arr[x][y][z]", "y") - form.Add("arr[x][y][e]", "f") - form.Add("arr[c]p", "l") - form.Add("arr[c]z", "") - - req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, strings.NewReader(form.Encode())) - assert.NoError(t, err) - - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - defer func() { - err := r.Body.Close() - if err != nil { - t.Errorf("error during the closing Body: error %v", err) - } - }() - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - - // Sorted - assert.Equal(t, "{\"arr\":{\"c\":{\"p\":\"l\",\"z\":\"\"},\"x\":{\"y\":{\"e\":\"f\",\"z\":\"y\"}}},\"key\":\"value\",\"name\":[\"name1\",\"name2\",\"name3\"]}", string(b)) -} - -func TestHandler_FormData_POST_Overwrite(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "data", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8083", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - form := url.Values{} - - form.Add("key", "value") - form.Add("key", "value2") - form.Add("name[]", "name1") - form.Add("name[]", "name2") - form.Add("name[]", "name3") - form.Add("arr[x][y][z]", "y") - form.Add("arr[x][y][e]", "f") - form.Add("arr[c]p", "l") - form.Add("arr[c]z", "") - - req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, strings.NewReader(form.Encode())) - assert.NoError(t, err) - - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - defer func() { - err := r.Body.Close() - if err != nil { - t.Errorf("error during the closing Body: error %v", err) - } - }() - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - - assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value2","name":["name1","name2","name3"]}`, string(b)) -} - -func TestHandler_FormData_POST_Form_UrlEncoded_Charset(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "data", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8083", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - form := url.Values{} - - form.Add("key", "value") - form.Add("name[]", "name1") - form.Add("name[]", "name2") - form.Add("name[]", "name3") - form.Add("arr[x][y][z]", "y") - form.Add("arr[x][y][e]", "f") - form.Add("arr[c]p", "l") - form.Add("arr[c]z", "") - - req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, strings.NewReader(form.Encode())) - assert.NoError(t, err) - - req.Header.Add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - defer func() { - err := r.Body.Close() - if err != nil { - t.Errorf("error during the closing Body: error %v", err) - } - }() - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - - assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b)) -} - -func TestHandler_FormData_PUT(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "data", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":17834", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 500) - - form := url.Values{} - - form.Add("key", "value") - form.Add("name[]", "name1") - form.Add("name[]", "name2") - form.Add("name[]", "name3") - form.Add("arr[x][y][z]", "y") - form.Add("arr[x][y][e]", "f") - form.Add("arr[c]p", "l") - form.Add("arr[c]z", "") - - req, err := http.NewRequest("PUT", "http://localhost"+hs.Addr, strings.NewReader(form.Encode())) - assert.NoError(t, err) - - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - defer func() { - err := r.Body.Close() - if err != nil { - t.Errorf("error during the closing Body: error %v", err) - } - }() - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - - assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b)) -} - -func TestHandler_FormData_PATCH(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "data", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8085", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - form := url.Values{} - - form.Add("key", "value") - form.Add("name[]", "name1") - form.Add("name[]", "name2") - form.Add("name[]", "name3") - form.Add("arr[x][y][z]", "y") - form.Add("arr[x][y][e]", "f") - form.Add("arr[c]p", "l") - form.Add("arr[c]z", "") - - req, err := http.NewRequest("PATCH", "http://localhost"+hs.Addr, strings.NewReader(form.Encode())) - assert.NoError(t, err) - - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - defer func() { - err := r.Body.Close() - if err != nil { - t.Errorf("error during the closing Body: error %v", err) - } - }() - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - - assert.Equal(t, "{\"arr\":{\"c\":{\"p\":\"l\",\"z\":\"\"},\"x\":{\"y\":{\"e\":\"f\",\"z\":\"y\"}}},\"key\":\"value\",\"name\":[\"name1\",\"name2\",\"name3\"]}", string(b)) -} - -func TestHandler_Multipart_POST(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "data", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8019", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - var mb bytes.Buffer - w := multipart.NewWriter(&mb) - err = w.WriteField("key", "value") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("key", "value") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("name[]", "name1") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("name[]", "name2") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("name[]", "name3") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("arr[x][y][z]", "y") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("arr[x][y][e]", "f") - - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("arr[c]p", "l") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("arr[c]z", "") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.Close() - if err != nil { - t.Errorf("error closing the writer: error %v", err) - } - - req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, &mb) - assert.NoError(t, err) - - req.Header.Set("Content-Type", w.FormDataContentType()) - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - defer func() { - err := r.Body.Close() - if err != nil { - t.Errorf("error during the closing Body: error %v", err) - } - }() - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - - assert.Equal(t, "{\"arr\":{\"c\":{\"p\":\"l\",\"z\":\"\"},\"x\":{\"y\":{\"e\":\"f\",\"z\":\"y\"}}},\"key\":\"value\",\"name\":[\"name1\",\"name2\",\"name3\"]}", string(b)) -} - -func TestHandler_Multipart_PUT(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "data", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8020", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 500) - - var mb bytes.Buffer - w := multipart.NewWriter(&mb) - err = w.WriteField("key", "value") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("key", "value") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("name[]", "name1") - - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("name[]", "name2") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("name[]", "name3") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("arr[x][y][z]", "y") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("arr[x][y][e]", "f") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("arr[c]p", "l") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("arr[c]z", "") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.Close() - if err != nil { - t.Errorf("error closing the writer: error %v", err) - } - - req, err := http.NewRequest("PUT", "http://localhost"+hs.Addr, &mb) - assert.NoError(t, err) - - req.Header.Set("Content-Type", w.FormDataContentType()) - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - defer func() { - err := r.Body.Close() - if err != nil { - t.Errorf("error during the closing Body: error %v", err) - } - }() - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - - assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b)) -} - -func TestHandler_Multipart_PATCH(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "data", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8021", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 500) - - var mb bytes.Buffer - w := multipart.NewWriter(&mb) - err = w.WriteField("key", "value") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("key", "value") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("name[]", "name1") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("name[]", "name2") - - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("name[]", "name3") - - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("arr[x][y][z]", "y") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("arr[x][y][e]", "f") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("arr[c]p", "l") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.WriteField("arr[c]z", "") - if err != nil { - t.Errorf("error writing the field: error %v", err) - } - - err = w.Close() - if err != nil { - t.Errorf("error closing the writer: error %v", err) - } - - req, err := http.NewRequest("PATCH", "http://localhost"+hs.Addr, &mb) - assert.NoError(t, err) - - req.Header.Set("Content-Type", w.FormDataContentType()) - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - defer func() { - err := r.Body.Close() - if err != nil { - t.Errorf("error during the closing Body: error %v", err) - } - }() - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - - assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b)) -} - -func TestHandler_Error(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "error", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8177", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - _, r, err := get("http://localhost:8177/?hello=world") - assert.NoError(t, err) - defer func() { - _ = r.Body.Close() - }() - assert.Equal(t, 500, r.StatusCode) -} - -func TestHandler_Error2(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "error2", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8177", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - _, r, err := get("http://localhost:8177/?hello=world") - assert.NoError(t, err) - defer func() { - _ = r.Body.Close() - }() - assert.Equal(t, 500, r.StatusCode) -} - -func TestHandler_Error3(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "pid", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8177", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err = hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - b2 := &bytes.Buffer{} - for i := 0; i < 1024*1024; i++ { - b2.Write([]byte(" ")) - } - - req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, b2) - assert.NoError(t, err) - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - defer func() { - err = r.Body.Close() - if err != nil { - t.Errorf("error during the closing Body: error %v", err) - } - }() - - assert.NoError(t, err) - assert.Equal(t, 500, r.StatusCode) -} - -func TestHandler_ResponseDuration(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "echo", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8177", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - gotresp := make(chan interface{}) - h.AddListener(func(event interface{}) { - switch t := event.(type) { - case httpPlugin.ResponseEvent: - if t.Elapsed() > 0 { - close(gotresp) - } - default: - } - }) - - body, r, err := get("http://localhost:8177/?hello=world") - assert.NoError(t, err) - defer func() { - _ = r.Body.Close() - }() - - <-gotresp - - assert.Equal(t, 201, r.StatusCode) - assert.Equal(t, "WORLD", body) -} - -func TestHandler_ResponseDurationDelayed(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "echoDelay", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8177", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - gotresp := make(chan interface{}) - h.AddListener(func(event interface{}) { - switch tp := event.(type) { - case httpPlugin.ResponseEvent: - if tp.Elapsed() > time.Second { - close(gotresp) - } - default: - } - }) - - body, r, err := get("http://localhost:8177/?hello=world") - assert.NoError(t, err) - defer func() { - _ = r.Body.Close() - }() - <-gotresp - - assert.Equal(t, 201, r.StatusCode) - assert.Equal(t, "WORLD", body) -} - -func TestHandler_ErrorDuration(t *testing.T) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "error", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8177", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err = hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - goterr := make(chan interface{}) - h.AddListener(func(event interface{}) { - switch tp := event.(type) { - case httpPlugin.ErrorEvent: - if tp.Elapsed() > 0 { - close(goterr) - } - default: - } - }) - - _, r, err := get("http://localhost:8177/?hello=world") - assert.NoError(t, err) - defer func() { - _ = r.Body.Close() - }() - - <-goterr - - assert.Equal(t, 500, r.StatusCode) -} - -func TestHandler_IP(t *testing.T) { - trusted := []string{ - "10.0.0.0/8", - "127.0.0.0/8", - "172.16.0.0/12", - "192.168.0.0/16", - "::1/128", - "fc00::/7", - "fe80::/10", - } - - cidrs, err := config.ParseCIDRs(trusted) - assert.NoError(t, err) - assert.NotNil(t, cidrs) - - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "ip", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, cidrs, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: "127.0.0.1:8177", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - body, r, err := get("http://127.0.0.1:8177/") - assert.NoError(t, err) - defer func() { - _ = r.Body.Close() - }() - assert.Equal(t, 200, r.StatusCode) - assert.Equal(t, "127.0.0.1", body) -} - -func TestHandler_XRealIP(t *testing.T) { - trusted := []string{ - "10.0.0.0/8", - "127.0.0.0/8", - "172.16.0.0/12", - "192.168.0.0/16", - "::1/128", - "fc00::/7", - "fe80::/10", - } - - cidrs, err := config.ParseCIDRs(trusted) - assert.NoError(t, err) - assert.NotNil(t, cidrs) - - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "ip", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, cidrs, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: "127.0.0.1:8179", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - body, r, err := getHeader("http://127.0.0.1:8179/", map[string]string{ - "X-Real-Ip": "200.0.0.1", - }) - - assert.NoError(t, err) - defer func() { - _ = r.Body.Close() - }() - assert.Equal(t, 200, r.StatusCode) - assert.Equal(t, "200.0.0.1", body) -} - -func TestHandler_XForwardedFor(t *testing.T) { - trusted := []string{ - "10.0.0.0/8", - "127.0.0.0/8", - "172.16.0.0/12", - "192.168.0.0/16", - "100.0.0.0/16", - "200.0.0.0/16", - "::1/128", - "fc00::/7", - "fe80::/10", - } - - cidrs, err := config.ParseCIDRs(trusted) - assert.NoError(t, err) - assert.NotNil(t, cidrs) - - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "ip", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, cidrs, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: "127.0.0.1:8177", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - body, r, err := getHeader("http://127.0.0.1:8177/", map[string]string{ - "X-Forwarded-For": "100.0.0.1, 200.0.0.1, invalid, 101.0.0.1", - }) - - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - assert.Equal(t, "101.0.0.1", body) - _ = r.Body.Close() - - body, r, err = getHeader("http://127.0.0.1:8177/", map[string]string{ - "X-Forwarded-For": "100.0.0.1, 200.0.0.1, 101.0.0.1, invalid", - }) - - assert.NoError(t, err) - _ = r.Body.Close() - assert.Equal(t, 200, r.StatusCode) - assert.Equal(t, "101.0.0.1", body) -} - -func TestHandler_XForwardedFor_NotTrustedRemoteIp(t *testing.T) { - trusted := []string{ - "10.0.0.0/8", - } - - cidrs, err := config.ParseCIDRs(trusted) - assert.NoError(t, err) - assert.NotNil(t, cidrs) - - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "ip", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, cidrs, p) - assert.NoError(t, err) - - hs := &http.Server{Addr: "127.0.0.1:8177", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - body, r, err := getHeader("http://127.0.0.1:8177/", map[string]string{ - "X-Forwarded-For": "100.0.0.1, 200.0.0.1, invalid, 101.0.0.1", - }) - - assert.NoError(t, err) - _ = r.Body.Close() - assert.Equal(t, 200, r.StatusCode) - assert.Equal(t, "127.0.0.1", body) -} - -func BenchmarkHandler_Listen_Echo(b *testing.B) { - p, err := pool.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "echo", "pipes") }, - pipe.NewPipeFactory(), - pool.Config{ - NumWorkers: uint64(runtime.NumCPU()), - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - b.Fatal(err) - } - defer func() { - p.Destroy(context.Background()) - }() - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, p) - assert.NoError(b, err) - - hs := &http.Server{Addr: ":8177", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - b.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err = hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - b.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - b.ResetTimer() - b.ReportAllocs() - bb := "WORLD" - for n := 0; n < b.N; n++ { - r, err := http.Get("http://localhost:8177/?hello=world") - if err != nil { - b.Fail() - } - // Response might be nil here - if r != nil { - br, err := ioutil.ReadAll(r.Body) - if err != nil { - b.Errorf("error reading Body: error %v", err) - } - if string(br) != bb { - b.Fail() - } - err = r.Body.Close() - if err != nil { - b.Errorf("error closing the Body: error %v", err) - } - } else { - b.Errorf("got nil response") - } - } -} diff --git a/tests/plugins/http/http_plugin_test.go b/tests/plugins/http/http_plugin_test.go deleted file mode 100644 index 4f99dbbb..00000000 --- a/tests/plugins/http/http_plugin_test.go +++ /dev/null @@ -1,1430 +0,0 @@ -package http - -import ( - "bytes" - "crypto/tls" - "fmt" - "io/ioutil" - "net" - "net/http" - "net/http/httptest" - "net/rpc" - "os" - "os/signal" - "sync" - "syscall" - "testing" - "time" - - "github.com/golang/mock/gomock" - endure "github.com/spiral/endure/pkg/container" - goridgeRpc "github.com/spiral/goridge/v3/pkg/rpc" - "github.com/spiral/roadrunner/v2/pkg/events" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/informer" - "github.com/spiral/roadrunner/v2/plugins/logger" - "github.com/spiral/roadrunner/v2/plugins/resetter" - "github.com/spiral/roadrunner/v2/plugins/server" - "github.com/spiral/roadrunner/v2/tests/mocks" - "github.com/spiral/roadrunner/v2/tools" - "github.com/yookoala/gofast" - - httpPlugin "github.com/spiral/roadrunner/v2/plugins/http" - rpcPlugin "github.com/spiral/roadrunner/v2/plugins/rpc" - "github.com/stretchr/testify/assert" -) - -var sslClient = &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, //nolint:gosec - }, - }, -} - -func TestHTTPInit(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - rIn := makeConfig("6001", "15395", "7921", ":8892", "false", "false", "php ../../http/client.php echo pipes") - cfg := &config.Viper{ - ReadInCfg: rIn, - Type: "yaml", - } - - err = cont.RegisterAll( - cfg, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - stopCh <- struct{}{} - wg.Wait() -} - -func TestHTTPNoConfigSection(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-no-http.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second * 2) - stopCh <- struct{}{} - wg.Wait() -} - -func TestHTTPInformerReset(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-resetter.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &rpcPlugin.Plugin{}, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &informer.Plugin{}, - &resetter.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second * 1) - t.Run("HTTPInformerTest", informerTest) - t.Run("HTTPEchoTestBefore", echoHTTP) - t.Run("HTTPResetTest", resetTest) - t.Run("HTTPEchoTestAfter", echoHTTP) - - stopCh <- struct{}{} - - wg.Wait() -} - -func echoHTTP(t *testing.T) { - req, err := http.NewRequest("GET", "http://localhost:10084?hello=world", nil) - assert.NoError(t, err) - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - assert.Equal(t, 201, r.StatusCode) - assert.Equal(t, "WORLD", string(b)) - - err = r.Body.Close() - assert.NoError(t, err) -} - -func resetTest(t *testing.T) { - conn, err := net.Dial("tcp", "127.0.0.1:6001") - assert.NoError(t, err) - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - // WorkerList contains list of workers. - - var ret bool - err = client.Call("resetter.Reset", "http", &ret) - assert.NoError(t, err) - assert.True(t, ret) - ret = false - - var services []string - err = client.Call("resetter.List", nil, &services) - assert.NoError(t, err) - if services[0] != "http" { - t.Fatal("no enough services") - } -} - -func informerTest(t *testing.T) { - conn, err := net.Dial("tcp", "127.0.0.1:6001") - assert.NoError(t, err) - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - // WorkerList contains list of workers. - list := struct { - // Workers is list of workers. - Workers []tools.ProcessState `json:"workers"` - }{} - - err = client.Call("informer.Workers", "http", &list) - assert.NoError(t, err) - assert.Len(t, list.Workers, 2) -} - -func TestSSL(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-ssl.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &rpcPlugin.Plugin{}, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second * 1) - t.Run("SSLEcho", sslEcho) - t.Run("SSLNoRedirect", sslNoRedirect) - t.Run("fCGIecho", fcgiEcho) - - stopCh <- struct{}{} - wg.Wait() -} - -func sslNoRedirect(t *testing.T) { - req, err := http.NewRequest("GET", "http://localhost:8085?hello=world", nil) - assert.NoError(t, err) - - r, err := sslClient.Do(req) - assert.NoError(t, err) - - assert.Nil(t, r.TLS) - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 201, r.StatusCode) - assert.Equal(t, "WORLD", string(b)) - - err2 := r.Body.Close() - if err2 != nil { - t.Errorf("fail to close the Body: error %v", err2) - } -} - -func sslEcho(t *testing.T) { - req, err := http.NewRequest("GET", "https://localhost:8893?hello=world", nil) - assert.NoError(t, err) - - r, err := sslClient.Do(req) - assert.NoError(t, err) - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 201, r.StatusCode) - assert.Equal(t, "WORLD", string(b)) - - err2 := r.Body.Close() - if err2 != nil { - t.Errorf("fail to close the Body: error %v", err2) - } -} - -func fcgiEcho(t *testing.T) { - fcgiConnFactory := gofast.SimpleConnFactory("tcp", "0.0.0.0:16920") - - fcgiHandler := gofast.NewHandler( - gofast.BasicParamsMap(gofast.BasicSession), - gofast.SimpleClientFactory(fcgiConnFactory, 0), - ) - - w := httptest.NewRecorder() - req := httptest.NewRequest("GET", "http://site.local/?hello=world", nil) - fcgiHandler.ServeHTTP(w, req) - - body, err := ioutil.ReadAll(w.Result().Body) //nolint:bodyclose - - defer func() { - _ = w.Result().Body.Close() - w.Body.Reset() - }() - - assert.NoError(t, err) - assert.Equal(t, 201, w.Result().StatusCode) //nolint:bodyclose - assert.Equal(t, "WORLD", string(body)) -} - -func TestSSLRedirect(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-ssl-redirect.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &rpcPlugin.Plugin{}, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second * 1) - t.Run("SSLRedirect", sslRedirect) - - stopCh <- struct{}{} - wg.Wait() -} - -func sslRedirect(t *testing.T) { - req, err := http.NewRequest("GET", "http://localhost:8087?hello=world", nil) - assert.NoError(t, err) - - r, err := sslClient.Do(req) - assert.NoError(t, err) - assert.NotNil(t, r.TLS) - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 201, r.StatusCode) - assert.Equal(t, "WORLD", string(b)) - - err2 := r.Body.Close() - if err2 != nil { - t.Errorf("fail to close the Body: error %v", err2) - } -} - -func TestSSLPushPipes(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-ssl-push.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &rpcPlugin.Plugin{}, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second * 1) - t.Run("SSLPush", sslPush) - - stopCh <- struct{}{} - wg.Wait() -} - -func sslPush(t *testing.T) { - req, err := http.NewRequest("GET", "https://localhost:8894?hello=world", nil) - assert.NoError(t, err) - - r, err := sslClient.Do(req) - assert.NoError(t, err) - - assert.NotNil(t, r.TLS) - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.Equal(t, "", r.Header.Get("Http2-Push")) - - assert.NoError(t, err) - assert.Equal(t, 201, r.StatusCode) - assert.Equal(t, "WORLD", string(b)) - - err2 := r.Body.Close() - if err2 != nil { - t.Errorf("fail to close the Body: error %v", err2) - } -} - -func TestFastCGI_RequestUri(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-fcgi-reqUri.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second * 1) - t.Run("FastCGIServiceRequestUri", fcgiReqURI) - - stopCh <- struct{}{} - wg.Wait() -} - -func fcgiReqURI(t *testing.T) { - time.Sleep(time.Second * 2) - fcgiConnFactory := gofast.SimpleConnFactory("tcp", "127.0.0.1:6921") - - fcgiHandler := gofast.NewHandler( - gofast.BasicParamsMap(gofast.BasicSession), - gofast.SimpleClientFactory(fcgiConnFactory, 0), - ) - - w := httptest.NewRecorder() - req := httptest.NewRequest("GET", "http://site.local/hello-world", nil) - fcgiHandler.ServeHTTP(w, req) - - body, err := ioutil.ReadAll(w.Result().Body) //nolint:bodyclose - assert.NoError(t, err) - assert.Equal(t, 200, w.Result().StatusCode) //nolint:bodyclose - assert.Equal(t, "http://site.local/hello-world", string(body)) -} - -func TestH2CUpgrade(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-h2c.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &rpcPlugin.Plugin{}, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second * 1) - t.Run("H2cUpgrade", h2cUpgrade) - - stopCh <- struct{}{} - wg.Wait() -} - -func h2cUpgrade(t *testing.T) { - req, err := http.NewRequest("PRI", "http://localhost:8083?hello=world", nil) - if err != nil { - t.Fatal(err) - } - - req.Header.Add("Upgrade", "h2c") - req.Header.Add("Connection", "HTTP2-Settings") - req.Header.Add("HTTP2-Settings", "") - - r, err2 := http.DefaultClient.Do(req) - if err2 != nil { - t.Fatal(err) - } - - assert.Equal(t, "101 Switching Protocols", r.Status) - - err3 := r.Body.Close() - if err3 != nil { - t.Fatal(err) - } -} - -func TestH2C(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-h2c.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &rpcPlugin.Plugin{}, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second * 1) - t.Run("H2c", h2c) - - stopCh <- struct{}{} - wg.Wait() -} - -func h2c(t *testing.T) { - req, err := http.NewRequest("PRI", "http://localhost:8083?hello=world", nil) - if err != nil { - t.Fatal(err) - } - - req.Header.Add("Connection", "HTTP2-Settings") - req.Header.Add("HTTP2-Settings", "") - - r, err2 := http.DefaultClient.Do(req) - if err2 != nil { - t.Fatal(err) - } - - assert.Equal(t, "201 Created", r.Status) - - err3 := r.Body.Close() - if err3 != nil { - t.Fatal(err) - } -} - -func TestHttpMiddleware(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-http.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &rpcPlugin.Plugin{}, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &PluginMiddleware{}, - &PluginMiddleware2{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second * 1) - t.Run("MiddlewareTest", middleware) - - stopCh <- struct{}{} - wg.Wait() -} - -func middleware(t *testing.T) { - req, err := http.NewRequest("GET", "http://localhost:18903?hello=world", nil) - assert.NoError(t, err) - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.Equal(t, 201, r.StatusCode) - assert.Equal(t, "WORLD", string(b)) - - err = r.Body.Close() - assert.NoError(t, err) - - req, err = http.NewRequest("GET", "http://localhost:18903/halt", nil) - assert.NoError(t, err) - - r, err = http.DefaultClient.Do(req) - assert.NoError(t, err) - b, err = ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.Equal(t, 500, r.StatusCode) - assert.Equal(t, "halted", string(b)) - - err = r.Body.Close() - assert.NoError(t, err) -} - -func TestHttpEchoErr(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - rIn := ` -rpc: - listen: tcp://127.0.0.1:6001 - disabled: false - -server: - command: "php ../../http/client.php echoerr pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relayTimeout: "20s" - -http: - debug: true - address: 127.0.0.1:34999 - maxRequestSize: 1024 - middleware: [ "pluginMiddleware", "pluginMiddleware2" ] - uploads: - forbid: [ "" ] - trustedSubnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - pool: - numWorkers: 2 - maxJobs: 0 - allocateTimeout: 60s - destroyTimeout: 60s -logs: - mode: development - level: error -` - - cfg := &config.Viper{ - Path: "", - Prefix: "", - Type: "yaml", - ReadInCfg: []byte(rIn), - } - - controller := gomock.NewController(t) - mockLogger := mocks.NewMockLogger(controller) - - mockLogger.EXPECT().Debug("worker destructed", "pid", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Debug("worker constructed", "pid", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Debug("", "remote", gomock.Any(), "ts", gomock.Any(), "resp.status", gomock.Any(), "method", gomock.Any(), "uri", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Debug("WORLD", "pid", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Debug("worker event received", "event", events.EventWorkerLog, "worker state", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Error(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() // placeholder for the workerlogerror - - err = cont.RegisterAll( - cfg, - mockLogger, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &PluginMiddleware{}, - &PluginMiddleware2{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second * 1) - t.Run("HttpEchoError", echoError) - - stopCh <- struct{}{} - wg.Wait() -} - -func echoError(t *testing.T) { - req, err := http.NewRequest("GET", "http://localhost:34999?hello=world", nil) - assert.NoError(t, err) - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.Equal(t, 201, r.StatusCode) - assert.Equal(t, "WORLD", string(b)) - err = r.Body.Close() - assert.NoError(t, err) -} - -func TestHttpEnvVariables(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-env.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &PluginMiddleware{}, - &PluginMiddleware2{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second * 1) - t.Run("EnvVariablesTest", envVarsTest) - - stopCh <- struct{}{} - wg.Wait() -} - -func envVarsTest(t *testing.T) { - req, err := http.NewRequest("GET", "http://localhost:12084", nil) - assert.NoError(t, err) - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.Equal(t, 200, r.StatusCode) - assert.Equal(t, "ENV_VALUE", string(b)) - - err = r.Body.Close() - assert.NoError(t, err) -} - -func TestHttpBrokenPipes(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-broken-pipes.yaml", - Prefix: "rr", - Type: "yaml", - } - - err = cont.RegisterAll( - cfg, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &PluginMiddleware{}, - &PluginMiddleware2{}, - ) - assert.NoError(t, err) - - err = cont.Init() - assert.Error(t, err) - - _, err = cont.Serve() - assert.Error(t, err) -} - -func TestHTTPSupervisedPool(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-http-supervised-pool.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &rpcPlugin.Plugin{}, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &informer.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second * 1) - t.Run("HTTPEchoTest", echoHTTP2) - // worker should be destructed (idle_ttl) - t.Run("HTTPInformerCompareWorkersTest", informerTest2) - - stopCh <- struct{}{} - wg.Wait() -} - -func echoHTTP2(t *testing.T) { - req, err := http.NewRequest("GET", "http://localhost:18888?hello=world", nil) - assert.NoError(t, err) - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - assert.Equal(t, 201, r.StatusCode) - assert.Equal(t, "WORLD", string(b)) - - err = r.Body.Close() - assert.NoError(t, err) -} - -// get worker -// sleep -// supervisor destroy worker -// compare pid's -func informerTest2(t *testing.T) { - conn, err := net.Dial("tcp", "127.0.0.1:15432") - assert.NoError(t, err) - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - pid := 0 - // WorkerList contains list of workers. - list := struct { - // Workers is list of workers. - Workers []tools.ProcessState `json:"workers"` - }{} - - err = client.Call("informer.Workers", "http", &list) - assert.NoError(t, err) - assert.Len(t, list.Workers, 1) - // save the pid - pid = list.Workers[0].Pid - time.Sleep(time.Second * 10) - - list = struct { - // Workers is list of workers. - Workers []tools.ProcessState `json:"workers"` - }{} - - err = client.Call("informer.Workers", "http", &list) - assert.NoError(t, err) - assert.Len(t, list.Workers, 1) - assert.NotEqual(t, list.Workers[0].Pid, pid) -} - -func get(url string) (string, *http.Response, error) { - r, err := http.Get(url) //nolint:gosec - if err != nil { - return "", nil, err - } - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return "", nil, err - } - defer func() { - _ = r.Body.Close() - }() - return string(b), r, err -} - -// get request and return body -func getHeader(url string, h map[string]string) (string, *http.Response, error) { - req, err := http.NewRequest("GET", url, bytes.NewBuffer(nil)) - if err != nil { - return "", nil, err - } - - for k, v := range h { - req.Header.Set(k, v) - } - - r, err := http.DefaultClient.Do(req) - if err != nil { - return "", nil, err - } - - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return "", nil, err - } - - err = r.Body.Close() - if err != nil { - return "", nil, err - } - return string(b), r, err -} - -func makeConfig(rpcPort, httpPort, fcgiPort, sslAddress, redirect, http2Enabled, command string) []byte { - return []byte(fmt.Sprintf(` -rpc: - listen: tcp://127.0.0.1:%s - disabled: false - -server: - command: "%s" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relayTimeout: "20s" - -http: - address: 127.0.0.1:%s - maxRequestSize: 1024 - middleware: [ "" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - trustedSubnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - pool: - numWorkers: 2 - maxJobs: 0 - allocateTimeout: 60s - destroyTimeout: 60s - - ssl: - address: %s - redirect: %s - cert: fixtures/server.crt - key: fixtures/server.key - # rootCa: root.crt - fcgi: - address: tcp://0.0.0.0:%s - http2: - enabled: %s - h2c: false - maxConcurrentStreams: 128 -logs: - mode: development - level: error -`, rpcPort, command, httpPort, sslAddress, redirect, fcgiPort, http2Enabled)) -} diff --git a/tests/plugins/http/parse_test.go b/tests/plugins/http/parse_test.go deleted file mode 100644 index 5cc1ce32..00000000 --- a/tests/plugins/http/parse_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package http - -import ( - "testing" - - "github.com/spiral/roadrunner/v2/plugins/http" -) - -var samples = []struct { - in string - out []string -}{ - {"key", []string{"key"}}, - {"key[subkey]", []string{"key", "subkey"}}, - {"key[subkey]value", []string{"key", "subkey", "value"}}, - {"key[subkey][value]", []string{"key", "subkey", "value"}}, - {"key[subkey][value][]", []string{"key", "subkey", "value", ""}}, - {"key[subkey] [value][]", []string{"key", "subkey", "value", ""}}, - {"key [ subkey ] [ value ] [ ]", []string{"key", "subkey", "value", ""}}, -} - -func Test_FetchIndexes(t *testing.T) { - for i := 0; i < len(samples); i++ { - r := http.FetchIndexes(samples[i].in) - if !same(r, samples[i].out) { - t.Errorf("got %q, want %q", r, samples[i].out) - } - } -} - -func BenchmarkConfig_FetchIndexes(b *testing.B) { - for _, tt := range samples { - for n := 0; n < b.N; n++ { - r := http.FetchIndexes(tt.in) - if !same(r, tt.out) { - b.Fail() - } - } - } -} - -func same(in, out []string) bool { - if len(in) != len(out) { - return false - } - - for i, v := range in { - if v != out[i] { - return false - } - } - - return true -} diff --git a/tests/plugins/http/plugin1.go b/tests/plugins/http/plugin1.go deleted file mode 100644 index 0ec31211..00000000 --- a/tests/plugins/http/plugin1.go +++ /dev/null @@ -1,27 +0,0 @@ -package http - -import ( - "github.com/spiral/roadrunner/v2/plugins/config" -) - -type Plugin1 struct { - config config.Configurer -} - -func (p1 *Plugin1) Init(cfg config.Configurer) error { - p1.config = cfg - return nil -} - -func (p1 *Plugin1) Serve() chan error { - errCh := make(chan error, 1) - return errCh -} - -func (p1 *Plugin1) Stop() error { - return nil -} - -func (p1 *Plugin1) Name() string { - return "http_test.plugin1" -} diff --git a/tests/plugins/http/plugin_middleware.go b/tests/plugins/http/plugin_middleware.go deleted file mode 100644 index 00640b69..00000000 --- a/tests/plugins/http/plugin_middleware.go +++ /dev/null @@ -1,69 +0,0 @@ -package http - -import ( - "net/http" - - "github.com/spiral/roadrunner/v2/plugins/config" -) - -// PluginMiddleware test -type PluginMiddleware struct { - config config.Configurer -} - -// Init test -func (p *PluginMiddleware) Init(cfg config.Configurer) error { - p.config = cfg - return nil -} - -// Middleware test -func (p *PluginMiddleware) Middleware(next http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/halt" { - w.WriteHeader(500) - _, err := w.Write([]byte("halted")) - if err != nil { - panic("error writing the data to the http reply") - } - } else { - next.ServeHTTP(w, r) - } - } -} - -// Name test -func (p *PluginMiddleware) Name() string { - return "pluginMiddleware" -} - -// PluginMiddleware2 test -type PluginMiddleware2 struct { - config config.Configurer -} - -// Init test -func (p *PluginMiddleware2) Init(cfg config.Configurer) error { - p.config = cfg - return nil -} - -// Middleware test -func (p *PluginMiddleware2) Middleware(next http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/boom" { - w.WriteHeader(555) - _, err := w.Write([]byte("boom")) - if err != nil { - panic("error writing the data to the http reply") - } - } else { - next.ServeHTTP(w, r) - } - } -} - -// Name test -func (p *PluginMiddleware2) Name() string { - return "pluginMiddleware2" -} diff --git a/tests/plugins/http/response_test.go b/tests/plugins/http/response_test.go deleted file mode 100644 index dc9856ac..00000000 --- a/tests/plugins/http/response_test.go +++ /dev/null @@ -1,165 +0,0 @@ -package http - -import ( - "bytes" - "errors" - "net/http" - "testing" - - "github.com/spiral/roadrunner/v2/pkg/payload" - httpPlugin "github.com/spiral/roadrunner/v2/plugins/http" - "github.com/stretchr/testify/assert" -) - -type testWriter struct { - h http.Header - buf bytes.Buffer - wroteHeader bool - code int - err error - pushErr error - pushes []string -} - -func (tw *testWriter) Header() http.Header { return tw.h } - -func (tw *testWriter) Write(p []byte) (int, error) { - if !tw.wroteHeader { - tw.WriteHeader(http.StatusOK) - } - - n, e := tw.buf.Write(p) - if e == nil { - e = tw.err - } - - return n, e -} - -func (tw *testWriter) WriteHeader(code int) { tw.wroteHeader = true; tw.code = code } - -func (tw *testWriter) Push(target string, opts *http.PushOptions) error { - tw.pushes = append(tw.pushes, target) - - return tw.pushErr -} - -func TestNewResponse_Error(t *testing.T) { - r, err := httpPlugin.NewResponse(payload.Payload{Context: []byte(`invalid payload`)}) - assert.Error(t, err) - assert.Nil(t, r) -} - -func TestNewResponse_Write(t *testing.T) { - r, err := httpPlugin.NewResponse(payload.Payload{ - Context: []byte(`{"headers":{"key":["value"]},"status": 301}`), - Body: []byte(`sample body`), - }) - - assert.NoError(t, err) - assert.NotNil(t, r) - - w := &testWriter{h: http.Header(make(map[string][]string))} - assert.NoError(t, r.Write(w)) - - assert.Equal(t, 301, w.code) - assert.Equal(t, "value", w.h.Get("key")) - assert.Equal(t, "sample body", w.buf.String()) -} - -func TestNewResponse_Stream(t *testing.T) { - r, err := httpPlugin.NewResponse(payload.Payload{ - Context: []byte(`{"headers":{"key":["value"]},"status": 301}`), - }) - - // r is pointer, so, it might be nil - if r == nil { - t.Fatal("response is nil") - return - } - - r.Body = new(bytes.Buffer) - r.Body.(*bytes.Buffer).WriteString("hello world") - - assert.NoError(t, err) - assert.NotNil(t, r) - - w := &testWriter{h: http.Header(make(map[string][]string))} - assert.NoError(t, r.Write(w)) - - assert.Equal(t, 301, w.code) - assert.Equal(t, "value", w.h.Get("key")) - assert.Equal(t, "hello world", w.buf.String()) -} - -func TestNewResponse_StreamError(t *testing.T) { - r, err := httpPlugin.NewResponse(payload.Payload{ - Context: []byte(`{"headers":{"key":["value"]},"status": 301}`), - }) - - // r is pointer, so, it might be nil - if r == nil { - t.Fatal("response is nil") - return - } - - r.Body = &bytes.Buffer{} - r.Body.(*bytes.Buffer).WriteString("hello world") - - assert.NoError(t, err) - assert.NotNil(t, r) - - w := &testWriter{h: http.Header(make(map[string][]string)), err: errors.New("error")} - assert.Error(t, r.Write(w)) -} - -func TestWrite_HandlesPush(t *testing.T) { - r, err := httpPlugin.NewResponse(payload.Payload{ - Context: []byte(`{"headers":{"Http2-Push":["/test.js"],"content-type":["text/html"]},"status": 200}`), - }) - - assert.NoError(t, err) - assert.NotNil(t, r) - - w := &testWriter{h: http.Header(make(map[string][]string))} - assert.NoError(t, r.Write(w)) - - assert.Nil(t, w.h["Http2-Push"]) - assert.Equal(t, []string{"/test.js"}, w.pushes) -} - -func TestWrite_HandlesTrailers(t *testing.T) { - r, err := httpPlugin.NewResponse(payload.Payload{ - Context: []byte(`{"headers":{"Trailer":["foo, bar", "baz"],"foo":["test"],"bar":["demo"]},"status": 200}`), - }) - - assert.NoError(t, err) - assert.NotNil(t, r) - - w := &testWriter{h: http.Header(make(map[string][]string))} - assert.NoError(t, r.Write(w)) - - assert.Nil(t, w.h[httpPlugin.TrailerHeaderKey]) - assert.Nil(t, w.h["foo"]) //nolint:staticcheck - assert.Nil(t, w.h["baz"]) //nolint:staticcheck - - assert.Equal(t, "test", w.h.Get("Trailer:foo")) - assert.Equal(t, "demo", w.h.Get("Trailer:bar")) -} - -func TestWrite_HandlesHandlesWhitespacesInTrailer(t *testing.T) { - r, err := httpPlugin.NewResponse(payload.Payload{ - Context: []byte( - `{"headers":{"Trailer":["foo\t,bar , baz"],"foo":["a"],"bar":["b"],"baz":["c"]},"status": 200}`), - }) - - assert.NoError(t, err) - assert.NotNil(t, r) - - w := &testWriter{h: http.Header(make(map[string][]string))} - assert.NoError(t, r.Write(w)) - - assert.Equal(t, "a", w.h.Get("Trailer:foo")) - assert.Equal(t, "b", w.h.Get("Trailer:bar")) - assert.Equal(t, "c", w.h.Get("Trailer:baz")) -} diff --git a/tests/plugins/http/uploads_config_test.go b/tests/plugins/http/uploads_config_test.go deleted file mode 100644 index 4f99b621..00000000 --- a/tests/plugins/http/uploads_config_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package http - -import ( - "os" - "testing" - - "github.com/spiral/roadrunner/v2/plugins/http/config" - "github.com/stretchr/testify/assert" -) - -func TestFsConfig_Forbids(t *testing.T) { - cfg := config.Uploads{Forbid: []string{".php"}} - - assert.True(t, cfg.Forbids("index.php")) - assert.True(t, cfg.Forbids("index.PHP")) - assert.True(t, cfg.Forbids("phpadmin/index.bak.php")) - assert.False(t, cfg.Forbids("index.html")) -} - -func TestFsConfig_TmpFallback(t *testing.T) { - cfg := config.Uploads{Dir: "test"} - assert.Equal(t, "test", cfg.TmpDir()) - - cfg = config.Uploads{Dir: ""} - assert.Equal(t, os.TempDir(), cfg.TmpDir()) -} diff --git a/tests/plugins/http/uploads_test.go b/tests/plugins/http/uploads_test.go deleted file mode 100644 index dd986902..00000000 --- a/tests/plugins/http/uploads_test.go +++ /dev/null @@ -1,433 +0,0 @@ -package http - -import ( - "bytes" - "context" - "crypto/sha512" - "encoding/hex" - "fmt" - "io" - "io/ioutil" - "mime/multipart" - "net/http" - "os" - "os/exec" - "testing" - "time" - - j "github.com/json-iterator/go" - poolImpl "github.com/spiral/roadrunner/v2/pkg/pool" - "github.com/spiral/roadrunner/v2/pkg/transport/pipe" - httpPlugin "github.com/spiral/roadrunner/v2/plugins/http" - "github.com/spiral/roadrunner/v2/plugins/http/config" - "github.com/stretchr/testify/assert" -) - -var json = j.ConfigCompatibleWithStandardLibrary - -const testFile = "uploads_test.go" - -func TestHandler_Upload_File(t *testing.T) { - pool, err := poolImpl.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "upload", "pipes") }, - pipe.NewPipeFactory(), - poolImpl.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, pool) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8021", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - var mb bytes.Buffer - w := multipart.NewWriter(&mb) - - f := mustOpen(testFile) - defer func() { - err := f.Close() - if err != nil { - t.Errorf("failed to close a file: error %v", err) - } - }() - fw, err := w.CreateFormFile("upload", f.Name()) - assert.NotNil(t, fw) - assert.NoError(t, err) - _, err = io.Copy(fw, f) - if err != nil { - t.Errorf("error copying the file: error %v", err) - } - - err = w.Close() - if err != nil { - t.Errorf("error closing the file: error %v", err) - } - - req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, &mb) - assert.NoError(t, err) - - req.Header.Set("Content-Type", w.FormDataContentType()) - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - defer func() { - err := r.Body.Close() - if err != nil { - t.Errorf("error closing the Body: error %v", err) - } - }() - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - - fs := fileString(testFile, 0, "application/octet-stream") - - assert.Equal(t, `{"upload":`+fs+`}`, string(b)) -} - -func TestHandler_Upload_NestedFile(t *testing.T) { - pool, err := poolImpl.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "upload", "pipes") }, - pipe.NewPipeFactory(), - poolImpl.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{}, - }, nil, pool) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8021", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - var mb bytes.Buffer - w := multipart.NewWriter(&mb) - - f := mustOpen(testFile) - defer func() { - err := f.Close() - if err != nil { - t.Errorf("failed to close a file: error %v", err) - } - }() - fw, err := w.CreateFormFile("upload[x][y][z][]", f.Name()) - assert.NotNil(t, fw) - assert.NoError(t, err) - _, err = io.Copy(fw, f) - if err != nil { - t.Errorf("error copying the file: error %v", err) - } - - err = w.Close() - if err != nil { - t.Errorf("error closing the file: error %v", err) - } - - req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, &mb) - assert.NoError(t, err) - - req.Header.Set("Content-Type", w.FormDataContentType()) - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - defer func() { - err := r.Body.Close() - if err != nil { - t.Errorf("error closing the Body: error %v", err) - } - }() - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - - fs := fileString(testFile, 0, "application/octet-stream") - - assert.Equal(t, `{"upload":{"x":{"y":{"z":[`+fs+`]}}}}`, string(b)) -} - -func TestHandler_Upload_File_NoTmpDir(t *testing.T) { - pool, err := poolImpl.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "upload", "pipes") }, - pipe.NewPipeFactory(), - poolImpl.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: "-------", - Forbid: []string{}, - }, nil, pool) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8021", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - var mb bytes.Buffer - w := multipart.NewWriter(&mb) - - f := mustOpen(testFile) - defer func() { - err := f.Close() - if err != nil { - t.Errorf("failed to close a file: error %v", err) - } - }() - fw, err := w.CreateFormFile("upload", f.Name()) - assert.NotNil(t, fw) - assert.NoError(t, err) - _, err = io.Copy(fw, f) - if err != nil { - t.Errorf("error copying the file: error %v", err) - } - - err = w.Close() - if err != nil { - t.Errorf("error closing the file: error %v", err) - } - - req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, &mb) - assert.NoError(t, err) - - req.Header.Set("Content-Type", w.FormDataContentType()) - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - defer func() { - err := r.Body.Close() - if err != nil { - t.Errorf("error closing the Body: error %v", err) - } - }() - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - - fs := fileString(testFile, 6, "application/octet-stream") - - assert.Equal(t, `{"upload":`+fs+`}`, string(b)) -} - -func TestHandler_Upload_File_Forbids(t *testing.T) { - pool, err := poolImpl.Initialize(context.Background(), - func() *exec.Cmd { return exec.Command("php", "../../http/client.php", "upload", "pipes") }, - pipe.NewPipeFactory(), - poolImpl.Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 1000, - DestroyTimeout: time.Second * 1000, - }) - if err != nil { - t.Fatal(err) - } - - h, err := httpPlugin.NewHandler(1024, config.Uploads{ - Dir: os.TempDir(), - Forbid: []string{".go"}, - }, nil, pool) - assert.NoError(t, err) - - hs := &http.Server{Addr: ":8021", Handler: h} - defer func() { - err := hs.Shutdown(context.Background()) - if err != nil { - t.Errorf("error during the shutdown: error %v", err) - } - }() - - go func() { - err := hs.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - t.Errorf("error listening the interface: error %v", err) - } - }() - time.Sleep(time.Millisecond * 10) - - var mb bytes.Buffer - w := multipart.NewWriter(&mb) - - f := mustOpen(testFile) - defer func() { - err := f.Close() - if err != nil { - t.Errorf("failed to close a file: error %v", err) - } - }() - fw, err := w.CreateFormFile("upload", f.Name()) - assert.NotNil(t, fw) - assert.NoError(t, err) - _, err = io.Copy(fw, f) - if err != nil { - t.Errorf("error copying the file: error %v", err) - } - - err = w.Close() - if err != nil { - t.Errorf("error closing the file: error %v", err) - } - - req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, &mb) - assert.NoError(t, err) - - req.Header.Set("Content-Type", w.FormDataContentType()) - - r, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - defer func() { - err := r.Body.Close() - if err != nil { - t.Errorf("error closing the Body: error %v", err) - } - }() - - b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - - assert.NoError(t, err) - assert.Equal(t, 200, r.StatusCode) - - fs := fileString(testFile, 8, "application/octet-stream") - - assert.Equal(t, `{"upload":`+fs+`}`, string(b)) -} - -func Test_FileExists(t *testing.T) { - assert.True(t, exists(testFile)) - assert.False(t, exists("uploads_test.")) -} - -func mustOpen(f string) *os.File { - r, err := os.Open(f) - if err != nil { - panic(err) - } - return r -} - -type fInfo struct { - Name string `json:"name"` - Size int64 `json:"size"` - Mime string `json:"mime"` - Error int `json:"error"` - Sha512 string `json:"sha512,omitempty"` -} - -func fileString(f string, errNo int, mime string) string { - s, err := os.Stat(f) - if err != nil { - fmt.Println(fmt.Errorf("error stat the file, error: %v", err)) - } - - ff, err := os.Open(f) - if err != nil { - fmt.Println(fmt.Errorf("error opening the file, error: %v", err)) - } - - defer func() { - er := ff.Close() - if er != nil { - fmt.Println(fmt.Errorf("error closing the file, error: %v", er)) - } - }() - - h := sha512.New() - _, err = io.Copy(h, ff) - if err != nil { - fmt.Println(fmt.Errorf("error copying the file, error: %v", err)) - } - - v := &fInfo{ - Name: s.Name(), - Size: s.Size(), - Error: errNo, - Mime: mime, - Sha512: hex.EncodeToString(h.Sum(nil)), - } - - if errNo != 0 { - v.Sha512 = "" - v.Size = 0 - } - - r, err := json.Marshal(v) - if err != nil { - fmt.Println(fmt.Errorf("error marshalling fInfo, error: %v", err)) - } - return string(r) -} - -// exists if file exists. -func exists(path string) bool { - if _, err := os.Stat(path); os.IsNotExist(err) { - return false - } - return true -} diff --git a/tests/plugins/informer/.rr-informer.yaml b/tests/plugins/informer/.rr-informer.yaml deleted file mode 100644 index e1edbb44..00000000 --- a/tests/plugins/informer/.rr-informer.yaml +++ /dev/null @@ -1,15 +0,0 @@ -server: - command: "php ../../http/client.php echo pipes" - user: "" - group: "" - env: - "RR_CONFIG": "/some/place/on/the/C134" - "RR_CONFIG2": "C138" - relay: "pipes" - relay_timeout: "20s" - -rpc: - listen: tcp://127.0.0.1:6001 -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/informer/informer_test.go b/tests/plugins/informer/informer_test.go deleted file mode 100644 index 31e14ff4..00000000 --- a/tests/plugins/informer/informer_test.go +++ /dev/null @@ -1,121 +0,0 @@ -package informer - -import ( - "net" - "net/rpc" - "os" - "os/signal" - "sync" - "syscall" - "testing" - "time" - - endure "github.com/spiral/endure/pkg/container" - goridgeRpc "github.com/spiral/goridge/v3/pkg/rpc" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/informer" - "github.com/spiral/roadrunner/v2/plugins/logger" - rpcPlugin "github.com/spiral/roadrunner/v2/plugins/rpc" - "github.com/spiral/roadrunner/v2/plugins/server" - "github.com/spiral/roadrunner/v2/tools" - "github.com/stretchr/testify/assert" -) - -func TestInformerInit(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - if err != nil { - t.Fatal(err) - } - - cfg := &config.Viper{ - Path: ".rr-informer.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &server.Plugin{}, - &logger.ZapLogger{}, - &informer.Plugin{}, - &rpcPlugin.Plugin{}, - &Plugin1{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - stopCh := make(chan struct{}, 1) - - wg := &sync.WaitGroup{} - wg.Add(1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second) - t.Run("InformerWorkersRpcTest", informerWorkersRPCTest) - t.Run("InformerListRpcTest", informerListRPCTest) - - stopCh <- struct{}{} - wg.Wait() -} - -func informerWorkersRPCTest(t *testing.T) { - conn, err := net.Dial("tcp", "127.0.0.1:6001") - assert.NoError(t, err) - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - // WorkerList contains list of workers. - list := struct { - // Workers is list of workers. - Workers []tools.ProcessState `json:"workers"` - }{} - - err = client.Call("informer.Workers", "informer.plugin1", &list) - assert.NoError(t, err) - assert.Len(t, list.Workers, 10) -} - -func informerListRPCTest(t *testing.T) { - conn, err := net.Dial("tcp", "127.0.0.1:6001") - assert.NoError(t, err) - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - // WorkerList contains list of workers. - list := make([]string, 0, 0) - - err = client.Call("informer.List", true, &list) - assert.NoError(t, err) - assert.Equal(t, "informer.plugin1", list[0]) -} diff --git a/tests/plugins/informer/test_plugin.go b/tests/plugins/informer/test_plugin.go deleted file mode 100644 index 2300de89..00000000 --- a/tests/plugins/informer/test_plugin.go +++ /dev/null @@ -1,65 +0,0 @@ -package informer - -import ( - "context" - "time" - - "github.com/spiral/roadrunner/v2/pkg/pool" - "github.com/spiral/roadrunner/v2/pkg/worker" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/server" -) - -var testPoolConfig = pool.Config{ - NumWorkers: 10, - MaxJobs: 100, - AllocateTimeout: time.Second * 10, - DestroyTimeout: time.Second * 10, - Supervisor: &pool.SupervisorConfig{ - WatchTick: 60 * time.Second, - TTL: 1000 * time.Second, - IdleTTL: 10 * time.Second, - ExecTTL: 10 * time.Second, - MaxWorkerMemory: 1000, - }, -} - -// Gauge ////////////// -type Plugin1 struct { - config config.Configurer - server server.Server -} - -func (p1 *Plugin1) Init(cfg config.Configurer, server server.Server) error { - p1.config = cfg - p1.server = server - return nil -} - -func (p1 *Plugin1) Serve() chan error { - errCh := make(chan error, 1) - return errCh -} - -func (p1 *Plugin1) Stop() error { - return nil -} - -func (p1 *Plugin1) Name() string { - return "informer.plugin1" -} - -func (p1 *Plugin1) Workers() []worker.BaseProcess { - p, err := p1.server.NewWorkerPool(context.Background(), testPoolConfig, nil) - if err != nil { - panic(err) - } - - workers := p.Workers() - baseWorkers := make([]worker.BaseProcess, 0, len(workers)) - for i := 0; i < len(workers); i++ { - baseWorkers = append(baseWorkers, worker.FromSync(workers[i].(*worker.SyncWorkerImpl))) - } - - return baseWorkers -} diff --git a/tests/plugins/kv/boltdb/configs/.rr-init.yaml b/tests/plugins/kv/boltdb/configs/.rr-init.yaml deleted file mode 100644 index e4644511..00000000 --- a/tests/plugins/kv/boltdb/configs/.rr-init.yaml +++ /dev/null @@ -1,45 +0,0 @@ -rpc: - listen: tcp://127.0.0.1:6001 - -server: - command: "php ../../../psr-worker-bench.php" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -logs: - mode: development - level: error - -http: - address: 127.0.0.1:44933 - max_request_size: 1024 - middleware: ["gzip", "headers"] - uploads: - forbid: [".php", ".exe", ".bat"] - trusted_subnets: - [ - "10.0.0.0/8", - "127.0.0.0/8", - "172.16.0.0/12", - "192.168.0.0/16", - "::1/128", - "fc00::/7", - "fe80::/10", - ] - pool: - num_workers: 6 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s - -# boltdb simple driver -boltdb: - dir: "." - file: "rr" - bucket: "test" - permissions: 777 - interval: 1 # seconds diff --git a/tests/plugins/kv/boltdb/plugin_test.go b/tests/plugins/kv/boltdb/plugin_test.go deleted file mode 100644 index 3a4542ff..00000000 --- a/tests/plugins/kv/boltdb/plugin_test.go +++ /dev/null @@ -1,195 +0,0 @@ -package boltdb_tests //nolint:golint,stylecheck - -import ( - "net" - "net/rpc" - "os" - "os/signal" - "sync" - "syscall" - "testing" - "time" - - endure "github.com/spiral/endure/pkg/container" - goridgeRpc "github.com/spiral/goridge/v3/pkg/rpc" - "github.com/spiral/roadrunner/v2/plugins/config" - httpPlugin "github.com/spiral/roadrunner/v2/plugins/http" - "github.com/spiral/roadrunner/v2/plugins/kv" - "github.com/spiral/roadrunner/v2/plugins/kv/boltdb" - "github.com/spiral/roadrunner/v2/plugins/logger" - rpcPlugin "github.com/spiral/roadrunner/v2/plugins/rpc" - "github.com/spiral/roadrunner/v2/plugins/server" - "github.com/stretchr/testify/assert" -) - -func TestBoltDb(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-init.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &boltdb.Plugin{}, - &rpcPlugin.Plugin{}, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second * 1) - t.Run("testBoltDbRPCMethods", testRPCMethods) - stopCh <- struct{}{} - wg.Wait() - - _ = os.Remove("rr") -} - -func testRPCMethods(t *testing.T) { - conn, err := net.Dial("tcp", "127.0.0.1:6001") - assert.NoError(t, err) - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - - var setRes bool - items := make([]kv.Item, 0, 5) - items = append(items, kv.Item{ - Key: "a", - Value: "aa", - }) - items = append(items, kv.Item{ - Key: "b", - Value: "bb", - }) - // add 5 second ttl - tt := time.Now().Add(time.Second * 5).Format(time.RFC3339) - items = append(items, kv.Item{ - Key: "c", - Value: "cc", - TTL: tt, - }) - - items = append(items, kv.Item{ - Key: "d", - Value: "dd", - }) - - items = append(items, kv.Item{ - Key: "e", - Value: "ee", - }) - - // Register 3 keys with values - err = client.Call("boltdb.Set", items, &setRes) - assert.NoError(t, err) - assert.True(t, setRes) - - ret := make(map[string]bool) - keys := []string{"a", "b", "c"} - err = client.Call("boltdb.Has", keys, &ret) - assert.NoError(t, err) - assert.Len(t, ret, 3) // should be 3 - - // key "c" should be deleted - time.Sleep(time.Second * 7) - - ret = make(map[string]bool) - err = client.Call("boltdb.Has", keys, &ret) - assert.NoError(t, err) - assert.Len(t, ret, 2) // should be 2 - - mGet := make(map[string]interface{}) - keys = []string{"a", "b", "c"} - err = client.Call("boltdb.MGet", keys, &mGet) - assert.NoError(t, err) - assert.Len(t, mGet, 2) // c is expired - assert.Equal(t, string("aa"), mGet["a"].(string)) - assert.Equal(t, string("bb"), mGet["b"].(string)) - - mExpKeys := make([]kv.Item, 0, 2) - tt2 := time.Now().Add(time.Second * 10).Format(time.RFC3339) - mExpKeys = append(mExpKeys, kv.Item{Key: "a", TTL: tt2}) - mExpKeys = append(mExpKeys, kv.Item{Key: "b", TTL: tt2}) - mExpKeys = append(mExpKeys, kv.Item{Key: "d", TTL: tt2}) - - // MEXPIRE - var mExpRes bool - err = client.Call("boltdb.MExpire", mExpKeys, &mExpRes) - assert.NoError(t, err) - assert.True(t, mExpRes) - - // TTL - keys = []string{"a", "b", "d"} - ttlRes := make(map[string]interface{}) - err = client.Call("boltdb.TTL", keys, &ttlRes) - assert.NoError(t, err) - assert.Len(t, ttlRes, 3) - - // HAS AFTER TTL - time.Sleep(time.Second * 15) - ret = make(map[string]bool) - keys = []string{"a", "b", "d"} - err = client.Call("boltdb.Has", keys, &ret) - assert.NoError(t, err) - assert.Len(t, ret, 0) - - // DELETE - keys = []string{"e"} - var delRet bool - err = client.Call("boltdb.Delete", keys, &delRet) - assert.NoError(t, err) - assert.True(t, delRet) - - // HAS AFTER DELETE - ret = make(map[string]bool) - keys = []string{"e"} - err = client.Call("boltdb.Has", keys, &ret) - assert.NoError(t, err) - assert.Len(t, ret, 0) -} diff --git a/tests/plugins/kv/memcached/configs/.rr-init.yaml b/tests/plugins/kv/memcached/configs/.rr-init.yaml deleted file mode 100644 index fbca3250..00000000 --- a/tests/plugins/kv/memcached/configs/.rr-init.yaml +++ /dev/null @@ -1,42 +0,0 @@ -rpc: - listen: tcp://127.0.0.1:6001 - -server: - command: "php ../../../psr-worker-bench.php" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -logs: - mode: development - level: error - -http: - address: 127.0.0.1:44933 - max_request_size: 1024 - middleware: ["gzip", "headers"] - uploads: - forbid: [".php", ".exe", ".bat"] - trusted_subnets: - [ - "10.0.0.0/8", - "127.0.0.0/8", - "172.16.0.0/12", - "192.168.0.0/16", - "::1/128", - "fc00::/7", - "fe80::/10", - ] - pool: - num_workers: 6 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s - -# boltdb simple driver -memcached: - addr: - - "localhost:11211"
\ No newline at end of file diff --git a/tests/plugins/kv/memcached/plugin_test.go b/tests/plugins/kv/memcached/plugin_test.go deleted file mode 100644 index 3878ef67..00000000 --- a/tests/plugins/kv/memcached/plugin_test.go +++ /dev/null @@ -1,195 +0,0 @@ -package memcached_test - -import ( - "net" - "net/rpc" - "os" - "os/signal" - "sync" - "syscall" - "testing" - "time" - - endure "github.com/spiral/endure/pkg/container" - goridgeRpc "github.com/spiral/goridge/v3/pkg/rpc" - "github.com/spiral/roadrunner/v2/plugins/config" - httpPlugin "github.com/spiral/roadrunner/v2/plugins/http" - "github.com/spiral/roadrunner/v2/plugins/kv" - "github.com/spiral/roadrunner/v2/plugins/kv/memcached" - "github.com/spiral/roadrunner/v2/plugins/logger" - rpcPlugin "github.com/spiral/roadrunner/v2/plugins/rpc" - "github.com/spiral/roadrunner/v2/plugins/server" - "github.com/stretchr/testify/assert" -) - -func TestMemcache(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-init.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &memcached.Plugin{}, - &rpcPlugin.Plugin{}, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second * 1) - t.Run("testMemcachedRPCMethods", testRPCMethods) - stopCh <- struct{}{} - wg.Wait() - - _ = os.Remove("rr") -} - -func testRPCMethods(t *testing.T) { - conn, err := net.Dial("tcp", "127.0.0.1:6001") - assert.NoError(t, err) - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - - var setRes bool - items := make([]kv.Item, 0, 5) - items = append(items, kv.Item{ - Key: "a", - Value: "aa", - }) - items = append(items, kv.Item{ - Key: "b", - Value: "bb", - }) - // add 5 second ttl - tt := time.Now().Add(time.Second * 5).Format(time.RFC3339) - items = append(items, kv.Item{ - Key: "c", - Value: "cc", - TTL: tt, - }) - - items = append(items, kv.Item{ - Key: "d", - Value: "dd", - }) - - items = append(items, kv.Item{ - Key: "e", - Value: "ee", - }) - - // Register 3 keys with values - err = client.Call("memcached.Set", items, &setRes) - assert.NoError(t, err) - assert.True(t, setRes) - - ret := make(map[string]bool) - keys := []string{"a", "b", "c"} - err = client.Call("memcached.Has", keys, &ret) - assert.NoError(t, err) - assert.Len(t, ret, 3) // should be 3 - - // key "c" should be deleted - time.Sleep(time.Second * 7) - - ret = make(map[string]bool) - err = client.Call("memcached.Has", keys, &ret) - assert.NoError(t, err) - assert.Len(t, ret, 2) // should be 2 - - mGet := make(map[string]interface{}) - keys = []string{"a", "b", "c"} - err = client.Call("memcached.MGet", keys, &mGet) - assert.NoError(t, err) - assert.Len(t, mGet, 2) // c is expired - assert.Equal(t, string("aa"), string(mGet["a"].([]byte))) - assert.Equal(t, string("bb"), string(mGet["b"].([]byte))) - - mExpKeys := make([]kv.Item, 0, 2) - tt2 := time.Now().Add(time.Second * 10).Format(time.RFC3339) - mExpKeys = append(mExpKeys, kv.Item{Key: "a", TTL: tt2}) - mExpKeys = append(mExpKeys, kv.Item{Key: "b", TTL: tt2}) - mExpKeys = append(mExpKeys, kv.Item{Key: "d", TTL: tt2}) - - // MEXPIRE - var mExpRes bool - err = client.Call("memcached.MExpire", mExpKeys, &mExpRes) - assert.NoError(t, err) - assert.True(t, mExpRes) - - // TTL call is not supported for the memcached driver - keys = []string{"a", "b", "d"} - ttlRes := make(map[string]interface{}) - err = client.Call("memcached.TTL", keys, &ttlRes) - assert.Error(t, err) - assert.Len(t, ttlRes, 0) - - // HAS AFTER TTL - time.Sleep(time.Second * 15) - ret = make(map[string]bool) - keys = []string{"a", "b", "d"} - err = client.Call("memcached.Has", keys, &ret) - assert.NoError(t, err) - assert.Len(t, ret, 0) - - // DELETE - keys = []string{"e"} - var delRet bool - err = client.Call("memcached.Delete", keys, &delRet) - assert.NoError(t, err) - assert.True(t, delRet) - - // HAS AFTER DELETE - ret = make(map[string]bool) - keys = []string{"e"} - err = client.Call("memcached.Has", keys, &ret) - assert.NoError(t, err) - assert.Len(t, ret, 0) -} diff --git a/tests/plugins/kv/memory/configs/.rr-init.yaml b/tests/plugins/kv/memory/configs/.rr-init.yaml deleted file mode 100644 index 8780a622..00000000 --- a/tests/plugins/kv/memory/configs/.rr-init.yaml +++ /dev/null @@ -1,42 +0,0 @@ -rpc: - listen: tcp://127.0.0.1:6001 - -server: - command: "php ../../../psr-worker-bench.php" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -logs: - mode: development - level: error - -http: - address: 127.0.0.1:44933 - max_request_size: 1024 - middleware: ["gzip", "headers"] - uploads: - forbid: [".php", ".exe", ".bat"] - trusted_subnets: - [ - "10.0.0.0/8", - "127.0.0.0/8", - "172.16.0.0/12", - "192.168.0.0/16", - "::1/128", - "fc00::/7", - "fe80::/10", - ] - pool: - num_workers: 6 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s - -# in memory KV driver -memory: - # keys ttl check interval - interval: 1 diff --git a/tests/plugins/kv/memory/plugin_test.go b/tests/plugins/kv/memory/plugin_test.go deleted file mode 100644 index 528403d0..00000000 --- a/tests/plugins/kv/memory/plugin_test.go +++ /dev/null @@ -1,195 +0,0 @@ -package memory_test - -import ( - "net" - "net/rpc" - "os" - "os/signal" - "sync" - "syscall" - "testing" - "time" - - endure "github.com/spiral/endure/pkg/container" - goridgeRpc "github.com/spiral/goridge/v3/pkg/rpc" - "github.com/spiral/roadrunner/v2/plugins/config" - httpPlugin "github.com/spiral/roadrunner/v2/plugins/http" - "github.com/spiral/roadrunner/v2/plugins/kv" - "github.com/spiral/roadrunner/v2/plugins/kv/memory" - "github.com/spiral/roadrunner/v2/plugins/logger" - rpcPlugin "github.com/spiral/roadrunner/v2/plugins/rpc" - "github.com/spiral/roadrunner/v2/plugins/server" - "github.com/stretchr/testify/assert" -) - -func TestInMemory(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-init.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &memory.Plugin{}, - &rpcPlugin.Plugin{}, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second * 1) - t.Run("testInMemoryRPCMethods", testRPCMethods) - stopCh <- struct{}{} - wg.Wait() - - _ = os.Remove("rr") -} - -func testRPCMethods(t *testing.T) { - conn, err := net.Dial("tcp", "127.0.0.1:6001") - assert.NoError(t, err) - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - - var setRes bool - items := make([]kv.Item, 0, 5) - items = append(items, kv.Item{ - Key: "a", - Value: "aa", - }) - items = append(items, kv.Item{ - Key: "b", - Value: "bb", - }) - // add 5 second ttl - tt := time.Now().Add(time.Second * 5).Format(time.RFC3339) - items = append(items, kv.Item{ - Key: "c", - Value: "cc", - TTL: tt, - }) - - items = append(items, kv.Item{ - Key: "d", - Value: "dd", - }) - - items = append(items, kv.Item{ - Key: "e", - Value: "ee", - }) - - // Register 3 keys with values - err = client.Call("memory.Set", items, &setRes) - assert.NoError(t, err) - assert.True(t, setRes) - - ret := make(map[string]bool) - keys := []string{"a", "b", "c"} - err = client.Call("memory.Has", keys, &ret) - assert.NoError(t, err) - assert.Len(t, ret, 3) // should be 3 - - // key "c" should be deleted - time.Sleep(time.Second * 7) - - ret = make(map[string]bool) - err = client.Call("memory.Has", keys, &ret) - assert.NoError(t, err) - assert.Len(t, ret, 2) // should be 2 - - mGet := make(map[string]interface{}) - keys = []string{"a", "b", "c"} - err = client.Call("memory.MGet", keys, &mGet) - assert.NoError(t, err) - assert.Len(t, mGet, 2) // c is expired - assert.Equal(t, string("aa"), mGet["a"].(string)) - assert.Equal(t, string("bb"), mGet["b"].(string)) - - mExpKeys := make([]kv.Item, 0, 2) - tt2 := time.Now().Add(time.Second * 10).Format(time.RFC3339) - mExpKeys = append(mExpKeys, kv.Item{Key: "a", TTL: tt2}) - mExpKeys = append(mExpKeys, kv.Item{Key: "b", TTL: tt2}) - mExpKeys = append(mExpKeys, kv.Item{Key: "d", TTL: tt2}) - - // MEXPIRE - var mExpRes bool - err = client.Call("memory.MExpire", mExpKeys, &mExpRes) - assert.NoError(t, err) - assert.True(t, mExpRes) - - // TTL - keys = []string{"a", "b", "d"} - ttlRes := make(map[string]interface{}) - err = client.Call("memory.TTL", keys, &ttlRes) - assert.NoError(t, err) - assert.Len(t, ttlRes, 3) - - // HAS AFTER TTL - time.Sleep(time.Second * 15) - ret = make(map[string]bool) - keys = []string{"a", "b", "d"} - err = client.Call("memory.Has", keys, &ret) - assert.NoError(t, err) - assert.Len(t, ret, 0) - - // DELETE - keys = []string{"e"} - var delRet bool - err = client.Call("memory.Delete", keys, &delRet) - assert.NoError(t, err) - assert.True(t, delRet) - - // HAS AFTER DELETE - ret = make(map[string]bool) - keys = []string{"e"} - err = client.Call("memory.Has", keys, &ret) - assert.NoError(t, err) - assert.Len(t, ret, 0) -} diff --git a/tests/plugins/logger/.rr.yaml b/tests/plugins/logger/.rr.yaml deleted file mode 100644 index 5ab359d3..00000000 --- a/tests/plugins/logger/.rr.yaml +++ /dev/null @@ -1,3 +0,0 @@ -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/logger/logger_test.go b/tests/plugins/logger/logger_test.go deleted file mode 100644 index 63f233ee..00000000 --- a/tests/plugins/logger/logger_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package logger - -import ( - "os" - "os/signal" - "sync" - "testing" - - endure "github.com/spiral/endure/pkg/container" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/logger" - "github.com/stretchr/testify/assert" -) - -func TestLogger(t *testing.T) { - container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.ErrorLevel)) - if err != nil { - t.Fatal(err) - } - // config plugin - vp := &config.Viper{} - vp.Path = ".rr.yaml" - vp.Prefix = "rr" - err = container.Register(vp) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&Plugin{}) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&logger.ZapLogger{}) - if err != nil { - t.Fatal(err) - } - - err = container.Init() - if err != nil { - t.Fatal(err) - } - - errCh, err := container.Serve() - if err != nil { - t.Fatal(err) - } - - // stop by CTRL+C - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - - stopCh := make(chan struct{}, 1) - - wg := &sync.WaitGroup{} - wg.Add(1) - - go func() { - defer wg.Done() - for { - select { - case e := <-errCh: - assert.NoError(t, e.Error) - assert.NoError(t, container.Stop()) - return - case <-c: - err = container.Stop() - assert.NoError(t, err) - return - case <-stopCh: - assert.NoError(t, container.Stop()) - return - } - } - }() - - stopCh <- struct{}{} - wg.Wait() -} diff --git a/tests/plugins/logger/plugin.go b/tests/plugins/logger/plugin.go deleted file mode 100644 index 9ddf9ec9..00000000 --- a/tests/plugins/logger/plugin.go +++ /dev/null @@ -1,40 +0,0 @@ -package logger - -import ( - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/logger" -) - -type Plugin struct { - config config.Configurer - log logger.Logger -} - -func (p1 *Plugin) Init(cfg config.Configurer, log logger.Logger) error { - p1.config = cfg - p1.log = log - return nil -} - -func (p1 *Plugin) Serve() chan error { - errCh := make(chan error, 1) - p1.log.Error("error", "test", errors.E(errors.Str("test"))) - p1.log.Info("error", "test", errors.E(errors.Str("test"))) - p1.log.Debug("error", "test", errors.E(errors.Str("test"))) - p1.log.Warn("error", "test", errors.E(errors.Str("test"))) - - p1.log.Error("error", "test") - p1.log.Info("error", "test") - p1.log.Debug("error", "test") - p1.log.Warn("error", "test") - return errCh -} - -func (p1 *Plugin) Stop() error { - return nil -} - -func (p1 *Plugin) Name() string { - return "logger_plugin" -} diff --git a/tests/plugins/metrics/.rr-test.yaml b/tests/plugins/metrics/.rr-test.yaml deleted file mode 100644 index bc68b90f..00000000 --- a/tests/plugins/metrics/.rr-test.yaml +++ /dev/null @@ -1,15 +0,0 @@ -rpc: - listen: tcp://127.0.0.1:6001 - -metrics: - # prometheus client address (path /metrics added automatically) - address: localhost:2112 - collect: - app_metric: - type: histogram - help: "Custom application metric" - labels: [ "type" ] - buckets: [ 0.1, 0.2, 0.3, 1.0 ] -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/metrics/docker-compose.yml b/tests/plugins/metrics/docker-compose.yml deleted file mode 100644 index 610633b4..00000000 --- a/tests/plugins/metrics/docker-compose.yml +++ /dev/null @@ -1,7 +0,0 @@ -version: '3.7' - -services: - prometheus: - image: prom/prometheus - ports: - - 9090:9090 diff --git a/tests/plugins/metrics/metrics_test.go b/tests/plugins/metrics/metrics_test.go deleted file mode 100644 index b5a4fd4f..00000000 --- a/tests/plugins/metrics/metrics_test.go +++ /dev/null @@ -1,811 +0,0 @@ -package metrics - -import ( - "io/ioutil" - "net" - "net/http" - "net/rpc" - "os" - "os/signal" - "syscall" - "testing" - "time" - - "github.com/golang/mock/gomock" - endure "github.com/spiral/endure/pkg/container" - goridgeRpc "github.com/spiral/goridge/v3/pkg/rpc" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/logger" - "github.com/spiral/roadrunner/v2/plugins/metrics" - rpcPlugin "github.com/spiral/roadrunner/v2/plugins/rpc" - "github.com/spiral/roadrunner/v2/tests/mocks" - "github.com/stretchr/testify/assert" -) - -const dialAddr = "127.0.0.1:6001" -const dialNetwork = "tcp" -const getAddr = "http://localhost:2112/metrics" - -// get request and return body -func get() (string, error) { - r, err := http.Get(getAddr) - if err != nil { - return "", err - } - - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return "", err - } - - err = r.Body.Close() - if err != nil { - return "", err - } - // unsafe - return string(b), err -} - -func TestMetricsInit(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - if err != nil { - t.Fatal(err) - } - - cfg := &config.Viper{} - cfg.Prefix = "rr" - cfg.Path = ".rr-test.yaml" - - err = cont.RegisterAll( - cfg, - &metrics.Plugin{}, - &rpcPlugin.Plugin{}, - &logger.ZapLogger{}, - &Plugin1{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - tt := time.NewTimer(time.Second * 5) - defer tt.Stop() - - out, err := get() - assert.NoError(t, err) - - assert.Contains(t, out, "go_gc_duration_seconds") - - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-tt.C: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } -} - -func TestMetricsGaugeCollector(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - if err != nil { - t.Fatal(err) - } - - cfg := &config.Viper{} - cfg.Prefix = "rr" - cfg.Path = ".rr-test.yaml" - - err = cont.RegisterAll( - cfg, - &metrics.Plugin{}, - &rpcPlugin.Plugin{}, - &logger.ZapLogger{}, - &Plugin1{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - time.Sleep(time.Second) - tt := time.NewTimer(time.Second * 5) - defer tt.Stop() - - out, err := get() - assert.NoError(t, err) - assert.Contains(t, out, "my_gauge 100") - assert.Contains(t, out, "my_gauge2 100") - - out, err = get() - assert.NoError(t, err) - assert.Contains(t, out, "go_gc_duration_seconds") - - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-tt.C: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } -} - -func TestMetricsDifferentRPCCalls(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - if err != nil { - t.Fatal(err) - } - - cfg := &config.Viper{} - cfg.Prefix = "rr" - cfg.Path = ".rr-test.yaml" - - controller := gomock.NewController(t) - mockLogger := mocks.NewMockLogger(controller) - - mockLogger.EXPECT().Debug("worker destructed", "pid", gomock.Any()).AnyTimes() - mockLogger.EXPECT().Debug("worker constructed", "pid", gomock.Any()).AnyTimes() - mockLogger.EXPECT().Debug("Started RPC service", "address", "tcp://127.0.0.1:6001", "services", []string{"metrics"}).MinTimes(1) - - mockLogger.EXPECT().Info("adding metric", "name", "counter_CounterMetric", "value", gomock.Any(), "labels", []string{"type2", "section2"}).MinTimes(1) - mockLogger.EXPECT().Info("adding metric", "name", "histogram_registerHistogram", "value", gomock.Any(), "labels", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("adding metric", "name", "sub_gauge_subVector", "value", gomock.Any(), "labels", []string{"core", "first"}).MinTimes(1) - mockLogger.EXPECT().Info("adding metric", "name", "sub_gauge_subMetric", "value", gomock.Any(), "labels", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("adding metric", "name", "test_metrics_named_collector", "value", gomock.Any(), "labels", gomock.Any()).MinTimes(1) - - mockLogger.EXPECT().Info("metric successfully added", "name", "observe_observeMetricNotEnoughLabels", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("metric successfully added", "name", "observe_observeMetric", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("metric successfully added", "name", "counter_CounterMetric", "labels", []string{"type2", "section2"}, "value", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("metric successfully added", "name", "counter_CounterMetric", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("metric successfully added", "name", "histogram_registerHistogram", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("metric successfully added", "name", "sub_gauge_subVector", "labels", []string{"core", "first"}, "value", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("metric successfully added", "name", "sub_gauge_subVector", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("metric successfully added", "name", "sub_gauge_subMetric", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("metric successfully added", "name", "sub_gauge_subMetric", "labels", gomock.Any(), "value", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("metric successfully added", "name", "histogram_setOnHistogram", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("metric successfully added", "name", "gauge_setWithoutLabels", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("metric successfully added", "name", "gauge_missing_section_collector", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("metric successfully added", "name", "gauge_2_collector", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("metric successfully added", "name", "test_metrics_named_collector", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("metric successfully added", "name", "test_metrics_named_collector", "labels", gomock.Any(), "value", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("metric successfully added", "name", "user_gauge_collector", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - - mockLogger.EXPECT().Info("declaring new metric", "name", "observe_observeMetricNotEnoughLabels", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("declaring new metric", "name", "observe_observeMetric", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("declaring new metric", "name", "counter_CounterMetric", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("declaring new metric", "name", "histogram_registerHistogram", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("declaring new metric", "name", "sub_gauge_subVector", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("declaring new metric", "name", "sub_gauge_subMetric", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("declaring new metric", "name", "histogram_setOnHistogram", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("declaring new metric", "name", "gauge_setWithoutLabels", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("declaring new metric", "name", "gauge_missing_section_collector", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("declaring new metric", "name", "test_metrics_named_collector", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("declaring new metric", "name", "gauge_2_collector", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("declaring new metric", "name", "user_gauge_collector", "type", gomock.Any(), "namespace", gomock.Any()).MinTimes(1) - - mockLogger.EXPECT().Info("observing metric", "name", "observe_observeMetric", "value", gomock.Any(), "labels", []string{"test"}).MinTimes(1) - mockLogger.EXPECT().Info("observing metric", "name", "observe_observeMetric", "value", gomock.Any(), "labels", []string{"test", "test2"}).MinTimes(1) - mockLogger.EXPECT().Info("observing metric", "name", "gauge_setOnHistogram", "value", gomock.Any(), "labels", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("observing metric", "name", "gauge_setWithoutLabels", "value", gomock.Any(), "labels", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("observing metric", "name", "gauge_missing_section_collector", "value", gomock.Any(), "labels", []string{"missing"}).MinTimes(1) - mockLogger.EXPECT().Info("observing metric", "name", "user_gauge_collector", "value", gomock.Any(), "labels", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("observing metric", "name", "gauge_2_collector", "value", gomock.Any(), "labels", []string{"core", "first"}).MinTimes(1) - - mockLogger.EXPECT().Info("observe operation finished successfully", "name", "observe_observeMetric", "labels", []string{"test", "test2"}, "value", gomock.Any()).MinTimes(1) - - mockLogger.EXPECT().Info("set operation finished successfully", "name", "gauge_2_collector", "labels", []string{"core", "first"}, "value", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("set operation finished successfully", "name", "user_gauge_collector", "labels", gomock.Any(), "value", gomock.Any()).MinTimes(1) - - mockLogger.EXPECT().Info("subtracting value from metric", "name", "sub_gauge_subVector", "value", gomock.Any(), "labels", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("subtracting value from metric", "name", "sub_gauge_subMetric", "value", gomock.Any(), "labels", gomock.Any()).MinTimes(1) - - mockLogger.EXPECT().Info("subtracting operation finished successfully", "name", "sub_gauge_subVector", "labels", gomock.Any(), "value", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("subtracting operation finished successfully", "name", "sub_gauge_subMetric", "labels", gomock.Any(), "value", gomock.Any()).MinTimes(1) - - mockLogger.EXPECT().Error("failed to get metrics with label values", "collector", "gauge_missing_section_collector", "labels", []string{"missing"}).MinTimes(1) - mockLogger.EXPECT().Error("required labels for collector", "collector", "gauge_setWithoutLabels").MinTimes(1) - mockLogger.EXPECT().Error("failed to get metrics with label values", "collector", "observe_observeMetric", "labels", []string{"test"}).MinTimes(1) - - err = cont.RegisterAll( - cfg, - &metrics.Plugin{}, - &rpcPlugin.Plugin{}, - mockLogger, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - tt := time.NewTimer(time.Minute * 3) - defer tt.Stop() - - go func() { - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-tt.C: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - t.Run("DeclareMetric", declareMetricsTest) - genericOut, err := get() - assert.NoError(t, err) - assert.Contains(t, genericOut, "test_metrics_named_collector") - - t.Run("AddMetric", addMetricsTest) - genericOut, err = get() - assert.NoError(t, err) - assert.Contains(t, genericOut, "test_metrics_named_collector 10000") - - t.Run("SetMetric", setMetric) - genericOut, err = get() - assert.NoError(t, err) - assert.Contains(t, genericOut, "user_gauge_collector 100") - - t.Run("VectorMetric", vectorMetric) - genericOut, err = get() - assert.NoError(t, err) - assert.Contains(t, genericOut, "gauge_2_collector{section=\"first\",type=\"core\"} 100") - - t.Run("MissingSection", missingSection) - t.Run("SetWithoutLabels", setWithoutLabels) - t.Run("SetOnHistogram", setOnHistogram) - t.Run("MetricSub", subMetric) - genericOut, err = get() - assert.NoError(t, err) - assert.Contains(t, genericOut, "sub_gauge_subMetric 1") - - t.Run("SubVector", subVector) - genericOut, err = get() - assert.NoError(t, err) - assert.Contains(t, genericOut, "sub_gauge_subVector{section=\"first\",type=\"core\"} 1") - - t.Run("RegisterHistogram", registerHistogram) - - genericOut, err = get() - assert.NoError(t, err) - assert.Contains(t, genericOut, `TYPE histogram_registerHistogram`) - - // check buckets - assert.Contains(t, genericOut, `histogram_registerHistogram_bucket{le="0.1"} 0`) - assert.Contains(t, genericOut, `histogram_registerHistogram_bucket{le="0.2"} 0`) - assert.Contains(t, genericOut, `histogram_registerHistogram_bucket{le="0.5"} 0`) - assert.Contains(t, genericOut, `histogram_registerHistogram_bucket{le="+Inf"} 0`) - assert.Contains(t, genericOut, `histogram_registerHistogram_sum 0`) - assert.Contains(t, genericOut, `histogram_registerHistogram_count 0`) - - t.Run("CounterMetric", counterMetric) - genericOut, err = get() - assert.NoError(t, err) - assert.Contains(t, genericOut, "HELP default_default_counter_CounterMetric test_counter") - assert.Contains(t, genericOut, `default_default_counter_CounterMetric{section="section2",type="type2"}`) - - t.Run("ObserveMetric", observeMetric) - genericOut, err = get() - assert.NoError(t, err) - assert.Contains(t, genericOut, "observe_observeMetric") - - t.Run("ObserveMetricNotEnoughLabels", observeMetricNotEnoughLabels) - - close(sig) -} - -func observeMetricNotEnoughLabels(t *testing.T) { - conn, err := net.Dial(dialNetwork, dialAddr) - assert.NoError(t, err) - defer func() { - _ = conn.Close() - }() - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - var ret bool - - nc := metrics.NamedCollector{ - Name: "observe_observeMetricNotEnoughLabels", - Collector: metrics.Collector{ - Namespace: "default", - Subsystem: "default", - Help: "test_observe", - Type: metrics.Histogram, - Labels: []string{"type", "section"}, - }, - } - - err = client.Call("metrics.Declare", nc, &ret) - assert.NoError(t, err) - assert.True(t, ret) - ret = false - - assert.Error(t, client.Call("metrics.Observe", metrics.Metric{ - Name: "observe_observeMetric", - Value: 100.0, - Labels: []string{"test"}, - }, &ret)) - assert.False(t, ret) -} - -func observeMetric(t *testing.T) { - conn, err := net.Dial(dialNetwork, dialAddr) - assert.NoError(t, err) - defer func() { - _ = conn.Close() - }() - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - var ret bool - - nc := metrics.NamedCollector{ - Name: "observe_observeMetric", - Collector: metrics.Collector{ - Namespace: "default", - Subsystem: "default", - Help: "test_observe", - Type: metrics.Histogram, - Labels: []string{"type", "section"}, - }, - } - - err = client.Call("metrics.Declare", nc, &ret) - assert.NoError(t, err) - assert.True(t, ret) - ret = false - - assert.NoError(t, client.Call("metrics.Observe", metrics.Metric{ - Name: "observe_observeMetric", - Value: 100.0, - Labels: []string{"test", "test2"}, - }, &ret)) - assert.True(t, ret) -} - -func counterMetric(t *testing.T) { - conn, err := net.Dial(dialNetwork, dialAddr) - assert.NoError(t, err) - defer func() { - _ = conn.Close() - }() - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - var ret bool - - nc := metrics.NamedCollector{ - Name: "counter_CounterMetric", - Collector: metrics.Collector{ - Namespace: "default", - Subsystem: "default", - Help: "test_counter", - Type: metrics.Counter, - Labels: []string{"type", "section"}, - }, - } - - err = client.Call("metrics.Declare", nc, &ret) - assert.NoError(t, err) - assert.True(t, ret) - - ret = false - - assert.NoError(t, client.Call("metrics.Add", metrics.Metric{ - Name: "counter_CounterMetric", - Value: 100.0, - Labels: []string{"type2", "section2"}, - }, &ret)) - assert.True(t, ret) -} - -func registerHistogram(t *testing.T) { - conn, err := net.Dial(dialNetwork, dialAddr) - assert.NoError(t, err) - defer func() { - _ = conn.Close() - }() - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - var ret bool - - nc := metrics.NamedCollector{ - Name: "histogram_registerHistogram", - Collector: metrics.Collector{ - Help: "test_histogram", - Type: metrics.Histogram, - Buckets: []float64{0.1, 0.2, 0.5}, - }, - } - - err = client.Call("metrics.Declare", nc, &ret) - assert.NoError(t, err) - assert.True(t, ret) - - ret = false - - m := metrics.Metric{ - Name: "histogram_registerHistogram", - Value: 10000, - Labels: nil, - } - - err = client.Call("metrics.Add", m, &ret) - assert.Error(t, err) - assert.False(t, ret) -} - -func subVector(t *testing.T) { - conn, err := net.Dial(dialNetwork, dialAddr) - assert.NoError(t, err) - defer func() { - _ = conn.Close() - }() - - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - var ret bool - - nc := metrics.NamedCollector{ - Name: "sub_gauge_subVector", - Collector: metrics.Collector{ - Namespace: "default", - Subsystem: "default", - Type: metrics.Gauge, - Labels: []string{"type", "section"}, - }, - } - - err = client.Call("metrics.Declare", nc, &ret) - assert.NoError(t, err) - assert.True(t, ret) - ret = false - - m := metrics.Metric{ - Name: "sub_gauge_subVector", - Value: 100000, - Labels: []string{"core", "first"}, - } - - err = client.Call("metrics.Add", m, &ret) - assert.NoError(t, err) - assert.True(t, ret) - ret = false - - m = metrics.Metric{ - Name: "sub_gauge_subVector", - Value: 99999, - Labels: []string{"core", "first"}, - } - - err = client.Call("metrics.Sub", m, &ret) - assert.NoError(t, err) - assert.True(t, ret) -} - -func subMetric(t *testing.T) { - conn, err := net.Dial(dialNetwork, dialAddr) - assert.NoError(t, err) - defer func() { - _ = conn.Close() - }() - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - var ret bool - - nc := metrics.NamedCollector{ - Name: "sub_gauge_subMetric", - Collector: metrics.Collector{ - Namespace: "default", - Subsystem: "default", - Type: metrics.Gauge, - }, - } - - err = client.Call("metrics.Declare", nc, &ret) - assert.NoError(t, err) - assert.True(t, ret) - ret = false - - m := metrics.Metric{ - Name: "sub_gauge_subMetric", - Value: 100000, - } - - err = client.Call("metrics.Add", m, &ret) - assert.NoError(t, err) - assert.True(t, ret) - ret = false - - m = metrics.Metric{ - Name: "sub_gauge_subMetric", - Value: 99999, - } - - err = client.Call("metrics.Sub", m, &ret) - assert.NoError(t, err) - assert.True(t, ret) -} - -func setOnHistogram(t *testing.T) { - conn, err := net.Dial(dialNetwork, dialAddr) - assert.NoError(t, err) - defer func() { - _ = conn.Close() - }() - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - var ret bool - - nc := metrics.NamedCollector{ - Name: "histogram_setOnHistogram", - Collector: metrics.Collector{ - Namespace: "default", - Subsystem: "default", - Type: metrics.Histogram, - Labels: []string{"type", "section"}, - }, - } - - err = client.Call("metrics.Declare", nc, &ret) - assert.NoError(t, err) - assert.True(t, ret) - - ret = false - - m := metrics.Metric{ - Name: "gauge_setOnHistogram", - Value: 100.0, - } - - err = client.Call("metrics.Set", m, &ret) // expected 2 label values but got 1 in []string{"missing"} - assert.Error(t, err) - assert.False(t, ret) -} - -func setWithoutLabels(t *testing.T) { - conn, err := net.Dial(dialNetwork, dialAddr) - assert.NoError(t, err) - defer func() { - _ = conn.Close() - }() - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - var ret bool - - nc := metrics.NamedCollector{ - Name: "gauge_setWithoutLabels", - Collector: metrics.Collector{ - Namespace: "default", - Subsystem: "default", - Type: metrics.Gauge, - Labels: []string{"type", "section"}, - }, - } - - err = client.Call("metrics.Declare", nc, &ret) - assert.NoError(t, err) - assert.True(t, ret) - - ret = false - - m := metrics.Metric{ - Name: "gauge_setWithoutLabels", - Value: 100.0, - } - - err = client.Call("metrics.Set", m, &ret) // expected 2 label values but got 1 in []string{"missing"} - assert.Error(t, err) - assert.False(t, ret) -} - -func missingSection(t *testing.T) { - conn, err := net.Dial(dialNetwork, dialAddr) - assert.NoError(t, err) - defer func() { - _ = conn.Close() - }() - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - var ret bool - - nc := metrics.NamedCollector{ - Name: "gauge_missing_section_collector", - Collector: metrics.Collector{ - Namespace: "default", - Subsystem: "default", - Type: metrics.Gauge, - Labels: []string{"type", "section"}, - }, - } - - err = client.Call("metrics.Declare", nc, &ret) - assert.NoError(t, err) - assert.True(t, ret) - - ret = false - - m := metrics.Metric{ - Name: "gauge_missing_section_collector", - Value: 100.0, - Labels: []string{"missing"}, - } - - err = client.Call("metrics.Set", m, &ret) // expected 2 label values but got 1 in []string{"missing"} - assert.Error(t, err) - assert.False(t, ret) -} - -func vectorMetric(t *testing.T) { - conn, err := net.Dial(dialNetwork, dialAddr) - assert.NoError(t, err) - defer func() { - _ = conn.Close() - }() - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - var ret bool - - nc := metrics.NamedCollector{ - Name: "gauge_2_collector", - Collector: metrics.Collector{ - Namespace: "default", - Subsystem: "default", - Type: metrics.Gauge, - Labels: []string{"type", "section"}, - }, - } - - err = client.Call("metrics.Declare", nc, &ret) - assert.NoError(t, err) - assert.True(t, ret) - - ret = false - - m := metrics.Metric{ - Name: "gauge_2_collector", - Value: 100.0, - Labels: []string{"core", "first"}, - } - - err = client.Call("metrics.Set", m, &ret) - assert.NoError(t, err) - assert.True(t, ret) -} - -func setMetric(t *testing.T) { - conn, err := net.Dial(dialNetwork, dialAddr) - assert.NoError(t, err) - defer func() { - _ = conn.Close() - }() - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - var ret bool - - nc := metrics.NamedCollector{ - Name: "user_gauge_collector", - Collector: metrics.Collector{ - Namespace: "default", - Subsystem: "default", - Type: metrics.Gauge, - }, - } - - err = client.Call("metrics.Declare", nc, &ret) - assert.NoError(t, err) - assert.True(t, ret) - ret = false - - m := metrics.Metric{ - Name: "user_gauge_collector", - Value: 100.0, - } - - err = client.Call("metrics.Set", m, &ret) - assert.NoError(t, err) - assert.True(t, ret) -} - -func addMetricsTest(t *testing.T) { - conn, err := net.Dial(dialNetwork, dialAddr) - assert.NoError(t, err) - defer func() { - _ = conn.Close() - }() - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - var ret bool - - m := metrics.Metric{ - Name: "test_metrics_named_collector", - Value: 10000, - Labels: nil, - } - - err = client.Call("metrics.Add", m, &ret) - assert.NoError(t, err) - assert.True(t, ret) -} - -func declareMetricsTest(t *testing.T) { - conn, err := net.Dial(dialNetwork, dialAddr) - assert.NoError(t, err) - defer func() { - _ = conn.Close() - }() - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - var ret bool - - nc := metrics.NamedCollector{ - Name: "test_metrics_named_collector", - Collector: metrics.Collector{ - Namespace: "default", - Subsystem: "default", - Type: metrics.Counter, - Help: "NO HELP!", - Labels: nil, - Buckets: nil, - }, - } - - err = client.Call("metrics.Declare", nc, &ret) - assert.NoError(t, err) - assert.True(t, ret) -} diff --git a/tests/plugins/metrics/plugin1.go b/tests/plugins/metrics/plugin1.go deleted file mode 100644 index ae024a8a..00000000 --- a/tests/plugins/metrics/plugin1.go +++ /dev/null @@ -1,46 +0,0 @@ -package metrics - -import ( - "github.com/prometheus/client_golang/prometheus" - "github.com/spiral/roadrunner/v2/plugins/config" -) - -// Gauge ////////////// -type Plugin1 struct { - config config.Configurer -} - -func (p1 *Plugin1) Init(cfg config.Configurer) error { - p1.config = cfg - return nil -} - -func (p1 *Plugin1) Serve() chan error { - errCh := make(chan error, 1) - return errCh -} - -func (p1 *Plugin1) Stop() error { - return nil -} - -func (p1 *Plugin1) Name() string { - return "metrics_test.plugin1" -} - -func (p1 *Plugin1) MetricsCollector() []prometheus.Collector { - collector := prometheus.NewGauge(prometheus.GaugeOpts{ - Name: "my_gauge", - Help: "My gauge value", - }) - - collector.Set(100) - - collector2 := prometheus.NewGauge(prometheus.GaugeOpts{ - Name: "my_gauge2", - Help: "My gauge2 value", - }) - - collector2.Set(100) - return []prometheus.Collector{collector, collector2} -} diff --git a/tests/plugins/redis/plugin1.go b/tests/plugins/redis/plugin1.go deleted file mode 100644 index e50213e5..00000000 --- a/tests/plugins/redis/plugin1.go +++ /dev/null @@ -1,43 +0,0 @@ -package redis - -import ( - "context" - "time" - - "github.com/go-redis/redis/v8" - "github.com/spiral/errors" - redisPlugin "github.com/spiral/roadrunner/v2/plugins/redis" -) - -type Plugin1 struct { - redisClient redis.UniversalClient -} - -func (p *Plugin1) Init(redis redisPlugin.Redis) error { - p.redisClient = redis.GetClient() - return nil -} - -func (p *Plugin1) Serve() chan error { - const op = errors.Op("plugin1 serve") - errCh := make(chan error, 1) - p.redisClient.Set(context.Background(), "foo", "bar", time.Minute) - - stringCmd := p.redisClient.Get(context.Background(), "foo") - data, err := stringCmd.Result() - if err != nil { - errCh <- errors.E(op, err) - return errCh - } - - if data != "bar" { - errCh <- errors.E(op, errors.Str("no such key")) - return errCh - } - - return errCh -} - -func (p *Plugin1) Stop() error { - return nil -} diff --git a/tests/plugins/redis/redis_plugin_test.go b/tests/plugins/redis/redis_plugin_test.go deleted file mode 100644 index 96a191a1..00000000 --- a/tests/plugins/redis/redis_plugin_test.go +++ /dev/null @@ -1,120 +0,0 @@ -package redis - -import ( - "fmt" - "os" - "os/signal" - "sync" - "syscall" - "testing" - - "github.com/alicebob/miniredis/v2" - "github.com/golang/mock/gomock" - endure "github.com/spiral/endure/pkg/container" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/redis" - "github.com/spiral/roadrunner/v2/tests/mocks" - "github.com/stretchr/testify/assert" -) - -func redisConfig(port string) string { - cfg := ` -redis: - addrs: - - 'localhost:%s' - master_name: '' - username: '' - password: '' - db: 0 - sentinel_password: '' - route_by_latency: false - route_randomly: false - dial_timeout: 0 - max_retries: 1 - min_retry_backoff: 0 - max_retry_backoff: 0 - pool_size: 0 - min_idle_conns: 0 - max_conn_age: 0 - read_timeout: 0 - write_timeout: 0 - pool_timeout: 0 - idle_timeout: 0 - idle_check_freq: 0 - read_only: false -` - return fmt.Sprintf(cfg, port) -} - -func TestRedisInit(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - if err != nil { - t.Fatal(err) - } - - s, err := miniredis.Run() - assert.NoError(t, err) - - c := redisConfig(s.Port()) - - cfg := &config.Viper{} - cfg.Type = "yaml" - cfg.ReadInCfg = []byte(c) - - controller := gomock.NewController(t) - mockLogger := mocks.NewMockLogger(controller) - - err = cont.RegisterAll( - cfg, - mockLogger, - &redis.Plugin{}, - &Plugin1{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - stopCh <- struct{}{} - wg.Wait() -} diff --git a/tests/plugins/reload/config_test.go b/tests/plugins/reload/config_test.go deleted file mode 100644 index 72c11070..00000000 --- a/tests/plugins/reload/config_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package reload - -import ( - "testing" - "time" - - "github.com/spiral/roadrunner/v2/plugins/reload" - "github.com/stretchr/testify/assert" -) - -func Test_Config_Valid(t *testing.T) { - services := make(map[string]reload.ServiceConfig) - services["test"] = reload.ServiceConfig{ - Recursive: false, - Patterns: nil, - Dirs: nil, - Ignore: nil, - } - - cfg := &reload.Config{ - Interval: time.Second, - Patterns: nil, - Services: services, - } - assert.NoError(t, cfg.Valid()) -} - -func Test_Fake_ServiceConfig(t *testing.T) { - services := make(map[string]reload.ServiceConfig) - cfg := &reload.Config{ - Interval: time.Microsecond, - Patterns: nil, - Services: services, - } - assert.Error(t, cfg.Valid()) -} - -func Test_Interval(t *testing.T) { - services := make(map[string]reload.ServiceConfig) - services["test"] = reload.ServiceConfig{ - Enabled: false, - Recursive: false, - Patterns: nil, - Dirs: nil, - Ignore: nil, - } - - cfg := &reload.Config{ - Interval: time.Millisecond, // should crash here - Patterns: nil, - Services: services, - } - assert.Error(t, cfg.Valid()) -} - -func Test_NoServiceConfig(t *testing.T) { - cfg := &reload.Config{ - Interval: time.Second, - Patterns: nil, - Services: nil, - } - assert.Error(t, cfg.Valid()) -} diff --git a/tests/plugins/reload/configs/.rr-reload-2.yaml b/tests/plugins/reload/configs/.rr-reload-2.yaml deleted file mode 100644 index 882ab628..00000000 --- a/tests/plugins/reload/configs/.rr-reload-2.yaml +++ /dev/null @@ -1,44 +0,0 @@ -server: - command: php ../../psr-worker-bench.php - user: '' - group: '' - env: - RR_HTTP: 'true' - relay: pipes - relay_timeout: 20s -http: - debug: true - address: '127.0.0.1:27388' - max_request_size: 1024 - middleware: - - '' - uploads: - forbid: - - .php - - .exe - - .bat - trusted_subnets: - - 10.0.0.0/8 - - 127.0.0.0/8 - - 172.16.0.0/12 - - 192.168.0.0/16 - - '::1/128' - - 'fc00::/7' - - 'fe80::/10' - pool: - num_workers: 2 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s -logs: - mode: development - level: error -reload: - interval: 2s - patterns: - - .txt - services: - http: - dirs: - - './unit_tests' - recursive: true diff --git a/tests/plugins/reload/configs/.rr-reload-3.yaml b/tests/plugins/reload/configs/.rr-reload-3.yaml deleted file mode 100644 index 4f964609..00000000 --- a/tests/plugins/reload/configs/.rr-reload-3.yaml +++ /dev/null @@ -1,46 +0,0 @@ -server: - command: php ../../psr-worker-bench.php - user: '' - group: '' - env: - RR_HTTP: 'true' - relay: pipes - relay_timeout: 20s -http: - debug: true - address: '127.0.0.1:37388' - max_request_size: 1024 - middleware: - - '' - uploads: - forbid: - - .php - - .exe - - .bat - trusted_subnets: - - 10.0.0.0/8 - - 127.0.0.0/8 - - 172.16.0.0/12 - - 192.168.0.0/16 - - '::1/128' - - 'fc00::/7' - - 'fe80::/10' - pool: - num_workers: 2 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s -logs: - mode: development - level: error -reload: - interval: 2s - patterns: - - .txt - services: - http: - dirs: - - './unit_tests' - - './unit_tests_copied' - - './dir1' - recursive: true diff --git a/tests/plugins/reload/configs/.rr-reload-4.yaml b/tests/plugins/reload/configs/.rr-reload-4.yaml deleted file mode 100644 index 76966e49..00000000 --- a/tests/plugins/reload/configs/.rr-reload-4.yaml +++ /dev/null @@ -1,46 +0,0 @@ -server: - command: php ../../psr-worker-bench.php - user: '' - group: '' - env: - RR_HTTP: 'true' - relay: pipes - relay_timeout: 20s -http: - debug: true - address: '127.0.0.1:22766' - max_request_size: 1024 - middleware: - - '' - uploads: - forbid: - - .php - - .exe - - .bat - trusted_subnets: - - 10.0.0.0/8 - - 127.0.0.0/8 - - 172.16.0.0/12 - - 192.168.0.0/16 - - '::1/128' - - 'fc00::/7' - - 'fe80::/10' - pool: - num_workers: 2 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s -logs: - mode: development - level: error -reload: - interval: 2s - patterns: - - .aaa - services: - http: - dirs: - - './unit_tests' - - './unit_tests_copied' - - './dir1' - recursive: false diff --git a/tests/plugins/reload/configs/.rr-reload.yaml b/tests/plugins/reload/configs/.rr-reload.yaml deleted file mode 100644 index 5dfb1171..00000000 --- a/tests/plugins/reload/configs/.rr-reload.yaml +++ /dev/null @@ -1,44 +0,0 @@ -server: - command: php ../../psr-worker-bench.php - user: '' - group: '' - env: - RR_HTTP: 'true' - relay: pipes - relay_timeout: 20s -http: - debug: true - address: '127.0.0.1:22388' - max_request_size: 1024 - middleware: - - '' - uploads: - forbid: - - .php - - .exe - - .bat - trusted_subnets: - - 10.0.0.0/8 - - 127.0.0.0/8 - - 172.16.0.0/12 - - 192.168.0.0/16 - - '::1/128' - - 'fc00::/7' - - 'fe80::/10' - pool: - num_workers: 2 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s -logs: - mode: development - level: error -reload: - interval: 1s - patterns: - - .txt - services: - http: - dirs: - - './unit_tests' - recursive: true diff --git a/tests/plugins/reload/reload_plugin_test.go b/tests/plugins/reload/reload_plugin_test.go deleted file mode 100644 index 9007541b..00000000 --- a/tests/plugins/reload/reload_plugin_test.go +++ /dev/null @@ -1,832 +0,0 @@ -package reload - -import ( - "io" - "io/ioutil" - "math/rand" - "os" - "os/signal" - "path/filepath" - "strconv" - "sync" - "syscall" - "testing" - "time" - - "github.com/golang/mock/gomock" - endure "github.com/spiral/endure/pkg/container" - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/plugins/config" - httpPlugin "github.com/spiral/roadrunner/v2/plugins/http" - "github.com/spiral/roadrunner/v2/plugins/reload" - "github.com/spiral/roadrunner/v2/plugins/resetter" - "github.com/spiral/roadrunner/v2/plugins/server" - "github.com/spiral/roadrunner/v2/tests/mocks" - "github.com/stretchr/testify/assert" -) - -const testDir string = "unit_tests" -const testCopyToDir string = "unit_tests_copied" -const dir1 string = "dir1" -const hugeNumberOfFiles uint = 500 - -func TestReloadInit(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-reload.yaml", - Prefix: "rr", - } - - // try to remove, skip error - assert.NoError(t, freeResources(testDir)) - err = os.Mkdir(testDir, 0755) - assert.NoError(t, err) - - controller := gomock.NewController(t) - mockLogger := mocks.NewMockLogger(controller) - - mockLogger.EXPECT().Debug("worker destructed", "pid", gomock.Any()).AnyTimes() - mockLogger.EXPECT().Debug("worker constructed", "pid", gomock.Any()).AnyTimes() - mockLogger.EXPECT().Debug("http handler response received", "elapsed", gomock.Any(), "remote address", "127.0.0.1").Times(1) - mockLogger.EXPECT().Debug("file was created", "path", gomock.Any(), "name", "file.txt", "size", gomock.Any()).Times(2) - mockLogger.EXPECT().Debug("file was added to watcher", "path", gomock.Any(), "name", "file.txt", "size", gomock.Any()).Times(2) - mockLogger.EXPECT().Info("HTTP plugin got restart request. Restarting...").Times(1) - mockLogger.EXPECT().Info("HTTP workers Pool successfully restarted").Times(1) - mockLogger.EXPECT().Info("HTTP listeners successfully re-added").Times(1) - mockLogger.EXPECT().Info("HTTP plugin successfully restarted").Times(1) - mockLogger.EXPECT().Error(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() // placeholder for the workerlogerror - - err = cont.RegisterAll( - cfg, - mockLogger, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &reload.Plugin{}, - &resetter.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - assert.NoError(t, err) - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - t.Run("ReloadTestInit", reloadTestInit) - - stopCh <- struct{}{} - wg.Wait() - assert.NoError(t, freeResources(testDir)) -} - -func reloadTestInit(t *testing.T) { - err := ioutil.WriteFile(filepath.Join(testDir, "file.txt"), //nolint:gosec - []byte{}, 0755) - assert.NoError(t, err) -} - -func TestReloadHugeNumberOfFiles(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-reload.yaml", - Prefix: "rr", - } - - // try to remove, skip error - assert.NoError(t, freeResources(testDir)) - assert.NoError(t, freeResources(testCopyToDir)) - - assert.NoError(t, os.Mkdir(testDir, 0755)) - assert.NoError(t, os.Mkdir(testCopyToDir, 0755)) - - controller := gomock.NewController(t) - mockLogger := mocks.NewMockLogger(controller) - - mockLogger.EXPECT().Debug("worker destructed", "pid", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Debug("worker constructed", "pid", gomock.Any()).AnyTimes() - mockLogger.EXPECT().Debug("file added to the list of removed files", "path", gomock.Any(), "name", gomock.Any(), "size", gomock.Any()).AnyTimes() - mockLogger.EXPECT().Debug("http handler response received", "elapsed", gomock.Any(), "remote address", "127.0.0.1").Times(1) - mockLogger.EXPECT().Debug("file was created", "path", gomock.Any(), "name", gomock.Any(), "size", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Debug("file was updated", "path", gomock.Any(), "name", gomock.Any(), "size", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Debug("file was added to watcher", "path", gomock.Any(), "name", gomock.Any(), "size", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Info("HTTP plugin got restart request. Restarting...").MinTimes(1) - mockLogger.EXPECT().Info("HTTP workers Pool successfully restarted").MinTimes(1) - mockLogger.EXPECT().Info("HTTP listeners successfully re-added").MinTimes(1) - mockLogger.EXPECT().Info("HTTP plugin successfully restarted").MinTimes(1) - mockLogger.EXPECT().Error(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() // placeholder for the workerlogerror - - err = cont.RegisterAll( - cfg, - mockLogger, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &reload.Plugin{}, - &resetter.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - assert.NoError(t, err) - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - t.Run("ReloadTestHugeNumberOfFiles", reloadHugeNumberOfFiles) - t.Run("ReloadRandomlyChangeFile", randomlyChangeFile) - - stopCh <- struct{}{} - wg.Wait() - - assert.NoError(t, freeResources(testDir)) - assert.NoError(t, freeResources(testCopyToDir)) -} - -func randomlyChangeFile(t *testing.T) { - // we know, that directory contains 500 files (0-499) - // let's try to randomly change it - for i := 0; i < 10; i++ { - // rand sleep - rSleep := rand.Int63n(500) // nolint:gosec - time.Sleep(time.Millisecond * time.Duration(rSleep)) - rNum := rand.Int63n(int64(hugeNumberOfFiles)) // nolint:gosec - err := ioutil.WriteFile(filepath.Join(testDir, "file_"+strconv.Itoa(int(rNum))+".txt"), []byte("Hello, Gophers!"), 0755) // nolint:gosec - assert.NoError(t, err) - } -} - -func reloadHugeNumberOfFiles(t *testing.T) { - for i := uint(0); i < hugeNumberOfFiles; i++ { - assert.NoError(t, makeFile("file_"+strconv.Itoa(int(i))+".txt")) - } -} - -// Should be events only about creating files with txt ext -func TestReloadFilterFileExt(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-reload-2.yaml", - Prefix: "rr", - } - - // try to remove, skip error - assert.NoError(t, freeResources(testDir)) - assert.NoError(t, os.Mkdir(testDir, 0755)) - - controller := gomock.NewController(t) - mockLogger := mocks.NewMockLogger(controller) - - mockLogger.EXPECT().Debug("worker destructed", "pid", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Debug("worker constructed", "pid", gomock.Any()).AnyTimes() - mockLogger.EXPECT().Debug("http handler response received", "elapsed", gomock.Any(), "remote address", "127.0.0.1").Times(1) - mockLogger.EXPECT().Debug("file was created", "path", gomock.Any(), "name", gomock.Any(), "size", gomock.Any()).MinTimes(100) - mockLogger.EXPECT().Debug("file was added to watcher", "path", gomock.Any(), "name", gomock.Any(), "size", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Debug("file added to the list of removed files", "path", gomock.Any(), "name", gomock.Any(), "size", gomock.Any()).AnyTimes() - mockLogger.EXPECT().Info("HTTP plugin got restart request. Restarting...").Times(1) - mockLogger.EXPECT().Info("HTTP workers Pool successfully restarted").Times(1) - mockLogger.EXPECT().Info("HTTP listeners successfully re-added").Times(1) - mockLogger.EXPECT().Info("HTTP plugin successfully restarted").Times(1) - mockLogger.EXPECT().Error(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() // placeholder for the workerlogerror - - err = cont.RegisterAll( - cfg, - mockLogger, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &reload.Plugin{}, - &resetter.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - assert.NoError(t, err) - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - t.Run("ReloadMakeFiles", reloadMakeFiles) - t.Run("ReloadFilteredExt", reloadFilteredExt) - - stopCh <- struct{}{} - wg.Wait() - - assert.NoError(t, freeResources(testDir)) -} - -func reloadMakeFiles(t *testing.T) { - for i := uint(0); i < 100; i++ { - assert.NoError(t, makeFile("file_"+strconv.Itoa(int(i))+".txt")) - } - for i := uint(0); i < 100; i++ { - assert.NoError(t, makeFile("file_"+strconv.Itoa(int(i))+".abc")) - } - for i := uint(0); i < 100; i++ { - assert.NoError(t, makeFile("file_"+strconv.Itoa(int(i))+".def")) - } -} - -func reloadFilteredExt(t *testing.T) { - // change files with abc extension - for i := 0; i < 10; i++ { - // rand sleep - rSleep := rand.Int63n(1000) // nolint:gosec - time.Sleep(time.Millisecond * time.Duration(rSleep)) - rNum := rand.Int63n(int64(hugeNumberOfFiles)) // nolint:gosec - err := ioutil.WriteFile(filepath.Join(testDir, "file_"+strconv.Itoa(int(rNum))+".abc"), []byte("Hello, Gophers!"), 0755) // nolint:gosec - assert.NoError(t, err) - } - - // change files with def extension - for i := 0; i < 10; i++ { - // rand sleep - rSleep := rand.Int63n(1000) // nolint:gosec - time.Sleep(time.Millisecond * time.Duration(rSleep)) - rNum := rand.Int63n(int64(hugeNumberOfFiles)) // nolint:gosec - err := ioutil.WriteFile(filepath.Join(testDir, "file_"+strconv.Itoa(int(rNum))+".def"), []byte("Hello, Gophers!"), 0755) // nolint:gosec - assert.NoError(t, err) - } -} - -// Should be events only about creating files with txt ext -func TestReloadCopy500(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-reload-3.yaml", - Prefix: "rr", - } - - // try to remove, skip error - assert.NoError(t, freeResources(testDir)) - assert.NoError(t, freeResources(testCopyToDir)) - assert.NoError(t, freeResources(dir1)) - - assert.NoError(t, os.Mkdir(testDir, 0755)) - assert.NoError(t, os.Mkdir(testCopyToDir, 0755)) - assert.NoError(t, os.Mkdir(dir1, 0755)) - - controller := gomock.NewController(t) - mockLogger := mocks.NewMockLogger(controller) - // - mockLogger.EXPECT().Debug("worker destructed", "pid", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Debug("worker constructed", "pid", gomock.Any()).AnyTimes() - mockLogger.EXPECT().Debug("http handler response received", "elapsed", gomock.Any(), "remote address", "127.0.0.1").Times(1) - mockLogger.EXPECT().Debug("file was created", "path", gomock.Any(), "name", gomock.Any(), "size", gomock.Any()).MinTimes(50) - mockLogger.EXPECT().Debug("file was added to watcher", "path", gomock.Any(), "name", gomock.Any(), "size", gomock.Any()).MinTimes(50) - mockLogger.EXPECT().Debug("file added to the list of removed files", "path", gomock.Any(), "name", gomock.Any(), "size", gomock.Any()).MinTimes(50) - mockLogger.EXPECT().Debug("file was removed from watcher", "path", gomock.Any(), "name", gomock.Any(), "size", gomock.Any()).MinTimes(50) - mockLogger.EXPECT().Debug("file was updated", "path", gomock.Any(), "name", gomock.Any(), "size", gomock.Any()).MinTimes(50) - mockLogger.EXPECT().Info("HTTP plugin got restart request. Restarting...").MinTimes(1) - mockLogger.EXPECT().Info("HTTP workers Pool successfully restarted").MinTimes(1) - mockLogger.EXPECT().Info("HTTP listeners successfully re-added").MinTimes(1) - mockLogger.EXPECT().Info("HTTP plugin successfully restarted").MinTimes(1) - mockLogger.EXPECT().Error(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() // placeholder for the workerlogerror - - err = cont.RegisterAll( - cfg, - mockLogger, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &reload.Plugin{}, - &resetter.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - assert.NoError(t, err) - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - // Scenario - // 1 - // Create 3k files with txt, abc, def extensions - // Copy files to the unit_tests_copy dir - // 2 - // Delete both dirs, recreate - // Create 3k files with txt, abc, def extensions - // Move files to the unit_tests_copy dir - // 3 - // Recursive - - t.Run("ReloadMake300Files", reloadMake300Files) - t.Run("ReloadCopyFiles", reloadCopyFiles) - t.Run("ReloadRecursiveDirsSupport", copyFilesRecursive) - t.Run("RandomChangesInRecursiveDirs", randomChangesInRecursiveDirs) - t.Run("RemoveFilesSupport", removeFilesSupport) - t.Run("ReloadMoveSupport", reloadMoveSupport) - - assert.NoError(t, freeResources(testDir)) - assert.NoError(t, freeResources(testCopyToDir)) - assert.NoError(t, freeResources(dir1)) - - stopCh <- struct{}{} - wg.Wait() -} - -func reloadMoveSupport(t *testing.T) { - t.Run("MoveSupportCopy", copyFilesRecursive) - // move some files - for i := 0; i < 10; i++ { - // rand sleep - rSleep := rand.Int63n(500) // nolint:gosec - time.Sleep(time.Millisecond * time.Duration(rSleep)) - rNum := rand.Int63n(int64(100)) // nolint:gosec - rDir := rand.Int63n(9) // nolint:gosec - rExt := rand.Int63n(3) // nolint:gosec - - ext := []string{ - ".txt", - ".abc", - ".def", - } - - // change files with def extension - dirs := []string{ - "dir1", - "dir1/dir2", - "dir1/dir2/dir3", - "dir1/dir2/dir3/dir4", - "dir1/dir2/dir3/dir4/dir5", - "dir1/dir2/dir3/dir4/dir5/dir6", - "dir1/dir2/dir3/dir4/dir5/dir6/dir7", - "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8", - "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9", - "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9/dir10", - } - - // move file - err := os.Rename(filepath.Join(dirs[rDir], "file_"+strconv.Itoa(int(rNum))+ext[rExt]), filepath.Join(dirs[rDir+1], "file_"+strconv.Itoa(int(rNum))+ext[rExt])) - assert.NoError(t, err) - } -} - -func removeFilesSupport(t *testing.T) { - // remove some files - for i := 0; i < 10; i++ { - // rand sleep - rSleep := rand.Int63n(500) // nolint:gosec - time.Sleep(time.Millisecond * time.Duration(rSleep)) - rNum := rand.Int63n(int64(100)) // nolint:gosec - rDir := rand.Int63n(10) // nolint:gosec - rExt := rand.Int63n(3) // nolint:gosec - - ext := []string{ - ".txt", - ".abc", - ".def", - } - - // change files with def extension - dirs := []string{ - "dir1", - "dir1/dir2", - "dir1/dir2/dir3", - "dir1/dir2/dir3/dir4", - "dir1/dir2/dir3/dir4/dir5", - "dir1/dir2/dir3/dir4/dir5/dir6", - "dir1/dir2/dir3/dir4/dir5/dir6/dir7", - "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8", - "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9", - "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9/dir10", - } - // here can be a situation, when file already deleted - _ = os.Remove(filepath.Join(dirs[rDir], "file_"+strconv.Itoa(int(rNum))+ext[rExt])) - } -} - -func randomChangesInRecursiveDirs(t *testing.T) { - // change files with def extension - dirs := []string{ - "dir1", - "dir1/dir2", - "dir1/dir2/dir3", - "dir1/dir2/dir3/dir4", - "dir1/dir2/dir3/dir4/dir5", - "dir1/dir2/dir3/dir4/dir5/dir6", - "dir1/dir2/dir3/dir4/dir5/dir6/dir7", - "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8", - "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9", - "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9/dir10", - } - - ext := []string{ - ".txt", - ".abc", - ".def", - } - - filenames := []string{ - "file_", // should be update - "foo_", // should be created - "bar_", // should be created - } - for i := 0; i < 10; i++ { - // rand sleep - rSleep := rand.Int63n(500) // nolint:gosec - time.Sleep(time.Millisecond * time.Duration(rSleep)) - rNum := rand.Int63n(int64(100)) // nolint:gosec - rDir := rand.Int63n(10) // nolint:gosec - rExt := rand.Int63n(3) // nolint:gosec - rName := rand.Int63n(3) // nolint:gosec - - err := ioutil.WriteFile(filepath.Join(dirs[rDir], filenames[rName]+strconv.Itoa(int(rNum))+ext[rExt]), []byte("Hello, Gophers!"), 0755) // nolint:gosec - assert.NoError(t, err) - } -} - -func copyFilesRecursive(t *testing.T) { - err := copyDir(testDir, "dir1") - assert.NoError(t, err) - err = copyDir(testDir, "dir1/dir2") - assert.NoError(t, err) - err = copyDir(testDir, "dir1/dir2/dir3") - assert.NoError(t, err) - err = copyDir(testDir, "dir1/dir2/dir3/dir4") - assert.NoError(t, err) - err = copyDir(testDir, "dir1/dir2/dir3/dir4/dir5") - assert.NoError(t, err) - err = copyDir(testDir, "dir1/dir2/dir3/dir4/dir5/dir6") - assert.NoError(t, err) - err = copyDir(testDir, "dir1/dir2/dir3/dir4/dir5/dir6/dir7") - assert.NoError(t, err) - err = copyDir(testDir, "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8") - assert.NoError(t, err) - err = copyDir(testDir, "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9") - assert.NoError(t, err) - err = copyDir(testDir, "dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9/dir10") - assert.NoError(t, err) -} - -func reloadCopyFiles(t *testing.T) { - err := copyDir(testDir, testCopyToDir) - assert.NoError(t, err) - - assert.NoError(t, freeResources(testDir)) - assert.NoError(t, freeResources(testCopyToDir)) - - assert.NoError(t, os.Mkdir(testDir, 0755)) - assert.NoError(t, os.Mkdir(testCopyToDir, 0755)) - - // recreate files - for i := uint(0); i < 100; i++ { - assert.NoError(t, makeFile("file_"+strconv.Itoa(int(i))+".txt")) - } - for i := uint(0); i < 100; i++ { - assert.NoError(t, makeFile("file_"+strconv.Itoa(int(i))+".abc")) - } - for i := uint(0); i < 100; i++ { - assert.NoError(t, makeFile("file_"+strconv.Itoa(int(i))+".def")) - } - - err = copyDir(testDir, testCopyToDir) - assert.NoError(t, err) -} - -func reloadMake300Files(t *testing.T) { - for i := uint(0); i < 100; i++ { - assert.NoError(t, makeFile("file_"+strconv.Itoa(int(i))+".txt")) - } - for i := uint(0); i < 100; i++ { - assert.NoError(t, makeFile("file_"+strconv.Itoa(int(i))+".abc")) - } - for i := uint(0); i < 100; i++ { - assert.NoError(t, makeFile("file_"+strconv.Itoa(int(i))+".def")) - } -} - -func TestReloadNoRecursion(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-reload-4.yaml", - Prefix: "rr", - } - - // try to remove, skip error - assert.NoError(t, freeResources(testDir)) - assert.NoError(t, freeResources(testCopyToDir)) - assert.NoError(t, freeResources(dir1)) - - assert.NoError(t, os.Mkdir(testDir, 0755)) - assert.NoError(t, os.Mkdir(dir1, 0755)) - assert.NoError(t, os.Mkdir(testCopyToDir, 0755)) - - controller := gomock.NewController(t) - mockLogger := mocks.NewMockLogger(controller) - - // http server should not be restarted. all event from wrong file extensions should be skipped - mockLogger.EXPECT().Debug("worker destructed", "pid", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Debug("worker constructed", "pid", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Debug("http handler response received", "elapsed", gomock.Any(), "remote address", "127.0.0.1").Times(1) - mockLogger.EXPECT().Debug("file added to the list of removed files", "path", gomock.Any(), "name", gomock.Any(), "size", gomock.Any()).MinTimes(1) - mockLogger.EXPECT().Error(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() // placeholder for the workerlogerror - - err = cont.RegisterAll( - cfg, - mockLogger, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &reload.Plugin{}, - &resetter.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - assert.NoError(t, err) - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - t.Run("ReloadMakeFiles", reloadMakeFiles) // make files in the testDir - t.Run("ReloadCopyFilesRecursive", reloadCopyFiles) - - stopCh <- struct{}{} - wg.Wait() - - assert.NoError(t, freeResources(testDir)) - assert.NoError(t, freeResources(testCopyToDir)) - assert.NoError(t, freeResources(dir1)) -} - -// ======================================================================== - -func freeResources(path string) error { - return os.RemoveAll(path) -} - -func makeFile(filename string) error { - return ioutil.WriteFile(filepath.Join(testDir, filename), []byte{}, 0755) //nolint:gosec -} - -func copyDir(src string, dst string) error { - src = filepath.Clean(src) - dst = filepath.Clean(dst) - - si, err := os.Stat(src) - if err != nil { - return err - } - if !si.IsDir() { - return errors.E(errors.Str("source is not a directory")) - } - - _, err = os.Stat(dst) - if err != nil && !os.IsNotExist(err) { - return err - } - - err = os.MkdirAll(dst, si.Mode()) - if err != nil { - return err - } - - entries, err := ioutil.ReadDir(src) - if err != nil { - return err - } - - for _, entry := range entries { - srcPath := filepath.Join(src, entry.Name()) - dstPath := filepath.Join(dst, entry.Name()) - - if entry.IsDir() { - err = copyDir(srcPath, dstPath) - if err != nil { - return err - } - } else { - // Skip symlinks. - if entry.Mode()&os.ModeSymlink != 0 { - continue - } - - err = copyFile(srcPath, dstPath) - if err != nil { - return err - } - } - } - return nil -} - -func copyFile(src, dst string) error { - in, err := os.Open(src) - if err != nil { - return errors.E(err) - } - defer func() { - _ = in.Close() - }() - - out, err := os.Create(dst) - if err != nil { - return errors.E(err) - } - defer func() { - _ = out.Close() - }() - - _, err = io.Copy(out, in) - if err != nil { - return errors.E(err) - } - - err = out.Sync() - if err != nil { - return errors.E(err) - } - - si, err := os.Stat(src) - if err != nil { - return errors.E(err) - } - err = os.Chmod(dst, si.Mode()) - if err != nil { - return errors.E(err) - } - return nil -} diff --git a/tests/plugins/resetter/.rr-resetter.yaml b/tests/plugins/resetter/.rr-resetter.yaml deleted file mode 100644 index 623ba142..00000000 --- a/tests/plugins/resetter/.rr-resetter.yaml +++ /dev/null @@ -1,15 +0,0 @@ -server: - command: "php ../../http/client.php echo pipes" - user: "" - group: "" - env: - "RR_CONFIG": "/some/place/on/the/C134" - "RR_CONFIG2": "C138" - relay: "pipes" - relay_timeout: "20s" - -rpc: - listen: tcp://127.0.0.1:6001 -logs: - mode: development - level: debug
\ No newline at end of file diff --git a/tests/plugins/resetter/resetter_test.go b/tests/plugins/resetter/resetter_test.go deleted file mode 100644 index 465d22dd..00000000 --- a/tests/plugins/resetter/resetter_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package resetter - -import ( - "net" - "net/rpc" - "os" - "os/signal" - "sync" - "syscall" - "testing" - "time" - - "github.com/golang/mock/gomock" - endure "github.com/spiral/endure/pkg/container" - goridgeRpc "github.com/spiral/goridge/v3/pkg/rpc" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/resetter" - rpcPlugin "github.com/spiral/roadrunner/v2/plugins/rpc" - "github.com/spiral/roadrunner/v2/plugins/server" - "github.com/spiral/roadrunner/v2/tests/mocks" - "github.com/stretchr/testify/assert" -) - -func TestResetterInit(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - if err != nil { - t.Fatal(err) - } - - cfg := &config.Viper{ - Path: ".rr-resetter.yaml", - Prefix: "rr", - } - - controller := gomock.NewController(t) - mockLogger := mocks.NewMockLogger(controller) - - mockLogger.EXPECT().Debug("worker destructed", "pid", gomock.Any()).AnyTimes() - mockLogger.EXPECT().Debug("worker constructed", "pid", gomock.Any()).AnyTimes() - mockLogger.EXPECT().Debug("Started RPC service", "address", "tcp://127.0.0.1:6001", "services", []string{"resetter"}).MinTimes(1) - - mockLogger.EXPECT().Error(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() - - mockLogger.EXPECT().Debug("started List method").MinTimes(1) - mockLogger.EXPECT().Debug("services list", "services", []string{"resetter.plugin1"}).MinTimes(1) - mockLogger.EXPECT().Debug("finished List method").MinTimes(1) - mockLogger.EXPECT().Debug("started Reset method for the service", "service", "resetter.plugin1").MinTimes(1) - mockLogger.EXPECT().Debug("finished Reset method for the service", "service", "resetter.plugin1").MinTimes(1) - mockLogger.EXPECT().Warn("listener accept error, connection closed", "error", gomock.Any()).AnyTimes() - - err = cont.RegisterAll( - cfg, - &server.Plugin{}, - mockLogger, - &resetter.Plugin{}, - &rpcPlugin.Plugin{}, - &Plugin1{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - stopCh := make(chan struct{}, 1) - - wg := &sync.WaitGroup{} - wg.Add(1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second) - - t.Run("ResetterRpcTest", resetterRPCTest) - stopCh <- struct{}{} - wg.Wait() -} - -func resetterRPCTest(t *testing.T) { - conn, err := net.Dial("tcp", "127.0.0.1:6001") - assert.NoError(t, err) - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - // WorkerList contains list of workers. - - var ret bool - err = client.Call("resetter.Reset", "resetter.plugin1", &ret) - assert.NoError(t, err) - assert.True(t, ret) - ret = false - - var services []string - err = client.Call("resetter.List", nil, &services) - assert.NotNil(t, services) - assert.NoError(t, err) - if services[0] != "resetter.plugin1" { - t.Fatal("no enough services") - } -} diff --git a/tests/plugins/resetter/test_plugin.go b/tests/plugins/resetter/test_plugin.go deleted file mode 100644 index 61942516..00000000 --- a/tests/plugins/resetter/test_plugin.go +++ /dev/null @@ -1,66 +0,0 @@ -package resetter - -import ( - "context" - "time" - - poolImpl "github.com/spiral/roadrunner/v2/pkg/pool" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/server" -) - -var testPoolConfig = poolImpl.Config{ - NumWorkers: 10, - MaxJobs: 100, - AllocateTimeout: time.Second * 10, - DestroyTimeout: time.Second * 10, - Supervisor: &poolImpl.SupervisorConfig{ - WatchTick: 60 * time.Second, - TTL: 1000 * time.Second, - IdleTTL: 10 * time.Second, - ExecTTL: 10 * time.Second, - MaxWorkerMemory: 1000, - }, -} - -// Gauge ////////////// -type Plugin1 struct { - config config.Configurer - server server.Server -} - -func (p1 *Plugin1) Init(cfg config.Configurer, server server.Server) error { - p1.config = cfg - p1.server = server - return nil -} - -func (p1 *Plugin1) Serve() chan error { - errCh := make(chan error, 1) - return errCh -} - -func (p1 *Plugin1) Stop() error { - return nil -} - -func (p1 *Plugin1) Name() string { - return "resetter.plugin1" -} - -func (p1 *Plugin1) Reset() error { - pool, err := p1.server.NewWorkerPool(context.Background(), testPoolConfig, nil) - if err != nil { - panic(err) - } - pool.Destroy(context.Background()) - - pool, err = p1.server.NewWorkerPool(context.Background(), testPoolConfig, nil) - if err != nil { - panic(err) - } - - _ = pool - - return nil -} diff --git a/tests/plugins/rpc/config_test.go b/tests/plugins/rpc/config_test.go deleted file mode 100755 index 34ca9cee..00000000 --- a/tests/plugins/rpc/config_test.go +++ /dev/null @@ -1,134 +0,0 @@ -package rpc - -import ( - "runtime" - "testing" - - "github.com/spiral/roadrunner/v2/plugins/rpc" - "github.com/stretchr/testify/assert" -) - -func TestConfig_Listener(t *testing.T) { - cfg := &rpc.Config{Listen: "tcp://:18001"} - - ln, err := cfg.Listener() - assert.NoError(t, err) - assert.NotNil(t, ln) - defer func() { - err := ln.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - - assert.Equal(t, "tcp", ln.Addr().Network()) - if runtime.GOOS == "windows" { - assert.Equal(t, "[::]:18001", ln.Addr().String()) - } else { - assert.Equal(t, "0.0.0.0:18001", ln.Addr().String()) - } -} - -func TestConfig_ListenerUnix(t *testing.T) { - cfg := &rpc.Config{Listen: "unix://file.sock"} - - ln, err := cfg.Listener() - assert.NoError(t, err) - assert.NotNil(t, ln) - defer func() { - err := ln.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - - assert.Equal(t, "unix", ln.Addr().Network()) - assert.Equal(t, "file.sock", ln.Addr().String()) -} - -func Test_Config_Error(t *testing.T) { - cfg := &rpc.Config{Listen: "uni:unix.sock"} - ln, err := cfg.Listener() - assert.Nil(t, ln) - assert.Error(t, err) -} - -func Test_Config_ErrorMethod(t *testing.T) { - cfg := &rpc.Config{Listen: "xinu://unix.sock"} - - ln, err := cfg.Listener() - assert.Nil(t, ln) - assert.Error(t, err) -} - -func TestConfig_Dialer(t *testing.T) { - cfg := &rpc.Config{Listen: "tcp://:18001"} - - ln, _ := cfg.Listener() - defer func() { - err := ln.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - - conn, err := cfg.Dialer() - assert.NoError(t, err) - assert.NotNil(t, conn) - defer func() { - err := conn.Close() - if err != nil { - t.Errorf("error closing the connection: error %v", err) - } - }() - - assert.Equal(t, "tcp", conn.RemoteAddr().Network()) - assert.Equal(t, "127.0.0.1:18001", conn.RemoteAddr().String()) -} - -func TestConfig_DialerUnix(t *testing.T) { - cfg := &rpc.Config{Listen: "unix://file.sock"} - - ln, _ := cfg.Listener() - defer func() { - err := ln.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - - conn, err := cfg.Dialer() - assert.NoError(t, err) - assert.NotNil(t, conn) - defer func() { - err := conn.Close() - if err != nil { - t.Errorf("error closing the connection: error %v", err) - } - }() - - assert.Equal(t, "unix", conn.RemoteAddr().Network()) - assert.Equal(t, "file.sock", conn.RemoteAddr().String()) -} - -func Test_Config_DialerError(t *testing.T) { - cfg := &rpc.Config{Listen: "uni:unix.sock"} - ln, err := cfg.Dialer() - assert.Nil(t, ln) - assert.Error(t, err) - assert.Equal(t, "invalid socket DSN (tcp://:6001, unix://file.sock)", err.Error()) -} - -func Test_Config_DialerErrorMethod(t *testing.T) { - cfg := &rpc.Config{Listen: "xinu://unix.sock"} - - ln, err := cfg.Dialer() - assert.Nil(t, ln) - assert.Error(t, err) -} - -func Test_Config_Defaults(t *testing.T) { - c := &rpc.Config{} - c.InitDefaults() - assert.Equal(t, "tcp://127.0.0.1:6001", c.Listen) -} diff --git a/tests/plugins/rpc/configs/.rr-rpc-disabled.yaml b/tests/plugins/rpc/configs/.rr-rpc-disabled.yaml deleted file mode 100644 index 5ab359d3..00000000 --- a/tests/plugins/rpc/configs/.rr-rpc-disabled.yaml +++ /dev/null @@ -1,3 +0,0 @@ -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/rpc/configs/.rr.yaml b/tests/plugins/rpc/configs/.rr.yaml deleted file mode 100644 index 67d935e3..00000000 --- a/tests/plugins/rpc/configs/.rr.yaml +++ /dev/null @@ -1,5 +0,0 @@ -rpc: - listen: tcp://127.0.0.1:6001 -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/rpc/plugin1.go b/tests/plugins/rpc/plugin1.go deleted file mode 100644 index 6843b396..00000000 --- a/tests/plugins/rpc/plugin1.go +++ /dev/null @@ -1,42 +0,0 @@ -package rpc - -import ( - "fmt" - - "github.com/spiral/roadrunner/v2/plugins/config" -) - -type Plugin1 struct { - config config.Configurer -} - -func (p1 *Plugin1) Init(cfg config.Configurer) error { - p1.config = cfg - return nil -} - -func (p1 *Plugin1) Serve() chan error { - errCh := make(chan error, 1) - return errCh -} - -func (p1 *Plugin1) Stop() error { - return nil -} - -func (p1 *Plugin1) Name() string { - return "rpc_test.plugin1" -} - -func (p1 *Plugin1) RPC() interface{} { - return &PluginRPC{srv: p1} -} - -type PluginRPC struct { - srv *Plugin1 -} - -func (r *PluginRPC) Hello(in string, out *string) error { - *out = fmt.Sprintf("Hello, username: %s", in) - return nil -} diff --git a/tests/plugins/rpc/plugin2.go b/tests/plugins/rpc/plugin2.go deleted file mode 100644 index 2c47158f..00000000 --- a/tests/plugins/rpc/plugin2.go +++ /dev/null @@ -1,53 +0,0 @@ -package rpc - -import ( - "net" - "net/rpc" - "time" - - "github.com/spiral/errors" - goridgeRpc "github.com/spiral/goridge/v3/pkg/rpc" -) - -// plugin2 makes a call to the plugin1 via RPC -// this is just a simulation of external call FOR TEST -// you don't need to do such things :) -type Plugin2 struct { -} - -func (p2 *Plugin2) Init() error { - return nil -} - -func (p2 *Plugin2) Serve() chan error { - errCh := make(chan error, 1) - - go func() { - time.Sleep(time.Second * 3) - - conn, err := net.Dial("tcp", "127.0.0.1:6001") - if err != nil { - errCh <- errors.E(errors.Serve, err) - return - } - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - var ret string - err = client.Call("rpc_test.plugin1.Hello", "Valery", &ret) - if err != nil { - errCh <- err - return - } - if ret != "Hello, username: Valery" { - errCh <- errors.E("wrong response") - return - } - // to stop exec - errCh <- errors.E(errors.Disabled) - }() - - return errCh -} - -func (p2 *Plugin2) Stop() error { - return nil -} diff --git a/tests/plugins/rpc/rpc_test.go b/tests/plugins/rpc/rpc_test.go deleted file mode 100644 index 49d3b3f1..00000000 --- a/tests/plugins/rpc/rpc_test.go +++ /dev/null @@ -1,188 +0,0 @@ -package rpc - -import ( - "os" - "os/signal" - "sync" - "syscall" - "testing" - "time" - - endure "github.com/spiral/endure/pkg/container" - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/logger" - "github.com/spiral/roadrunner/v2/plugins/rpc" - "github.com/stretchr/testify/assert" -) - -// graph https://bit.ly/3ensdNb -func TestRpcInit(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - if err != nil { - t.Fatal(err) - } - - err = cont.Register(&Plugin1{}) - if err != nil { - t.Fatal(err) - } - - err = cont.Register(&Plugin2{}) - if err != nil { - t.Fatal(err) - } - - v := &config.Viper{} - v.Path = "configs/.rr.yaml" - v.Prefix = "rr" - err = cont.Register(v) - if err != nil { - t.Fatal(err) - } - - err = cont.Register(&rpc.Plugin{}) - if err != nil { - t.Fatal(err) - } - - err = cont.Register(&logger.ZapLogger{}) - if err != nil { - t.Fatal(err) - } - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - if err != nil { - t.Fatal(err) - } - - sig := make(chan os.Signal, 1) - - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - wg := &sync.WaitGroup{} - wg.Add(1) - - tt := time.NewTimer(time.Second * 10) - - go func() { - defer wg.Done() - defer tt.Stop() - for { - select { - case e := <-ch: - // just stop, this is ok - if errors.Is(errors.Disabled, e.Error) { - return - } - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-tt.C: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - assert.Fail(t, "timeout") - } - } - }() - - wg.Wait() -} - -// graph https://bit.ly/3ensdNb -func TestRpcDisabled(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - if err != nil { - t.Fatal(err) - } - - err = cont.Register(&Plugin1{}) - if err != nil { - t.Fatal(err) - } - - err = cont.Register(&Plugin2{}) - if err != nil { - t.Fatal(err) - } - - v := &config.Viper{} - v.Path = "configs/.rr-rpc-disabled.yaml" - v.Prefix = "rr" - err = cont.Register(v) - if err != nil { - t.Fatal(err) - } - - err = cont.Register(&rpc.Plugin{}) - if err != nil { - t.Fatal(err) - } - - err = cont.Register(&logger.ZapLogger{}) - if err != nil { - t.Fatal(err) - } - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - if err != nil { - t.Fatal(err) - } - - sig := make(chan os.Signal, 1) - - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - tt := time.NewTimer(time.Second * 20) - - wg := &sync.WaitGroup{} - wg.Add(1) - - go func() { - defer wg.Done() - defer tt.Stop() - for { - select { - case e := <-ch: - // RPC is turned off, should be and dial error - if errors.Is(errors.Disabled, e.Error) { - assert.FailNow(t, "should not be disabled error") - } - assert.Error(t, e.Error) - err = cont.Stop() - assert.Error(t, err) - return - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-tt.C: - // timeout - return - } - } - }() - - wg.Wait() -} diff --git a/tests/plugins/server/configs/.rr-no-app-section.yaml b/tests/plugins/server/configs/.rr-no-app-section.yaml deleted file mode 100644 index e44eeb56..00000000 --- a/tests/plugins/server/configs/.rr-no-app-section.yaml +++ /dev/null @@ -1,12 +0,0 @@ -server: - command: "php ../../client.php echo pipes" - user: "" - group: "" - env: - "RR_CONFIG": "/some/place/on/the/C134" - "RR_CONFIG2": "C138" - relay: "pipes" - relay_timeout: "20s" -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/server/configs/.rr-sockets.yaml b/tests/plugins/server/configs/.rr-sockets.yaml deleted file mode 100644 index 0bc2d0f9..00000000 --- a/tests/plugins/server/configs/.rr-sockets.yaml +++ /dev/null @@ -1,12 +0,0 @@ -server: - command: "php socket.php" - user: "" - group: "" - env: - "RR_CONFIG": "/some/place/on/the/C134" - "RR_CONFIG2": "C138" - relay: "unix://unix.sock" - relay_timeout: "20s" -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/server/configs/.rr-tcp.yaml b/tests/plugins/server/configs/.rr-tcp.yaml deleted file mode 100644 index f4580460..00000000 --- a/tests/plugins/server/configs/.rr-tcp.yaml +++ /dev/null @@ -1,12 +0,0 @@ -server: - command: "php tcp.php" - user: "" - group: "" - env: - "RR_CONFIG": "/some/place/on/the/C134" - "RR_CONFIG2": "C138" - relay: "tcp://localhost:9999" - relay_timeout: "20s" -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/server/configs/.rr-wrong-command.yaml b/tests/plugins/server/configs/.rr-wrong-command.yaml deleted file mode 100644 index c97d8b7e..00000000 --- a/tests/plugins/server/configs/.rr-wrong-command.yaml +++ /dev/null @@ -1,12 +0,0 @@ -server: - command: "php some_absent_file.php" - user: "" - group: "" - env: - "RR_CONFIG": "/some/place/on/the/C134" - "RR_CONFIG2": "C138" - relay: "pipes" - relay_timeout: "20s" -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/server/configs/.rr-wrong-relay.yaml b/tests/plugins/server/configs/.rr-wrong-relay.yaml deleted file mode 100644 index 9722a487..00000000 --- a/tests/plugins/server/configs/.rr-wrong-relay.yaml +++ /dev/null @@ -1,12 +0,0 @@ -server: - command: "php ../../client.php echo pipes" - user: "" - group: "" - env: - "RR_CONFIG": "/some/place/on/the/C134" - "RR_CONFIG2": "C138" - relay: "pupes" - relay_timeout: "20s" -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/server/configs/.rr.yaml b/tests/plugins/server/configs/.rr.yaml deleted file mode 100644 index e44eeb56..00000000 --- a/tests/plugins/server/configs/.rr.yaml +++ /dev/null @@ -1,12 +0,0 @@ -server: - command: "php ../../client.php echo pipes" - user: "" - group: "" - env: - "RR_CONFIG": "/some/place/on/the/C134" - "RR_CONFIG2": "C138" - relay: "pipes" - relay_timeout: "20s" -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/server/plugin_pipes.go b/tests/plugins/server/plugin_pipes.go deleted file mode 100644 index af34b4d3..00000000 --- a/tests/plugins/server/plugin_pipes.go +++ /dev/null @@ -1,128 +0,0 @@ -package server - -import ( - "context" - "time" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/pkg/payload" - "github.com/spiral/roadrunner/v2/pkg/pool" - "github.com/spiral/roadrunner/v2/pkg/worker" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/server" -) - -const ConfigSection = "server" -const Response = "test" - -var testPoolConfig = pool.Config{ - NumWorkers: 10, - MaxJobs: 100, - AllocateTimeout: time.Second * 10, - DestroyTimeout: time.Second * 10, - Supervisor: &pool.SupervisorConfig{ - WatchTick: 60 * time.Second, - TTL: 1000 * time.Second, - IdleTTL: 10 * time.Second, - ExecTTL: 10 * time.Second, - MaxWorkerMemory: 1000, - }, -} - -type Foo struct { - configProvider config.Configurer - wf server.Server - pool pool.Pool -} - -func (f *Foo) Init(p config.Configurer, workerFactory server.Server) error { - f.configProvider = p - f.wf = workerFactory - return nil -} - -func (f *Foo) Serve() chan error { - const op = errors.Op("serve") - - // test payload for echo - r := payload.Payload{ - Context: nil, - Body: []byte(Response), - } - - errCh := make(chan error, 1) - - conf := &server.Config{} - var err error - err = f.configProvider.UnmarshalKey(ConfigSection, conf) - if err != nil { - errCh <- err - return errCh - } - - // test CMDFactory - cmd, err := f.wf.CmdFactory(nil) - if err != nil { - errCh <- err - return errCh - } - if cmd == nil { - errCh <- errors.E(op, "command is nil") - return errCh - } - - // test worker creation - w, err := f.wf.NewWorker(context.Background(), nil) - if err != nil { - errCh <- err - return errCh - } - - // test that our worker is functional - sw := worker.From(w) - - rsp, err := sw.Exec(r) - if err != nil { - errCh <- err - return errCh - } - - if string(rsp.Body) != Response { - errCh <- errors.E("response from worker is wrong", errors.Errorf("response: %s", rsp.Body)) - return errCh - } - - // should not be errors - err = sw.Stop() - if err != nil { - errCh <- err - return errCh - } - - // test pool - f.pool, err = f.wf.NewWorkerPool(context.Background(), testPoolConfig, nil) - if err != nil { - errCh <- err - return errCh - } - - // test pool execution - rsp, err = f.pool.Exec(r) - if err != nil { - errCh <- err - return errCh - } - - // echo of the "test" should be -> test - if string(rsp.Body) != Response { - errCh <- errors.E("response from worker is wrong", errors.Errorf("response: %s", rsp.Body)) - return errCh - } - - return errCh -} - -func (f *Foo) Stop() error { - f.pool.Destroy(context.Background()) - return nil -} diff --git a/tests/plugins/server/plugin_sockets.go b/tests/plugins/server/plugin_sockets.go deleted file mode 100644 index 0b2857e3..00000000 --- a/tests/plugins/server/plugin_sockets.go +++ /dev/null @@ -1,109 +0,0 @@ -package server - -import ( - "context" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/pkg/payload" - "github.com/spiral/roadrunner/v2/pkg/pool" - "github.com/spiral/roadrunner/v2/pkg/worker" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/server" -) - -type Foo2 struct { - configProvider config.Configurer - wf server.Server - pool pool.Pool -} - -func (f *Foo2) Init(p config.Configurer, workerFactory server.Server) error { - f.configProvider = p - f.wf = workerFactory - return nil -} - -func (f *Foo2) Serve() chan error { - const op = errors.Op("serve") - var err error - errCh := make(chan error, 1) - conf := &server.Config{} - - // test payload for echo - r := payload.Payload{ - Context: nil, - Body: []byte(Response), - } - - err = f.configProvider.UnmarshalKey(ConfigSection, conf) - if err != nil { - errCh <- err - return errCh - } - - // test CMDFactory - cmd, err := f.wf.CmdFactory(nil) - if err != nil { - errCh <- err - return errCh - } - if cmd == nil { - errCh <- errors.E(op, "command is nil") - return errCh - } - - // test worker creation - w, err := f.wf.NewWorker(context.Background(), nil) - if err != nil { - errCh <- err - return errCh - } - - // test that our worker is functional - sw := worker.From(w) - - rsp, err := sw.Exec(r) - if err != nil { - errCh <- err - return errCh - } - - if string(rsp.Body) != Response { - errCh <- errors.E("response from worker is wrong", errors.Errorf("response: %s", rsp.Body)) - return errCh - } - - // should not be errors - err = sw.Stop() - if err != nil { - errCh <- err - return errCh - } - - // test pool - f.pool, err = f.wf.NewWorkerPool(context.Background(), testPoolConfig, nil) - if err != nil { - errCh <- err - return errCh - } - - // test pool execution - rsp, err = f.pool.Exec(r) - if err != nil { - errCh <- err - return errCh - } - - // echo of the "test" should be -> test - if string(rsp.Body) != Response { - errCh <- errors.E("response from worker is wrong", errors.Errorf("response: %s", rsp.Body)) - return errCh - } - - return errCh -} - -func (f *Foo2) Stop() error { - f.pool.Destroy(context.Background()) - return nil -} diff --git a/tests/plugins/server/plugin_tcp.go b/tests/plugins/server/plugin_tcp.go deleted file mode 100644 index ef4cea39..00000000 --- a/tests/plugins/server/plugin_tcp.go +++ /dev/null @@ -1,109 +0,0 @@ -package server - -import ( - "context" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/pkg/payload" - "github.com/spiral/roadrunner/v2/pkg/pool" - "github.com/spiral/roadrunner/v2/pkg/worker" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/server" -) - -type Foo3 struct { - configProvider config.Configurer - wf server.Server - pool pool.Pool -} - -func (f *Foo3) Init(p config.Configurer, workerFactory server.Server) error { - f.configProvider = p - f.wf = workerFactory - return nil -} - -func (f *Foo3) Serve() chan error { - const op = errors.Op("serve") - var err error - errCh := make(chan error, 1) - conf := &server.Config{} - - // test payload for echo - r := payload.Payload{ - Context: nil, - Body: []byte(Response), - } - - err = f.configProvider.UnmarshalKey(ConfigSection, conf) - if err != nil { - errCh <- err - return errCh - } - - // test CMDFactory - cmd, err := f.wf.CmdFactory(nil) - if err != nil { - errCh <- err - return errCh - } - if cmd == nil { - errCh <- errors.E(op, "command is nil") - return errCh - } - - // test worker creation - w, err := f.wf.NewWorker(context.Background(), nil) - if err != nil { - errCh <- err - return errCh - } - - // test that our worker is functional - sw := worker.From(w) - - rsp, err := sw.Exec(r) - if err != nil { - errCh <- err - return errCh - } - - if string(rsp.Body) != Response { - errCh <- errors.E("response from worker is wrong", errors.Errorf("response: %s", rsp.Body)) - return errCh - } - - // should not be errors - err = sw.Stop() - if err != nil { - errCh <- err - return errCh - } - - // test pool - f.pool, err = f.wf.NewWorkerPool(context.Background(), testPoolConfig, nil) - if err != nil { - errCh <- err - return errCh - } - - // test pool execution - rsp, err = f.pool.Exec(r) - if err != nil { - errCh <- err - return errCh - } - - // echo of the "test" should be -> test - if string(rsp.Body) != Response { - errCh <- errors.E("response from worker is wrong", errors.Errorf("response: %s", rsp.Body)) - return errCh - } - - return errCh -} - -func (f *Foo3) Stop() error { - f.pool.Destroy(context.Background()) - return nil -} diff --git a/tests/plugins/server/server_plugin_test.go b/tests/plugins/server/server_plugin_test.go deleted file mode 100644 index f600832a..00000000 --- a/tests/plugins/server/server_plugin_test.go +++ /dev/null @@ -1,361 +0,0 @@ -package server - -import ( - "os" - "os/signal" - "sync" - "testing" - "time" - - endure "github.com/spiral/endure/pkg/container" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/logger" - "github.com/spiral/roadrunner/v2/plugins/server" - "github.com/stretchr/testify/assert" -) - -func TestAppPipes(t *testing.T) { - container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.ErrorLevel)) - if err != nil { - t.Fatal(err) - } - // config plugin - vp := &config.Viper{} - vp.Path = "configs/.rr.yaml" - vp.Prefix = "rr" - err = container.Register(vp) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&server.Plugin{}) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&Foo{}) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&logger.ZapLogger{}) - if err != nil { - t.Fatal(err) - } - - err = container.Init() - if err != nil { - t.Fatal(err) - } - - errCh, err := container.Serve() - if err != nil { - t.Fatal(err) - } - - // stop by CTRL+C - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - - tt := time.NewTimer(time.Second * 10) - wg := &sync.WaitGroup{} - wg.Add(1) - - go func() { - defer wg.Done() - defer tt.Stop() - for { - select { - case e := <-errCh: - assert.NoError(t, e.Error) - assert.NoError(t, container.Stop()) - return - case <-c: - er := container.Stop() - assert.NoError(t, er) - return - case <-tt.C: - assert.NoError(t, container.Stop()) - return - } - } - }() - - wg.Wait() -} - -func TestAppSockets(t *testing.T) { - container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.ErrorLevel)) - if err != nil { - t.Fatal(err) - } - // config plugin - vp := &config.Viper{} - vp.Path = "configs/.rr-sockets.yaml" - vp.Prefix = "rr" - err = container.Register(vp) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&server.Plugin{}) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&Foo2{}) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&logger.ZapLogger{}) - if err != nil { - t.Fatal(err) - } - - err = container.Init() - if err != nil { - t.Fatal(err) - } - - errCh, err := container.Serve() - if err != nil { - t.Fatal(err) - } - - // stop by CTRL+C - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - - // stop after 10 seconds - tt := time.NewTicker(time.Second * 10) - - for { - select { - case e := <-errCh: - assert.NoError(t, e.Error) - assert.NoError(t, container.Stop()) - return - case <-c: - er := container.Stop() - if er != nil { - panic(er) - } - return - case <-tt.C: - tt.Stop() - assert.NoError(t, container.Stop()) - return - } - } -} - -func TestAppTCP(t *testing.T) { - container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.ErrorLevel)) - if err != nil { - t.Fatal(err) - } - // config plugin - vp := &config.Viper{} - vp.Path = "configs/.rr-tcp.yaml" - vp.Prefix = "rr" - err = container.Register(vp) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&server.Plugin{}) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&Foo3{}) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&logger.ZapLogger{}) - if err != nil { - t.Fatal(err) - } - - err = container.Init() - if err != nil { - t.Fatal(err) - } - - errCh, err := container.Serve() - if err != nil { - t.Fatal(err) - } - - // stop by CTRL+C - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - - // stop after 10 seconds - tt := time.NewTicker(time.Second * 10) - - for { - select { - case e := <-errCh: - assert.NoError(t, e.Error) - assert.NoError(t, container.Stop()) - return - case <-c: - er := container.Stop() - if er != nil { - panic(er) - } - return - case <-tt.C: - tt.Stop() - assert.NoError(t, container.Stop()) - return - } - } -} - -func TestAppWrongConfig(t *testing.T) { - container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.ErrorLevel)) - if err != nil { - t.Fatal(err) - } - // config plugin - vp := &config.Viper{} - vp.Path = "configs/.rrrrrrrrrr.yaml" - vp.Prefix = "rr" - err = container.Register(vp) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&server.Plugin{}) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&Foo3{}) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&logger.ZapLogger{}) - if err != nil { - t.Fatal(err) - } - - assert.Error(t, container.Init()) -} - -func TestAppWrongRelay(t *testing.T) { - container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.ErrorLevel)) - if err != nil { - t.Fatal(err) - } - // config plugin - vp := &config.Viper{} - vp.Path = "configs/.rr-wrong-relay.yaml" - vp.Prefix = "rr" - err = container.Register(vp) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&server.Plugin{}) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&Foo3{}) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&logger.ZapLogger{}) - if err != nil { - t.Fatal(err) - } - - err = container.Init() - assert.Error(t, err) - - _, err = container.Serve() - assert.Error(t, err) -} - -func TestAppWrongCommand(t *testing.T) { - container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.ErrorLevel)) - if err != nil { - t.Fatal(err) - } - // config plugin - vp := &config.Viper{} - vp.Path = "configs/.rr-wrong-command.yaml" - vp.Prefix = "rr" - err = container.Register(vp) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&server.Plugin{}) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&Foo3{}) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&logger.ZapLogger{}) - if err != nil { - t.Fatal(err) - } - - err = container.Init() - if err != nil { - t.Fatal(err) - } - - _, err = container.Serve() - assert.Error(t, err) -} - -func TestAppNoAppSectionInConfig(t *testing.T) { - container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.ErrorLevel)) - if err != nil { - t.Fatal(err) - } - // config plugin - vp := &config.Viper{} - vp.Path = "configs/.rr-wrong-command.yaml" - vp.Prefix = "rr" - err = container.Register(vp) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&server.Plugin{}) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&Foo3{}) - if err != nil { - t.Fatal(err) - } - - err = container.Register(&logger.ZapLogger{}) - if err != nil { - t.Fatal(err) - } - - err = container.Init() - if err != nil { - t.Fatal(err) - } - - _, err = container.Serve() - assert.Error(t, err) -} diff --git a/tests/plugins/server/socket.php b/tests/plugins/server/socket.php deleted file mode 100644 index 3159c445..00000000 --- a/tests/plugins/server/socket.php +++ /dev/null @@ -1,25 +0,0 @@ -<?php -/** - * @var Goridge\RelayInterface $relay - */ - -use Spiral\Goridge; -use Spiral\RoadRunner; - -require dirname(__DIR__) . "/../vendor/autoload.php"; - -$relay = new Goridge\SocketRelay( - "unix.sock", - null, - Goridge\SocketRelay::SOCK_UNIX - ); - -$rr = new RoadRunner\Worker($relay); - -while ($in = $rr->waitPayload()) { - try { - $rr->send((string)$in->body); - } catch (\Throwable $e) { - $rr->error((string)$e); - } -} diff --git a/tests/plugins/server/tcp.php b/tests/plugins/server/tcp.php deleted file mode 100644 index 88c49848..00000000 --- a/tests/plugins/server/tcp.php +++ /dev/null @@ -1,20 +0,0 @@ -<?php -/** - * @var Goridge\RelayInterface $relay - */ - -use Spiral\Goridge; -use Spiral\RoadRunner; - -require dirname(__DIR__) . "/../vendor/autoload.php"; - -$relay = new Goridge\SocketRelay("localhost", 9999); -$rr = new RoadRunner\Worker($relay); - -while ($in = $rr->waitPayload()) { - try { - $rr->send((string)$in->body); - } catch (\Throwable $e) { - $rr->error((string)$e); - } -} diff --git a/tests/plugins/static/config_test.go b/tests/plugins/static/config_test.go deleted file mode 100644 index d73fd845..00000000 --- a/tests/plugins/static/config_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package static - -import ( - "testing" - - "github.com/spiral/roadrunner/v2/plugins/static" - "github.com/stretchr/testify/assert" -) - -func TestConfig_Forbids(t *testing.T) { - cfg := static.Config{Static: &struct { - Dir string - Forbid []string - Always []string - Request map[string]string - Response map[string]string - }{Dir: "", Forbid: []string{".php"}, Always: nil, Request: nil, Response: nil}} - - assert.True(t, cfg.AlwaysForbid("index.php")) - assert.True(t, cfg.AlwaysForbid("index.PHP")) - assert.True(t, cfg.AlwaysForbid("phpadmin/index.bak.php")) - assert.False(t, cfg.AlwaysForbid("index.html")) -} - -func TestConfig_Valid(t *testing.T) { - assert.NoError(t, (&static.Config{Static: &struct { - Dir string - Forbid []string - Always []string - Request map[string]string - Response map[string]string - }{Dir: "./"}}).Valid()) - - assert.Error(t, (&static.Config{Static: &struct { - Dir string - Forbid []string - Always []string - Request map[string]string - Response map[string]string - }{Dir: "./http.go"}}).Valid()) - - assert.Error(t, (&static.Config{Static: &struct { - Dir string - Forbid []string - Always []string - Request map[string]string - Response map[string]string - }{Dir: "./dir/"}}).Valid()) -} diff --git a/tests/plugins/static/configs/.rr-http-static-disabled.yaml b/tests/plugins/static/configs/.rr-http-static-disabled.yaml deleted file mode 100644 index 9f04b8a9..00000000 --- a/tests/plugins/static/configs/.rr-http-static-disabled.yaml +++ /dev/null @@ -1,32 +0,0 @@ -server: - command: "php ../../http/client.php pid pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -http: - debug: true - address: 127.0.0.1:21234 - max_request_size: 1024 - middleware: [ "gzip" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - static: - dir: "abc" #not exists - forbid: [ ".php", ".htaccess" ] - request: - Example-Request-Header: "Value" - response: - X-Powered-By: "RoadRunner" - pool: - num_workers: 2 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/static/configs/.rr-http-static-files-disable.yaml b/tests/plugins/static/configs/.rr-http-static-files-disable.yaml deleted file mode 100644 index 3d4d50b9..00000000 --- a/tests/plugins/static/configs/.rr-http-static-files-disable.yaml +++ /dev/null @@ -1,33 +0,0 @@ -server: - command: "php ../../http/client.php echo pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -http: - debug: true - address: 127.0.0.1:45877 - max_request_size: 1024 - middleware: [ "gzip" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - static: - dir: "../../../tests" - forbid: [ ".php" ] - request: - Example-Request-Header: "Value" - # Automatically add headers to every response. - response: - X-Powered-By: "RoadRunner" - pool: - num_workers: 2 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/static/configs/.rr-http-static-files.yaml b/tests/plugins/static/configs/.rr-http-static-files.yaml deleted file mode 100644 index deb1f253..00000000 --- a/tests/plugins/static/configs/.rr-http-static-files.yaml +++ /dev/null @@ -1,34 +0,0 @@ -server: - command: "php ../../http/client.php echo pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -http: - debug: true - address: 127.0.0.1:34653 - max_request_size: 1024 - middleware: [ "gzip", "static" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - static: - dir: "../../../tests" - forbid: [ ".php", ".htaccess" ] - always: [ ".ico" ] - request: - "Example-Request-Header": "Value" - # Automatically add headers to every response. - response: - "X-Powered-By": "RoadRunner" - pool: - num_workers: 2 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/static/configs/.rr-http-static.yaml b/tests/plugins/static/configs/.rr-http-static.yaml deleted file mode 100644 index e5af9043..00000000 --- a/tests/plugins/static/configs/.rr-http-static.yaml +++ /dev/null @@ -1,31 +0,0 @@ -server: - command: "php ../../http/client.php pid pipes" - user: "" - group: "" - env: - "RR_HTTP": "true" - relay: "pipes" - relay_timeout: "20s" - -http: - address: 127.0.0.1:21603 - max_request_size: 1024 - middleware: [ "gzip", "static" ] - trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] - uploads: - forbid: [ ".php", ".exe", ".bat" ] - static: - dir: "../../../tests" - forbid: [ "" ] - request: - "input": "custom-header" - response: - "output": "output-header" - pool: - num_workers: 2 - max_jobs: 0 - allocate_timeout: 60s - destroy_timeout: 60s -logs: - mode: development - level: error
\ No newline at end of file diff --git a/tests/plugins/static/static_plugin_test.go b/tests/plugins/static/static_plugin_test.go deleted file mode 100644 index d43ef765..00000000 --- a/tests/plugins/static/static_plugin_test.go +++ /dev/null @@ -1,439 +0,0 @@ -package static - -import ( - "bytes" - "io" - "io/ioutil" - "net/http" - "os" - "os/signal" - "sync" - "syscall" - "testing" - "time" - - "github.com/golang/mock/gomock" - endure "github.com/spiral/endure/pkg/container" - "github.com/spiral/roadrunner/v2/plugins/config" - "github.com/spiral/roadrunner/v2/plugins/gzip" - httpPlugin "github.com/spiral/roadrunner/v2/plugins/http" - "github.com/spiral/roadrunner/v2/plugins/logger" - "github.com/spiral/roadrunner/v2/plugins/server" - "github.com/spiral/roadrunner/v2/plugins/static" - "github.com/spiral/roadrunner/v2/tests/mocks" - "github.com/stretchr/testify/assert" -) - -func TestStaticPlugin(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-http-static.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &gzip.Gzip{}, - &static.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second) - t.Run("ServeSample", serveStaticSample) - t.Run("StaticNotForbid", staticNotForbid) - t.Run("StaticHeaders", staticHeaders) - - stopCh <- struct{}{} - wg.Wait() -} - -func staticHeaders(t *testing.T) { - req, err := http.NewRequest("GET", "http://localhost:21603/client.php", nil) - if err != nil { - t.Fatal(err) - } - - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatal(err) - } - - if resp.Header.Get("Output") != "output-header" { - t.Fatal("can't find output header in response") - } - - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - - defer func() { - _ = resp.Body.Close() - }() - - assert.Equal(t, all("../../../tests/client.php"), string(b)) - assert.Equal(t, all("../../../tests/client.php"), string(b)) -} - -func staticNotForbid(t *testing.T) { - b, r, err := get("http://localhost:21603/client.php") - assert.NoError(t, err) - assert.Equal(t, all("../../../tests/client.php"), b) - assert.Equal(t, all("../../../tests/client.php"), b) - _ = r.Body.Close() -} - -func serveStaticSample(t *testing.T) { - b, r, err := get("http://localhost:21603/sample.txt") - assert.NoError(t, err) - assert.Equal(t, "sample", b) - _ = r.Body.Close() -} - -func TestStaticDisabled(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-http-static-disabled.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &gzip.Gzip{}, - &static.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second) - t.Run("StaticDisabled", staticDisabled) - - stopCh <- struct{}{} - wg.Wait() -} - -func staticDisabled(t *testing.T) { - _, r, err := get("http://localhost:21234/sample.txt") //nolint:bodyclose - assert.NoError(t, err) - assert.NotNil(t, r) - assert.Empty(t, r.Header.Get("X-Powered-By")) -} - -func TestStaticFilesDisabled(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-http-static-files-disable.yaml", - Prefix: "rr", - } - - err = cont.RegisterAll( - cfg, - &logger.ZapLogger{}, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &gzip.Gzip{}, - &static.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second) - t.Run("StaticFilesDisabled", staticFilesDisabled) - - stopCh <- struct{}{} - wg.Wait() -} - -func staticFilesDisabled(t *testing.T) { - b, r, err := get("http://localhost:45877/client.php?hello=world") - if err != nil { - t.Fatal(err) - } - assert.Equal(t, "WORLD", b) - _ = r.Body.Close() -} - -func TestStaticFilesForbid(t *testing.T) { - cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) - assert.NoError(t, err) - - cfg := &config.Viper{ - Path: "configs/.rr-http-static-files.yaml", - Prefix: "rr", - } - - controller := gomock.NewController(t) - mockLogger := mocks.NewMockLogger(controller) - - mockLogger.EXPECT().Debug("worker destructed", "pid", gomock.Any()).AnyTimes() - mockLogger.EXPECT().Debug("worker constructed", "pid", gomock.Any()).AnyTimes() - mockLogger.EXPECT().Debug("", "remote", gomock.Any(), "ts", gomock.Any(), "resp.status", gomock.Any(), "method", gomock.Any(), "uri", gomock.Any()).AnyTimes() - mockLogger.EXPECT().Error("file open error", "error", gomock.Any()).AnyTimes() - mockLogger.EXPECT().Error(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() // placeholder for the workerlogerror - - err = cont.RegisterAll( - cfg, - mockLogger, - &server.Plugin{}, - &httpPlugin.Plugin{}, - &gzip.Gzip{}, - &static.Plugin{}, - ) - assert.NoError(t, err) - - err = cont.Init() - if err != nil { - t.Fatal(err) - } - - ch, err := cont.Serve() - assert.NoError(t, err) - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - wg.Add(1) - - stopCh := make(chan struct{}, 1) - - go func() { - defer wg.Done() - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }() - - time.Sleep(time.Second) - t.Run("StaticTestFilesDir", staticTestFilesDir) - t.Run("StaticNotFound", staticNotFound) - t.Run("StaticFilesForbid", staticFilesForbid) - t.Run("StaticFilesAlways", staticFilesAlways) - - stopCh <- struct{}{} - wg.Wait() -} - -func staticTestFilesDir(t *testing.T) { - b, r, err := get("http://localhost:34653/http?hello=world") - assert.NoError(t, err) - assert.Equal(t, "WORLD", b) - _ = r.Body.Close() -} - -func staticNotFound(t *testing.T) { - b, _, _ := get("http://localhost:34653/client.XXX?hello=world") //nolint:bodyclose - assert.Equal(t, "WORLD", b) -} - -func staticFilesAlways(t *testing.T) { - _, r, err := get("http://localhost:34653/favicon.ico") - assert.NoError(t, err) - assert.Equal(t, 404, r.StatusCode) - _ = r.Body.Close() -} - -func staticFilesForbid(t *testing.T) { - b, r, err := get("http://localhost:34653/client.php?hello=world") - if err != nil { - t.Fatal(err) - } - assert.Equal(t, "WORLD", b) - _ = r.Body.Close() -} - -// HELPERS -func get(url string) (string, *http.Response, error) { - r, err := http.Get(url) //nolint:gosec - if err != nil { - return "", nil, err - } - - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return "", nil, err - } - - err = r.Body.Close() - if err != nil { - return "", nil, err - } - - return string(b), r, err -} - -func all(fn string) string { - f, _ := os.Open(fn) - - b := new(bytes.Buffer) - _, err := io.Copy(b, f) - if err != nil { - return "" - } - - err = f.Close() - if err != nil { - return "" - } - - return b.String() -} diff --git a/tests/psr-worker-bench.php b/tests/psr-worker-bench.php deleted file mode 100644 index ef741a61..00000000 --- a/tests/psr-worker-bench.php +++ /dev/null @@ -1,58 +0,0 @@ -<?php - -declare(strict_types=1); - -use Spiral\RoadRunner; -use Nyholm\Psr7\Factory; - -ini_set('display_errors', 'stderr'); -include "vendor/autoload.php"; - -$env = \Spiral\RoadRunner\Environment::fromGlobals(); - -if ($env->getMode() === 'http') { - $worker = new RoadRunner\Http\PSR7Worker( - RoadRunner\Worker::create(), - new Factory\Psr17Factory(), - new Factory\Psr17Factory(), - new Factory\Psr17Factory() - ); - - while ($req = $worker->waitRequest()) { - try { - $rsp = new \Nyholm\Psr7\Response(); - $rsp->getBody()->write("hello world"); - $worker->respond($rsp); - } catch (\Throwable $e) { - $worker->getWorker()->error((string)$e); - } - } -} else { - /** - * @param string $dir - * @return array<string> - */ - $getClasses = static function (string $dir): iterable { - $files = glob($dir . '/*.php'); - - foreach ($files as $file) { - yield substr(basename($file), 0, -4); - } - }; - - $factory = \Temporal\WorkerFactory::create(); - - $worker = $factory->newWorker('default'); - - // register all workflows - foreach ($getClasses(__DIR__ . '/../temporal/Workflow') as $name) { - $worker->registerWorkflowType('Temporal\\Tests\\Workflow\\' . $name); - } - - // register all activity - foreach ($getClasses(__DIR__ . '/../temporal/Activity') as $name) { - $worker->registerActivityType('Temporal\\Tests\\Activity\\' . $name); - } - - $factory->run(); -}
\ No newline at end of file diff --git a/tests/psr-worker.php b/tests/psr-worker.php deleted file mode 100644 index db53eee2..00000000 --- a/tests/psr-worker.php +++ /dev/null @@ -1,28 +0,0 @@ -<?php -/** - * @var Goridge\RelayInterface $relay - */ -use Spiral\Goridge; -use Spiral\RoadRunner; - -ini_set('display_errors', 'stderr'); -require __DIR__ . "/vendor/autoload.php"; - -$worker = new RoadRunner\Worker(new Goridge\StreamRelay(STDIN, STDOUT)); -$psr7 = new RoadRunner\Http\PSR7Worker( - $worker, - new \Nyholm\Psr7\Factory\Psr17Factory(), - new \Nyholm\Psr7\Factory\Psr17Factory(), - new \Nyholm\Psr7\Factory\Psr17Factory() -); - -while ($req = $psr7->waitRequest()) { - try { - $resp = new \Nyholm\Psr7\Response(); - $resp->getBody()->write(str_repeat("hello world", 1000)); - - $psr7->respond($resp); - } catch (\Throwable $e) { - $psr7->getWorker()->error((string)$e); - } -} diff --git a/tests/sample.txt b/tests/sample.txt deleted file mode 100644 index eed7e79a..00000000 --- a/tests/sample.txt +++ /dev/null @@ -1 +0,0 @@ -sample
\ No newline at end of file diff --git a/tests/sleep.php b/tests/sleep.php deleted file mode 100644 index e34a6834..00000000 --- a/tests/sleep.php +++ /dev/null @@ -1,15 +0,0 @@ -<?php - -declare(strict_types=1); - -use Spiral\Goridge\StreamRelay; -use Spiral\RoadRunner\Worker as RoadRunner; - -require __DIR__ . "/vendor/autoload.php"; - -$rr = new RoadRunner(new StreamRelay(\STDIN, \STDOUT)); - -while($rr->waitPayload()){ - sleep(3); - $rr->send(""); -} diff --git a/tests/slow-client.php b/tests/slow-client.php deleted file mode 100644 index 7737f0b1..00000000 --- a/tests/slow-client.php +++ /dev/null @@ -1,38 +0,0 @@ -<?php - -use Spiral\Goridge; - -ini_set('display_errors', 'stderr'); -require __DIR__ . "/vendor/autoload.php"; - -if (count($argv) < 3) { - die("need 2 arguments"); -} - -list($test, $goridge, $bootDelay, $shutdownDelay) = [$argv[1], $argv[2], $argv[3], $argv[4]]; - -switch ($goridge) { - case "pipes": - $relay = new Goridge\StreamRelay(STDIN, STDOUT); - break; - - case "tcp": - $relay = new Goridge\SocketRelay("localhost", 9007); - break; - - case "unix": - $relay = new Goridge\SocketRelay( - "sock.unix", - null, - Goridge\SocketRelay::SOCK_UNIX - ); - - break; - - default: - die("invalid protocol selection"); -} - -usleep($bootDelay * 1000); -require_once sprintf("%s/%s.php", __DIR__, $test); -usleep($shutdownDelay * 1000); diff --git a/tests/slow-destroy.php b/tests/slow-destroy.php deleted file mode 100644 index 900bb68a..00000000 --- a/tests/slow-destroy.php +++ /dev/null @@ -1,37 +0,0 @@ -<?php - -use Spiral\Goridge; - -ini_set('display_errors', 'stderr'); -require __DIR__ . "/vendor/autoload.php"; - -if (count($argv) < 3) { - die("need 2 arguments"); -} - -list($test, $goridge) = [$argv[1], $argv[2]]; - -switch ($goridge) { - case "pipes": - $relay = new Goridge\StreamRelay(STDIN, STDOUT); - break; - - case "tcp": - $relay = new Goridge\SocketRelay("localhost", 9007); - break; - - case "unix": - $relay = new Goridge\SocketRelay( - "sock.unix", - null, - Goridge\SocketRelay::SOCK_UNIX - ); - break; - - default: - die("invalid protocol selection"); -} - -require_once sprintf("%s/%s.php", __DIR__, $test); - -sleep(10); diff --git a/tests/slow-pid.php b/tests/slow-pid.php deleted file mode 100644 index 3660cb40..00000000 --- a/tests/slow-pid.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php - /** - * @var Goridge\RelayInterface $relay - */ - - use Spiral\Goridge; - use Spiral\RoadRunner; - - $rr = new RoadRunner\Worker($relay); - - while ($in = $rr->waitPayload()) { - try { - sleep(1); - $rr->send((string)getmypid()); - } catch (\Throwable $e) { - $rr->error((string)$e); - } - } diff --git a/tests/stop.php b/tests/stop.php deleted file mode 100644 index f83d3f29..00000000 --- a/tests/stop.php +++ /dev/null @@ -1,25 +0,0 @@ -<?php -/** - * @var Goridge\RelayInterface $relay - */ - -use Spiral\Goridge; -use Spiral\RoadRunner; - -$rr = new RoadRunner\Worker($relay); - -$used = false; -while ($in = $rr->waitPayload()) { - try { - if ($used) { - // kill on second attempt - $rr->stop(); - continue; - } - - $used = true; - $rr->send((string)getmypid()); - } catch (\Throwable $e) { - $rr->error((string)$e); - } -} diff --git a/tools/process.go b/tools/process.go deleted file mode 100644 index a6eb1139..00000000 --- a/tools/process.go +++ /dev/null @@ -1,44 +0,0 @@ -package tools - -import ( - "github.com/shirou/gopsutil/process" - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/pkg/worker" -) - -// ProcessState provides information about specific worker. -type ProcessState struct { - // Pid contains process id. - Pid int `json:"pid"` - - // Status of the worker. - Status string `json:"status"` - - // Number of worker executions. - NumJobs uint64 `json:"numExecs"` - - // Created is unix nano timestamp of worker creation time. - Created int64 `json:"created"` - - // MemoryUsage holds the information about worker memory usage in bytes. - // Values might vary for different operating systems and based on RSS. - MemoryUsage uint64 `json:"memoryUsage"` -} - -// WorkerProcessState creates new worker state definition. -func WorkerProcessState(w worker.BaseProcess) (ProcessState, error) { - const op = errors.Op("worker_process_state") - p, _ := process.NewProcess(int32(w.Pid())) - i, err := p.MemoryInfo() - if err != nil { - return ProcessState{}, errors.E(op, err) - } - - return ProcessState{ - Pid: int(w.Pid()), - Status: w.State().String(), - NumJobs: w.State().NumExecs(), - Created: w.Created().UnixNano(), - MemoryUsage: i.RSS, - }, nil -} diff --git a/tools/worker_table.go b/tools/worker_table.go deleted file mode 100644 index 20b8084f..00000000 --- a/tools/worker_table.go +++ /dev/null @@ -1,62 +0,0 @@ -package tools - -import ( - "io" - "strconv" - "time" - - "github.com/dustin/go-humanize" - "github.com/fatih/color" - "github.com/olekukonko/tablewriter" -) - -// WorkerTable renders table with information about rr server workers. -func WorkerTable(writer io.Writer, workers []ProcessState) *tablewriter.Table { - tw := tablewriter.NewWriter(writer) - tw.SetHeader([]string{"PID", "Status", "Execs", "Memory", "Created"}) - tw.SetColMinWidth(0, 7) - tw.SetColMinWidth(1, 9) - tw.SetColMinWidth(2, 7) - tw.SetColMinWidth(3, 7) - tw.SetColMinWidth(4, 18) - - for key := range workers { - tw.Append([]string{ - strconv.Itoa(workers[key].Pid), - renderStatus(workers[key].Status), - renderJobs(workers[key].NumJobs), - humanize.Bytes(workers[key].MemoryUsage), - renderAlive(time.Unix(0, workers[key].Created)), - }) - } - - return tw -} - -func renderStatus(status string) string { - switch status { - case "inactive": - return color.YellowString("inactive") - case "ready": - return color.CyanString("ready") - case "working": - return color.GreenString("working") - case "invalid": - return color.YellowString("invalid") - case "stopped": - return color.RedString("stopped") - case "errored": - return color.RedString("errored") - } - - return status -} - -func renderJobs(number uint64) string { - // TODO overflow - return humanize.Comma(int64(number)) -} - -func renderAlive(t time.Time) string { - return humanize.RelTime(t, time.Now(), "ago", "") -} diff --git a/utils/doc.go b/utils/doc.go deleted file mode 100755 index 2c1c0d9c..00000000 --- a/utils/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -package utils - -/* -This package should not contain roadrunner dependencies, only system or third-party -*/ diff --git a/utils/isolate.go b/utils/isolate.go deleted file mode 100755 index b05f4b3a..00000000 --- a/utils/isolate.go +++ /dev/null @@ -1,59 +0,0 @@ -// +build !windows - -package utils - -import ( - "fmt" - "os" - "os/exec" - "os/user" - "strconv" - "syscall" - - "github.com/spiral/errors" -) - -// IsolateProcess change gpid for the process to avoid bypassing signals to php processes. -func IsolateProcess(cmd *exec.Cmd) { - cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true, Pgid: 0} -} - -// ExecuteFromUser may work only if run RR under root user -func ExecuteFromUser(cmd *exec.Cmd, u string) error { - const op = errors.Op("execute_from_user") - usr, err := user.Lookup(u) - if err != nil { - return errors.E(op, err) - } - - usrI32, err := strconv.ParseInt(usr.Uid, 10, 32) - if err != nil { - return errors.E(op, err) - } - - grI32, err := strconv.ParseInt(usr.Gid, 10, 32) - if err != nil { - return errors.E(op, err) - } - - // For more information: - // https://www.man7.org/linux/man-pages/man7/user_namespaces.7.html - // https://www.man7.org/linux/man-pages/man7/namespaces.7.html - if _, err := os.Stat("/proc/self/ns/user"); err != nil { - if os.IsNotExist(err) { - return fmt.Errorf("kernel doesn't support user namespaces") - } - if os.IsPermission(err) { - return fmt.Errorf("unable to test user namespaces due to permissions") - } - - return errors.E(op, errors.Errorf("failed to stat /proc/self/ns/user: %v", err)) - } - - cmd.SysProcAttr.Credential = &syscall.Credential{ - Uid: uint32(usrI32), - Gid: uint32(grI32), - } - - return nil -} diff --git a/utils/isolate_win.go b/utils/isolate_win.go deleted file mode 100755 index b2b213a8..00000000 --- a/utils/isolate_win.go +++ /dev/null @@ -1,17 +0,0 @@ -// +build windows - -package utils - -import ( - "os/exec" - "syscall" -) - -// IsolateProcess change gpid for the process to avoid bypassing signals to php processes. -func IsolateProcess(cmd *exec.Cmd) { - cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP} -} - -func ExecuteFromUser(cmd *exec.Cmd, u string) error { - return nil -} diff --git a/utils/network.go b/utils/network.go deleted file mode 100755 index e57854a8..00000000 --- a/utils/network.go +++ /dev/null @@ -1,79 +0,0 @@ -// +build linux darwin freebsd - -package utils - -import ( - "fmt" - "net" - "os" - "strings" - "syscall" - - "github.com/valyala/tcplisten" -) - -// - SO_REUSEPORT. This option allows linear scaling server performance -// on multi-CPU servers. -// See https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/ for details. -// -// - TCP_DEFER_ACCEPT. This option expects the server reads from the accepted -// connection before writing to them. -// -// - TCP_FASTOPEN. See https://lwn.net/Articles/508865/ for details. -// CreateListener crates socket listener based on DSN definition. -func CreateListener(address string) (net.Listener, error) { - dsn := strings.Split(address, "://") - - switch len(dsn) { - case 1: - // assume, that there is no prefix here [127.0.0.1:8000] - return createTCPListener(dsn[0]) - case 2: - // we got two part here, first part is the transport, second - address - // [tcp://127.0.0.1:8000] OR [unix:///path/to/unix.socket] OR [error://path] - // where error is wrong transport name - switch dsn[0] { - case "unix": - // check of file exist. If exist, unlink - if fileExists(dsn[1]) { - err := syscall.Unlink(dsn[1]) - if err != nil { - return nil, fmt.Errorf("error during the unlink syscall: error %v", err) - } - } - return net.Listen(dsn[0], dsn[1]) - case "tcp": - return createTCPListener(dsn[1]) - // not an tcp or unix - default: - return nil, fmt.Errorf("invalid Protocol ([tcp://]:6001, unix://file.sock), address: %s", address) - } - // wrong number of split parts - default: - return nil, fmt.Errorf("wrong number of parsed protocol parts, address: %s", address) - } -} - -func createTCPListener(addr string) (net.Listener, error) { - cfg := tcplisten.Config{ - ReusePort: true, - DeferAccept: true, - FastOpen: true, - Backlog: 0, - } - listener, err := cfg.NewListener("tcp4", addr) - if err != nil { - return nil, err - } - return listener, nil -} - -// fileExists checks if a file exists and is not a directory before we -// try using it to prevent further errors. -func fileExists(filename string) bool { - info, err := os.Stat(filename) - if os.IsNotExist(err) { - return false - } - return !info.IsDir() -} diff --git a/utils/network_windows.go b/utils/network_windows.go deleted file mode 100755 index 6eefb8f7..00000000 --- a/utils/network_windows.go +++ /dev/null @@ -1,63 +0,0 @@ -// +build windows - -package utils - -import ( - "fmt" - "net" - "os" - "strings" - "syscall" -) - -// CreateListener crates socket listener based on DSN definition. -func CreateListener(address string) (net.Listener, error) { - dsn := strings.Split(address, "://") - - switch len(dsn) { - case 1: - // assume, that there is no prefix here [127.0.0.1:8000] - return createTCPListener(dsn[0]) - case 2: - // we got two part here, first part is the transport, second - address - // [tcp://127.0.0.1:8000] OR [unix:///path/to/unix.socket] OR [error://path] - // where error is wrong transport name - switch dsn[0] { - case "unix": - // check of file exist. If exist, unlink - if fileExists(dsn[1]) { - err := syscall.Unlink(dsn[1]) - if err != nil { - return nil, fmt.Errorf("error during the unlink syscall: error %v", err) - } - } - return net.Listen(dsn[0], dsn[1]) - case "tcp": - return createTCPListener(dsn[1]) - // not an tcp or unix - default: - return nil, fmt.Errorf("invalid Protocol ([tcp://]:6001, unix://file.sock), address: %s", address) - } - // wrong number of split parts - default: - return nil, fmt.Errorf("wrong number of parsed protocol parts, address: %s", address) - } -} - -func createTCPListener(addr string) (net.Listener, error) { - listener, err := net.Listen("tcp", addr) - if err != nil { - return nil, err - } - return listener, nil -} - -// fileExists checks if a file exists and is not a directory before we -// try using it to prevent further errors. -func fileExists(filename string) bool { - info, err := os.Stat(filename) - if os.IsNotExist(err) { - return false - } - return !info.IsDir() -} |